From 08fbaed98ac992a6ffd84f996c42714d87c5c59c Mon Sep 17 00:00:00 2001 From: Yolanda Robla Date: Tue, 16 Apr 2019 12:40:24 +0200 Subject: [PATCH] Rewrite terraform to support dynamic number of nodes Change the data structure to accept data of nodes using maps. This will allow to add a variable number of masters (1,3) in the future. Added a new dependency on rodaine/hclencoder, to render terraform data in HCL instead of JSON, to avoid a bug in JSON parsing: https://github.com/hashicorp/terraform/issues/15549 --- Gopkg.lock | 8 + data/data/baremetal/main.tf | 18 +- data/data/baremetal/masters/main.tf | 82 +--- data/data/baremetal/masters/variables.tf | 57 +-- data/data/baremetal/variables-baremetal.tf | 58 +-- pkg/tfvars/baremetal/baremetal.go | 54 +-- pkg/types/baremetal/platform.go | 9 +- .../github.com/hashicorp/hcl/LICENSE | 0 .../github.com/hashicorp/hcl/decoder.go | 0 .../github.com/hashicorp/hcl/hcl.go | 0 .../github.com/hashicorp/hcl/hcl/ast/ast.go | 0 .../github.com/hashicorp/hcl/hcl/ast/walk.go | 0 .../hashicorp/hcl/hcl/fmtcmd/fmtcmd.go | 162 +++++++ .../hashicorp/hcl/hcl/parser/error.go | 0 .../hashicorp/hcl/hcl/parser/parser.go | 0 .../hashicorp/hcl/hcl/printer/nodes.go | 0 .../hashicorp/hcl/hcl/printer/printer.go | 0 .../hashicorp/hcl/hcl/scanner/scanner.go | 0 .../hashicorp/hcl/hcl/strconv/quote.go | 0 .../hashicorp/hcl/hcl/token/position.go | 0 .../hashicorp/hcl/hcl/token/token.go | 0 .../hashicorp/hcl/json/parser/flatten.go | 0 .../hashicorp/hcl/json/parser/parser.go | 0 .../hashicorp/hcl/json/scanner/scanner.go | 0 .../hashicorp/hcl/json/token/position.go | 0 .../hashicorp/hcl/json/token/token.go | 0 .../github.com/hashicorp/hcl/lex.go | 0 .../github.com/hashicorp/hcl/parse.go | 0 vendor/github.com/rodaine/hclencoder/LICENSE | 9 + .../rodaine/hclencoder/hclencoder.go | 35 ++ vendor/github.com/rodaine/hclencoder/nodes.go | 428 ++++++++++++++++++ .../github.com/rodaine/hclencoder/walker.go | 108 +++++ 32 files changed, 799 insertions(+), 229 deletions(-) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/LICENSE (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/decoder.go (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/hcl.go (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/hcl/ast/ast.go (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/hcl/ast/walk.go (100%) create mode 100644 vendor/github.com/hashicorp/hcl/hcl/fmtcmd/fmtcmd.go rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/hcl/parser/error.go (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/hcl/parser/parser.go (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/hcl/printer/nodes.go (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/hcl/printer/printer.go (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/hcl/scanner/scanner.go (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/hcl/strconv/quote.go (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/hcl/token/position.go (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/hcl/token/token.go (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/json/parser/flatten.go (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/json/parser/parser.go (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/json/scanner/scanner.go (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/json/token/position.go (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/json/token/token.go (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/lex.go (100%) rename {pkg/terraform/exec/vendor => vendor}/github.com/hashicorp/hcl/parse.go (100%) create mode 100644 vendor/github.com/rodaine/hclencoder/LICENSE create mode 100644 vendor/github.com/rodaine/hclencoder/hclencoder.go create mode 100644 vendor/github.com/rodaine/hclencoder/nodes.go create mode 100644 vendor/github.com/rodaine/hclencoder/walker.go diff --git a/Gopkg.lock b/Gopkg.lock index d7bcbed38..4d345d2ea 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -601,6 +601,14 @@ revision = "792786c7400a136282c1664665ae0a8db921c6c2" version = "v1.0.0" +[[projects]] + branch = "master" + digest = "1:07de8180d5ce67dad2cd9fd94227a5d8612424cf80f2023d1386f4ff5e272567" + name = "github.com/rodaine/hclencoder" + packages = ["."] + pruneopts = "NUT" + revision = "fb9757bb536eb49d3d16503228b4bf6b258907f6" + [[projects]] branch = "master" digest = "1:7ca2584fa7da0520cd2d1136a10194fe5a5b220bdb215074ab6f7b5ad91115f4" diff --git a/data/data/baremetal/main.tf b/data/data/baremetal/main.tf index 0d29baa8b..0761d9406 100644 --- a/data/data/baremetal/main.tf +++ b/data/data/baremetal/main.tf @@ -26,19 +26,9 @@ module "masters" { root_gb = "${var.master_configuration["root_gb"]}" root_disk = "${var.master_configuration["root_disk"]}" - master_0 = "${var.master_0}" - properties_0 = "${var.properties_0}" - root_device_0 = "${var.root_device_0}" - driver_info_0 = "${var.driver_info_0}" - - master_1 = "${var.master_1}" - properties_1 = "${var.properties_1}" - root_device_1 = "${var.root_device_1}" - driver_info_1 = "${var.driver_info_1}" - - master_2 = "${var.master_2}" - properties_2 = "${var.properties_2}" - root_device_2 = "${var.root_device_2}" - driver_info_2 = "${var.driver_info_2}" + master_nodes = "${var.master_nodes}" + properties = "${var.properties}" + root_devices = "${var.root_devices}" + driver_infos = "${var.driver_infos}" } diff --git a/data/data/baremetal/masters/main.tf b/data/data/baremetal/masters/main.tf index 14343deba..7d369d79a 100644 --- a/data/data/baremetal/masters/main.tf +++ b/data/data/baremetal/masters/main.tf @@ -1,54 +1,22 @@ -# FIXME: This Terraform HCL file defines the 3 master nodes. It uses the original ironic_nodes.json format, -# flattened because Terraform v0.11 does not support nested data structures. Maps may only be key/value -# pairs. We could use terraform's resource `count` provider to have just one resource declaration, but -# the data would have to be structured differently. - -resource "ironic_node_v1" "openshift-master-0" { - name = "${var.master_0["name"]}" +resource "ironic_node_v1" "openshift-master" { + count = "${length(keys(var.master_nodes))}" + name = "${lookup(var.master_nodes[format("openshift-master-%d", count.index)], "name")}" target_provision_state = "active" user_data = "${var.ignition}" - root_device = "${var.root_device_0}" - driver = "${var.master_0["driver"]}" - driver_info = "${var.driver_info_0}" - - ports = [ - { - address = "${var.master_0["port_address"]}" - pxe_enabled = "true" - }, - ] - - properties = "${var.properties_0}" - instance_info = { - image_source = "${var.image_source}" - image_checksum = "${var.image_checksum}" - root_gb = "${var.root_gb}" - } - - management_interface = "${var.master_0["management_interface"]}" - power_interface = "${var.master_0["power_interface"]}" - vendor_interface = "${var.master_0["vendor_interface"]}" -} - -resource "ironic_node_v1" "openshift-master-1" { - name = "${var.master_1["name"]}" - - target_provision_state = "active" - user_data = "${var.ignition}" - root_device = "${var.root_device_1}" - driver = "${var.master_1["driver"]}" - driver_info = "${var.driver_info_1}" + root_device = "${var.root_devices[format("openshift-master-%d", count.index)]}" + driver = "${lookup(var.master_nodes[format("openshift-master-%d", count.index)], "driver")}" + driver_info = "${var.driver_infos[format("openshift-master-%d", count.index)]}" ports = [ { - address = "${var.master_1["port_address"]}" + address = "${lookup(var.master_nodes[format("openshift-master-%d", count.index)], "port_address")}" pxe_enabled = "true" }, ] - properties = "${var.properties_1}" + properties = "${var.properties[format("openshift-master-%d", count.index)]}" instance_info = { image_source = "${var.image_source}" @@ -56,36 +24,8 @@ resource "ironic_node_v1" "openshift-master-1" { root_gb = "${var.root_gb}" } - management_interface = "${var.master_1["management_interface"]}" - power_interface = "${var.master_1["power_interface"]}" - vendor_interface = "${var.master_1["vendor_interface"]}" -} - -resource "ironic_node_v1" "openshift-master-2" { - name = "${var.master_2["name"]}" - - target_provision_state = "active" - user_data = "${var.ignition}" - root_device = "${var.root_device_2}" - driver = "${var.master_2["driver"]}" - driver_info = "${var.driver_info_2}" - - ports = [ - { - address = "${var.master_2["port_address"]}" - pxe_enabled = "true" - }, - ] - - properties = "${var.properties_2}" - - instance_info = { - image_source = "${var.image_source}" - image_checksum = "${var.image_checksum}" - root_gb = "${var.root_gb}" - } + management_interface = "${lookup(var.master_nodes[format("openshift-master-%d", count.index)], "management_interface")}" + power_interface = "${lookup(var.master_nodes[format("openshift-master-%d", count.index)], "power_interface")}" + vendor_interface = "${lookup(var.master_nodes[format("openshift-master-%d", count.index)], "vendor_interface")}" - management_interface = "${var.master_2["management_interface"]}" - power_interface = "${var.master_2["power_interface"]}" - vendor_interface = "${var.master_2["vendor_interface"]}" } diff --git a/data/data/baremetal/masters/variables.tf b/data/data/baremetal/masters/variables.tf index 833827743..5f6659edc 100644 --- a/data/data/baremetal/masters/variables.tf +++ b/data/data/baremetal/masters/variables.tf @@ -23,64 +23,23 @@ variable "ignition" { description = "The content of the master ignition file" } -variable "master_0" { +variable "master_nodes" { type = "map" - description = "Master 0 bare metal node details" + description = "Master bare metal node details" } -variable "properties_0" { +variable "properties" { type = "map" - description = "Master 0 bare metal properties" + description = "Master bare metal properties" } -variable "root_device_0" { +variable "root_devices" { type = "map" - description = "Master 0 root device configuration" + description = "Master root device configuration" } -variable "driver_info_0" { +variable "driver_infos" { type = "map" - description = "Master 0 driver info" + description = "Master driver info" } -variable "master_1" { - type = "map" - description = "Master 1 bare metal node details" -} - -variable "properties_1" { - type = "map" - description = "Master 1 bare metal properties" -} - -variable "root_device_1" { - type = "map" - description = "Master 1 root device configuration" -} - -variable "driver_info_1" { - type = "map" - description = "Master 1 driver info" -} - -variable "master_2" { - type = "map" - description = "Master 2 bare metal node details" -} - -variable "properties_2" { - type = "map" - description = "Master 2 bare metal properties" -} - -variable "root_device_2" { - type = "map" - description = "Master 2 root device configuration" -} - -variable "driver_info_2" { - type = "map" - description = "Master 2 driver info" -} - - diff --git a/data/data/baremetal/variables-baremetal.tf b/data/data/baremetal/variables-baremetal.tf index dec550c17..e023953cd 100644 --- a/data/data/baremetal/variables-baremetal.tf +++ b/data/data/baremetal/variables-baremetal.tf @@ -28,64 +28,22 @@ variable "master_configuration" { description = "Configuration information for masters such as image location" } -variable "master_0" { +variable "master_nodes" { type = "map" - description = "Master 0 bare metal node details" + description = "Master bare metal node details" } -variable "properties_0" { +variable "properties" { type = "map" - description = "Master 0 bare metal properties" + description = "Master bare metal properties" } -variable "root_device_0" { +variable "root_devices" { type = "map" - description = "Master 0 root device configuration" + description = "Master root device configurations" } -variable "driver_info_0" { +variable "driver_infos" { type = "map" - description = "Master 0 driver info" + description = "Master driver infos" } - -variable "master_1" { - type = "map" - description = "Master 1 bare metal node details" -} - -variable "properties_1" { - type = "map" - description = "Master 1 bare metal properties" -} - -variable "root_device_1" { - type = "map" - description = "Master 1 root device configuration" -} - -variable "driver_info_1" { - type = "map" - description = "Master 1 driver info" -} - -variable "master_2" { - type = "map" - description = "Master 2 bare metal node details" -} - -variable "properties_2" { - type = "map" - description = "Master 2 bare metal properties" -} - -variable "root_device_2" { - type = "map" - description = "Master 2 root device configuration" -} - -variable "driver_info_2" { - type = "map" - description = "Master 2 driver info" -} - - diff --git a/pkg/tfvars/baremetal/baremetal.go b/pkg/tfvars/baremetal/baremetal.go index 9421ddf95..9fc0d4976 100644 --- a/pkg/tfvars/baremetal/baremetal.go +++ b/pkg/tfvars/baremetal/baremetal.go @@ -2,42 +2,30 @@ package baremetal import ( - "encoding/json" - "github.com/openshift-metalkube/kni-installer/pkg/types/baremetal" - libvirttfvars "github.com/openshift-metalkube/kni-installer/pkg/tfvars/libvirt" "github.com/pkg/errors" + "github.com/rodaine/hclencoder" ) type config struct { - LibvirtURI string `json:"libvirt_uri,omitempty"` - IronicURI string `json:"ironic_uri,omitempty"` - Image string `json:"os_image,omitempty"` - BareMetalBridge string `json:"baremetal_bridge,omitempty"` - OverCloudBridge string `json:"overcloud_bridge,omitempty"` + LibvirtURI string `hcl:"libvirt_uri,omitempty"` + IronicURI string `hcl:"ironic_uri,omitempty"` + Image string `hcl:"os_image,omitempty"` + BareMetalBridge string `hcl:"baremetal_bridge,omitempty"` + OverCloudBridge string `hcl:"overcloud_bridge,omitempty"` // Data required for masters deployment - several maps per master, because of terraform's // limitation that maps cannot be strings - Master0 interface{} `json:"master_0"` - Properties0 interface{} `json:"properties_0"` - RootDevice0 interface{} `json:"root_device_0"` - DriverInfo0 interface{} `json:"driver_info_0"` - - Master1 interface{} `json:"master_1"` - Properties1 interface{} `json:"properties_1"` - RootDevice1 interface{} `json:"root_device_1"` - DriverInfo1 interface{} `json:"driver_info_1"` - - Master2 interface{} `json:"master_2"` - Properties2 interface{} `json:"properties_2"` - RootDevice2 interface{} `json:"root_device_2"` - DriverInfo2 interface{} `json:"driver_info_2"` + MasterNodes interface{} `hcl:"master_nodes"` + Properties interface{} `hcl:"properties"` + RootDevices interface{} `hcl:"root_devices"` + DriverInfos interface{} `hcl:"driver_infos"` - MasterConfiguration baremetal.MasterConfiguration `json:"master_configuration"` + MasterConfiguration map[string]interface{} `hcl:"master_configuration"` } // TFVars generates bare metal specific Terraform variables. -func TFVars(libvirtURI, ironicURI, osImage, baremetalBridge, overcloudBridge string, nodes map[string]interface{}, configuration baremetal.MasterConfiguration) ([]byte, error) { +func TFVars(libvirtURI, ironicURI, osImage, baremetalBridge, overcloudBridge string, nodes map[string]interface{}, configuration map[string]interface{}) ([]byte, error) { osImage, err := libvirttfvars.CachedImage(osImage) if err != nil { return nil, errors.Wrap(err, "failed to use cached libvirt image") @@ -49,20 +37,12 @@ func TFVars(libvirtURI, ironicURI, osImage, baremetalBridge, overcloudBridge str Image: osImage, BareMetalBridge: baremetalBridge, OverCloudBridge: overcloudBridge, - Master0: nodes["master_0"], - Properties0: nodes["properties_0"], - RootDevice0: nodes["root_device_0"], - DriverInfo0: nodes["driver_info_0"], - Master1: nodes["master_1"], - Properties1: nodes["properties_1"], - RootDevice1: nodes["root_device_1"], - DriverInfo1: nodes["driver_info_1"], - Master2: nodes["master_2"], - Properties2: nodes["properties_2"], - RootDevice2: nodes["root_device_2"], - DriverInfo2: nodes["driver_info_2"], + MasterNodes: nodes["master_nodes"], + Properties: nodes["properties"], + RootDevices: nodes["root_devices"], + DriverInfos: nodes["driver_infos"], MasterConfiguration: configuration, } - return json.MarshalIndent(cfg, "", " ") + return hclencoder.Encode(cfg) } diff --git a/pkg/types/baremetal/platform.go b/pkg/types/baremetal/platform.go index a5a76c4aa..bd60fa9e1 100644 --- a/pkg/types/baremetal/platform.go +++ b/pkg/types/baremetal/platform.go @@ -1,12 +1,5 @@ package baremetal -type MasterConfiguration struct { - ImageSource string `json:"image_source,omitempty"` - ImageChecksum string `json:"image_checksum,omitempty"` - RootGb string `json:"root_gb,omitempty"` - RootDisk string `json:"root_disk,omitempty"` -} - // Platform stores all the global configuration that all // machinesets use. type Platform struct { @@ -27,7 +20,7 @@ type Platform struct { // MasterConfiguration contains the information needed to provision // a master. - MasterConfiguration MasterConfiguration `json:"master_configuration"` + MasterConfiguration map[string]interface{} `json:"master_configuration"` // DefaultMachinePlatform is the default configuration used when // installing on bare metal for machine pools which do not define their own diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/LICENSE b/vendor/github.com/hashicorp/hcl/LICENSE similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/LICENSE rename to vendor/github.com/hashicorp/hcl/LICENSE diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/decoder.go b/vendor/github.com/hashicorp/hcl/decoder.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/decoder.go rename to vendor/github.com/hashicorp/hcl/decoder.go diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl.go b/vendor/github.com/hashicorp/hcl/hcl.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl.go rename to vendor/github.com/hashicorp/hcl/hcl.go diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/ast/ast.go b/vendor/github.com/hashicorp/hcl/hcl/ast/ast.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/ast/ast.go rename to vendor/github.com/hashicorp/hcl/hcl/ast/ast.go diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/ast/walk.go b/vendor/github.com/hashicorp/hcl/hcl/ast/walk.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/ast/walk.go rename to vendor/github.com/hashicorp/hcl/hcl/ast/walk.go diff --git a/vendor/github.com/hashicorp/hcl/hcl/fmtcmd/fmtcmd.go b/vendor/github.com/hashicorp/hcl/hcl/fmtcmd/fmtcmd.go new file mode 100644 index 000000000..2380d71e3 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/hcl/fmtcmd/fmtcmd.go @@ -0,0 +1,162 @@ +// Derivative work from: +// - https://golang.org/src/cmd/gofmt/gofmt.go +// - https://github.com/fatih/hclfmt + +package fmtcmd + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/hashicorp/hcl/hcl/printer" +) + +var ( + ErrWriteStdin = errors.New("cannot use write option with standard input") +) + +type Options struct { + List bool // list files whose formatting differs + Write bool // write result to (source) file instead of stdout + Diff bool // display diffs of formatting changes +} + +func isValidFile(f os.FileInfo, extensions []string) bool { + if !f.IsDir() && !strings.HasPrefix(f.Name(), ".") { + for _, ext := range extensions { + if strings.HasSuffix(f.Name(), "."+ext) { + return true + } + } + } + + return false +} + +// If in == nil, the source is the contents of the file with the given filename. +func processFile(filename string, in io.Reader, out io.Writer, stdin bool, opts Options) error { + if in == nil { + f, err := os.Open(filename) + if err != nil { + return err + } + defer f.Close() + in = f + } + + src, err := ioutil.ReadAll(in) + if err != nil { + return err + } + + res, err := printer.Format(src) + if err != nil { + return fmt.Errorf("In %s: %s", filename, err) + } + + if !bytes.Equal(src, res) { + // formatting has changed + if opts.List { + fmt.Fprintln(out, filename) + } + if opts.Write { + err = ioutil.WriteFile(filename, res, 0644) + if err != nil { + return err + } + } + if opts.Diff { + data, err := diff(src, res) + if err != nil { + return fmt.Errorf("computing diff: %s", err) + } + fmt.Fprintf(out, "diff a/%s b/%s\n", filename, filename) + out.Write(data) + } + } + + if !opts.List && !opts.Write && !opts.Diff { + _, err = out.Write(res) + } + + return err +} + +func walkDir(path string, extensions []string, stdout io.Writer, opts Options) error { + visitFile := func(path string, f os.FileInfo, err error) error { + if err == nil && isValidFile(f, extensions) { + err = processFile(path, nil, stdout, false, opts) + } + return err + } + + return filepath.Walk(path, visitFile) +} + +func Run( + paths, extensions []string, + stdin io.Reader, + stdout io.Writer, + opts Options, +) error { + if len(paths) == 0 { + if opts.Write { + return ErrWriteStdin + } + if err := processFile("", stdin, stdout, true, opts); err != nil { + return err + } + return nil + } + + for _, path := range paths { + switch dir, err := os.Stat(path); { + case err != nil: + return err + case dir.IsDir(): + if err := walkDir(path, extensions, stdout, opts); err != nil { + return err + } + default: + if err := processFile(path, nil, stdout, false, opts); err != nil { + return err + } + } + } + + return nil +} + +func diff(b1, b2 []byte) (data []byte, err error) { + f1, err := ioutil.TempFile("", "") + if err != nil { + return + } + defer os.Remove(f1.Name()) + defer f1.Close() + + f2, err := ioutil.TempFile("", "") + if err != nil { + return + } + defer os.Remove(f2.Name()) + defer f2.Close() + + f1.Write(b1) + f2.Write(b2) + + data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput() + if len(data) > 0 { + // diff exits with a non-zero status when the files don't match. + // Ignore that failure as long as we get output. + err = nil + } + return +} diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/parser/error.go b/vendor/github.com/hashicorp/hcl/hcl/parser/error.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/parser/error.go rename to vendor/github.com/hashicorp/hcl/hcl/parser/error.go diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/parser/parser.go b/vendor/github.com/hashicorp/hcl/hcl/parser/parser.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/parser/parser.go rename to vendor/github.com/hashicorp/hcl/hcl/parser/parser.go diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go b/vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go rename to vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/printer/printer.go b/vendor/github.com/hashicorp/hcl/hcl/printer/printer.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/printer/printer.go rename to vendor/github.com/hashicorp/hcl/hcl/printer/printer.go diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/scanner/scanner.go b/vendor/github.com/hashicorp/hcl/hcl/scanner/scanner.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/scanner/scanner.go rename to vendor/github.com/hashicorp/hcl/hcl/scanner/scanner.go diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/strconv/quote.go b/vendor/github.com/hashicorp/hcl/hcl/strconv/quote.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/strconv/quote.go rename to vendor/github.com/hashicorp/hcl/hcl/strconv/quote.go diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/token/position.go b/vendor/github.com/hashicorp/hcl/hcl/token/position.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/token/position.go rename to vendor/github.com/hashicorp/hcl/hcl/token/position.go diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/token/token.go b/vendor/github.com/hashicorp/hcl/hcl/token/token.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/hcl/token/token.go rename to vendor/github.com/hashicorp/hcl/hcl/token/token.go diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/json/parser/flatten.go b/vendor/github.com/hashicorp/hcl/json/parser/flatten.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/json/parser/flatten.go rename to vendor/github.com/hashicorp/hcl/json/parser/flatten.go diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/json/parser/parser.go b/vendor/github.com/hashicorp/hcl/json/parser/parser.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/json/parser/parser.go rename to vendor/github.com/hashicorp/hcl/json/parser/parser.go diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/json/scanner/scanner.go b/vendor/github.com/hashicorp/hcl/json/scanner/scanner.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/json/scanner/scanner.go rename to vendor/github.com/hashicorp/hcl/json/scanner/scanner.go diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/json/token/position.go b/vendor/github.com/hashicorp/hcl/json/token/position.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/json/token/position.go rename to vendor/github.com/hashicorp/hcl/json/token/position.go diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/json/token/token.go b/vendor/github.com/hashicorp/hcl/json/token/token.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/json/token/token.go rename to vendor/github.com/hashicorp/hcl/json/token/token.go diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/lex.go b/vendor/github.com/hashicorp/hcl/lex.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/lex.go rename to vendor/github.com/hashicorp/hcl/lex.go diff --git a/pkg/terraform/exec/vendor/github.com/hashicorp/hcl/parse.go b/vendor/github.com/hashicorp/hcl/parse.go similarity index 100% rename from pkg/terraform/exec/vendor/github.com/hashicorp/hcl/parse.go rename to vendor/github.com/hashicorp/hcl/parse.go diff --git a/vendor/github.com/rodaine/hclencoder/LICENSE b/vendor/github.com/rodaine/hclencoder/LICENSE new file mode 100644 index 000000000..c6d49552f --- /dev/null +++ b/vendor/github.com/rodaine/hclencoder/LICENSE @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2016 Chris Roche + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/rodaine/hclencoder/hclencoder.go b/vendor/github.com/rodaine/hclencoder/hclencoder.go new file mode 100644 index 000000000..3a5d58932 --- /dev/null +++ b/vendor/github.com/rodaine/hclencoder/hclencoder.go @@ -0,0 +1,35 @@ +package hclencoder + +import ( + "bytes" + "reflect" + + "github.com/hashicorp/hcl/hcl/ast" + "github.com/hashicorp/hcl/hcl/printer" +) + +// Encode converts any supported type into the corresponding HCL format +func Encode(in interface{}) ([]byte, error) { + node, _, err := encode(reflect.ValueOf(in)) + if err != nil { + return nil, err + } + + file := &ast.File{} + switch node := node.(type) { + case *ast.ObjectType: + file.Node = node.List + default: + file.Node = node + } + + if _, err = positionNodes(file, startingCursor, 2); err != nil { + return nil, err + } + + b := &bytes.Buffer{} + err = printer.Fprint(b, file) + b.WriteString("\n") + + return b.Bytes(), err +} diff --git a/vendor/github.com/rodaine/hclencoder/nodes.go b/vendor/github.com/rodaine/hclencoder/nodes.go new file mode 100644 index 000000000..47a3aee40 --- /dev/null +++ b/vendor/github.com/rodaine/hclencoder/nodes.go @@ -0,0 +1,428 @@ +package hclencoder + +import ( + "errors" + "fmt" + "reflect" + "sort" + "strconv" + "strings" + + "github.com/hashicorp/hcl/hcl/ast" + "github.com/hashicorp/hcl/hcl/token" +) + +const ( + // HCLTagName is the struct field tag used by the HCL decoder. The + // values from this tag are used in the same way as the decoder. + HCLTagName = "hcl" + + // KeyTag indicates that the value of the field should be part of + // the parent object block's key, not a property of that block + KeyTag string = "key" + + // SquashTag is attached to anonymous fields of a struct and indicates + // to the encoder to lift the fields of that value into the parent + // block's scope transparently. Otherwise, the field's type is used as + // the key for the value. + SquashTag string = "squash" + + // UnusedKeysTag is a flag that indicates any unused keys found by the + // decoder are stored in this field of type []string. This has the same + // behavior as the OmitTag and is not encoded. + UnusedKeysTag string = "unusedKeys" + + // DecodedFieldsTag is a flag that indicates all fields decoded are + // stored in this field of type []string. This has the same behavior as + // the OmitTag and is not encoded. + DecodedFieldsTag string = "decodedFields" + + // HCLETagName is the struct field tag used by this package. The + // values from this tag are used in conjunction with HCLTag values. + HCLETagName = "hcle" + + // OmitTag will omit this field from encoding. This is the similar + // behavior to `json:"-"`. + OmitTag string = "omit" + + // OmitEmptyTag will omit this field if it is a zero value. This + // is similar behavior to `json:",omitempty"` + OmitEmptyTag string = "omitempty" +) + +type fieldMeta struct { + anonymous bool + name string + key bool + squash bool + unusedKeys bool + decodedFields bool + omit bool + omitEmpty bool +} + +// encode converts a reflected valued into an HCL ast.Node in a depth-first manner. +func encode(in reflect.Value) (node ast.Node, key []*ast.ObjectKey, err error) { + in, isNil := deref(in) + if isNil { + return nil, nil, nil + } + + switch in.Kind() { + + case reflect.Bool, reflect.Float64, reflect.String, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return encodePrimitive(in) + + case reflect.Slice: + return encodeList(in) + + case reflect.Map: + return encodeMap(in) + + case reflect.Struct: + return encodeStruct(in) + + default: + return nil, nil, fmt.Errorf("cannot encode kind %s to HCL", in.Kind()) + } + +} + +// encodePrimitive converts a primitive value into an ast.LiteralType. An +// ast.ObjectKey is never returned. +func encodePrimitive(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) { + tkn, err := tokenize(in, false) + if err != nil { + return nil, nil, err + } + + return &ast.LiteralType{Token: tkn}, nil, nil +} + +// encodeList converts a slice to an appropriate ast.Node type depending on its +// element value type. An ast.ObjectKey is never returned. +func encodeList(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) { + childType := in.Type().Elem() + +childLoop: + for { + switch childType.Kind() { + case reflect.Ptr: + childType = childType.Elem() + default: + break childLoop + } + } + + switch childType.Kind() { + case reflect.Map, reflect.Struct, reflect.Interface: + return encodeBlockList(in) + default: + return encodePrimitiveList(in) + } +} + +// encodePrimitiveList converts a slice of primitive values to an ast.ListType. An +// ast.ObjectKey is never returned. +func encodePrimitiveList(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) { + l := in.Len() + n := &ast.ListType{List: make([]ast.Node, 0, l)} + + for i := 0; i < l; i++ { + child, _, err := encode(in.Index(i)) + if err != nil { + return nil, nil, err + } + if child != nil { + n.Add(child) + } + } + + return n, nil, nil +} + +// encodeBlockList converts a slice of non-primitive types to an ast.ObjectList. An +// ast.ObjectKey is never returned. +func encodeBlockList(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) { + l := in.Len() + n := &ast.ObjectList{Items: make([]*ast.ObjectItem, 0, l)} + + for i := 0; i < l; i++ { + child, childKey, err := encode(in.Index(i)) + if err != nil { + return nil, nil, err + } + if child == nil { + continue + } + if childKey == nil { + return encodePrimitiveList(in) + } + + item := &ast.ObjectItem{Val: child} + item.Keys = childKey + n.Add(item) + } + + return n, nil, nil +} + +// encodeMap converts a map type into an ast.ObjectType. Maps must have string +// key values to be encoded. An ast.ObjectKey is never returned. +func encodeMap(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) { + if keyType := in.Type().Key().Kind(); keyType != reflect.String { + return nil, nil, fmt.Errorf("map keys must be strings, %s given", keyType) + } + + l := make(objectItems, 0, in.Len()) + for _, key := range in.MapKeys() { + tkn, _ := tokenize(key, true) // error impossible since we've already checked key kind + + val, childKey, err := encode(in.MapIndex(key)) + if err != nil { + return nil, nil, err + } + if val == nil { + continue + } + + switch typ := val.(type) { + case *ast.ObjectList: + // If the item is an object list, we need to flatten out the items. + // Child keys are assumed to be added to the above call to encode + itemKey := &ast.ObjectKey{Token: tkn} + for _, obj := range typ.Items { + keys := append([]*ast.ObjectKey{itemKey}, obj.Keys...) + l = append(l, &ast.ObjectItem{ + Keys: keys, + Val: obj.Val, + }) + } + + default: + item := &ast.ObjectItem{ + Keys: []*ast.ObjectKey{{Token: tkn}}, + Val: val, + } + if childKey != nil { + item.Keys = append(item.Keys, childKey...) + } + l = append(l, item) + + } + + } + + sort.Sort(l) + return &ast.ObjectType{List: &ast.ObjectList{Items: []*ast.ObjectItem(l)}}, nil, nil +} + +// encodeStruct converts a struct type into an ast.ObjectType. An ast.ObjectKey +// may be returned if a KeyTag is present that should be used by a parent +// ast.ObjectItem if this node is nested. +func encodeStruct(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) { + l := in.NumField() + list := &ast.ObjectList{Items: make([]*ast.ObjectItem, 0, l)} + keys := make([]*ast.ObjectKey, 0) + + for i := 0; i < l; i++ { + field := in.Type().Field(i) + meta := extractFieldMeta(field) + + // these tags are used for debugging the decoder + // they should not be output + if meta.unusedKeys || meta.decodedFields || meta.omit { + continue + } + + tkn, _ := tokenize(reflect.ValueOf(meta.name), true) // impossible to not be string + + // if the OmitEmptyTag is provided, check if the value is its zero value. + rawVal := in.Field(i) + if meta.omitEmpty { + zeroVal := reflect.Zero(rawVal.Type()).Interface() + if reflect.DeepEqual(rawVal.Interface(), zeroVal) { + continue + } + } + + val, childKeys, err := encode(rawVal) + if err != nil { + return nil, nil, err + } + if val == nil { + continue + } + + // this field is a key and should be bubbled up to the parent node + if meta.key { + if lit, ok := val.(*ast.LiteralType); ok && lit.Token.Type == token.STRING { + keys = append(keys, &ast.ObjectKey{Token: lit.Token}) + continue + } + return nil, nil, errors.New("struct key fields must be string literals") + } + + // this field is anonymous and should be squashed into the parent struct's fields + if meta.anonymous && meta.squash { + switch val := val.(type) { + case *ast.ObjectType: + list.Items = append(list.Items, val.List.Items...) + if childKeys != nil { + keys = childKeys + } + continue + } + } + + itemKey := &ast.ObjectKey{Token: tkn} + + // if the item is an object list, we need to flatten out the items + if objectList, ok := val.(*ast.ObjectList); ok { + for _, obj := range objectList.Items { + objectKeys := append([]*ast.ObjectKey{itemKey}, obj.Keys...) + list.Add(&ast.ObjectItem{ + Keys: objectKeys, + Val: obj.Val, + }) + } + continue + } + + item := &ast.ObjectItem{ + Keys: []*ast.ObjectKey{itemKey}, + Val: val, + } + if childKeys != nil { + item.Keys = append(item.Keys, childKeys...) + } + list.Add(item) + } + if len(keys) == 0 { + return &ast.ObjectType{List: list}, nil, nil + } + return &ast.ObjectType{List: list}, keys, nil +} + +// tokenize converts a primitive type into an token.Token. IDENT tokens (unquoted strings) +// can be optionally triggered for any string types. +func tokenize(in reflect.Value, ident bool) (t token.Token, err error) { + switch in.Kind() { + case reflect.Bool: + return token.Token{ + Type: token.BOOL, + Text: strconv.FormatBool(in.Bool()), + }, nil + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return token.Token{ + Type: token.NUMBER, + Text: fmt.Sprintf("%d", in.Uint()), + }, nil + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return token.Token{ + Type: token.NUMBER, + Text: fmt.Sprintf("%d", in.Int()), + }, nil + + case reflect.Float64: + return token.Token{ + Type: token.FLOAT, + Text: strconv.FormatFloat(in.Float(), 'g', -1, 64), + }, nil + + case reflect.String: + if ident { + return token.Token{ + Type: token.IDENT, + Text: in.String(), + }, nil + } + return token.Token{ + Type: token.STRING, + Text: fmt.Sprintf(`"%s"`, in.String()), + }, nil + } + + return t, fmt.Errorf("cannot encode primitive kind %s to token", in.Kind()) +} + +// extractFieldMeta pulls information about struct fields and the optional HCL tags +func extractFieldMeta(f reflect.StructField) (meta fieldMeta) { + if f.Anonymous { + meta.anonymous = true + meta.name = f.Type.Name() + } else { + meta.name = f.Name + } + + tags := strings.Split(f.Tag.Get(HCLTagName), ",") + if len(tags) > 0 { + if tags[0] != "" { + meta.name = tags[0] + } + + for _, tag := range tags[1:] { + switch tag { + case KeyTag: + meta.key = true + case SquashTag: + meta.squash = true + case DecodedFieldsTag: + meta.decodedFields = true + case UnusedKeysTag: + meta.unusedKeys = true + } + } + } + + tags = strings.Split(f.Tag.Get(HCLETagName), ",") + for _, tag := range tags { + switch tag { + case OmitTag: + meta.omit = true + case OmitEmptyTag: + meta.omitEmpty = true + } + } + + return +} + +// deref safely dereferences interface and pointer values to their underlying value types. +// It also detects if that value is invalid or nil. +func deref(in reflect.Value) (val reflect.Value, isNil bool) { + switch in.Kind() { + case reflect.Invalid: + return in, true + case reflect.Interface, reflect.Ptr: + if in.IsNil() { + return in, true + } + // recurse for the elusive double pointer + return deref(in.Elem()) + case reflect.Slice, reflect.Map: + return in, in.IsNil() + default: + return in, false + } +} + +type objectItems []*ast.ObjectItem + +func (ol objectItems) Len() int { return len(ol) } +func (ol objectItems) Swap(i, j int) { ol[i], ol[j] = ol[j], ol[i] } +func (ol objectItems) Less(i, j int) bool { + iKeys := ol[i].Keys + jKeys := ol[j].Keys + for k := 0; k < len(iKeys) && k < len(jKeys); k++ { + if iKeys[k].Token.Text == jKeys[k].Token.Text { + continue + } + return iKeys[k].Token.Text < jKeys[k].Token.Text + } + return len(iKeys) <= len(jKeys) +} diff --git a/vendor/github.com/rodaine/hclencoder/walker.go b/vendor/github.com/rodaine/hclencoder/walker.go new file mode 100644 index 000000000..1a28f8b09 --- /dev/null +++ b/vendor/github.com/rodaine/hclencoder/walker.go @@ -0,0 +1,108 @@ +package hclencoder + +import ( + "fmt" + "reflect" + "unicode/utf8" + + "github.com/hashicorp/hcl/hcl/ast" + "github.com/hashicorp/hcl/hcl/token" +) + +type cursor token.Pos + +func (c cursor) pos() token.Pos { + return token.Pos(c) +} + +func (c cursor) crlf() cursor { + c.Line++ + c.Column = 1 + return c +} + +func (c cursor) in(step int) cursor { + c.Offset += step + return c +} + +func (c cursor) out(step int) cursor { + c.Offset -= step + return c +} + +var startingCursor = cursor{ + Offset: 0, + Line: 1, + Column: 1, +} + +func positionNodes(node ast.Node, cur cursor, step int) (cursor, error) { + var err error + + switch node := node.(type) { + case *ast.LiteralType: + node.Token.Pos = cur.pos() + cur.Column += utf8.RuneCountInString(node.Token.Text) + return cur, nil + + case *ast.ListType: + node.Lbrack = cur.pos() + if len(node.List) > 1 { + cur = cur.crlf().in(step) + } + for _, item := range node.List { + if cur, err = positionNodes(item, cur, step); err != nil { + return cur, err + } + if len(node.List) > 1 { + cur = cur.crlf() + } + } + cur = cur.out(step) + node.Rbrack = cur.pos() + cur.Column++ + return cur, nil + + case *ast.ObjectItem: + for _, key := range node.Keys { + key.Token.Pos = cur.pos() + cur.Column += 1 + utf8.RuneCountInString(node.Keys[0].Token.Text) + } + + if _, ok := node.Val.(*ast.ObjectType); !ok { + node.Assign = cur.pos() + } + cur.Column += 2 + + return positionNodes(node.Val, cur, step) + + case *ast.ObjectList: + for _, item := range node.Items { + cur, err = positionNodes(item, cur, step) + if err != nil { + return cur, err + } + cur = cur.crlf() + } + return cur, nil + + case *ast.ObjectType: + node.Lbrace = cur.pos() + cur = cur.crlf().in(step) + + if cur, err = positionNodes(node.List, cur, step); err != nil { + return cur, err + } + cur = cur.out(step) + node.Rbrace = cur.pos() + cur.Column++ + return cur, nil + + case *ast.File: + return positionNodes(node.Node, cur, step) + + default: + return cur, fmt.Errorf("unknown node kind %s", reflect.ValueOf(node).Kind()) + } +}