Merge pull request #8878 from rifelpet/hcl2-gce

Fix Terraform 0.12 support for GCE
This commit is contained in:
Kubernetes Prow Robot 2020-04-08 21:27:44 -07:00 committed by GitHub
commit 2ab04e0ab5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 369 additions and 79 deletions

View File

@ -41,9 +41,7 @@ done 3< <(find "${KOPS_ROOT}/tests/integration/update_cluster" -type d -maxdepth
if [ $RC != 0 ]; then
echo -e "\nTerraform validation failed\n"
# TODO(rifelpet): make this script blocking in PRs by exiting non-zero on failure
# exit $RC
exit 0
exit $RC
else
echo -e "\nTerraform validation succeeded\n"
fi

View File

@ -332,19 +332,163 @@ resource "google_compute_instance_group_manager" "c-nodes-ha-gce-example-com" {
}
resource "google_compute_instance_template" "master-us-test1-a-ha-gce-example-com" {
can_ip_forward = true
disk {
auto_delete = true
boot = true
device_name = "persistent-disks-0"
disk_name = ""
disk_size_gb = 64
disk_type = "pd-standard"
interface = ""
mode = "READ_WRITE"
source = ""
source_image = "https://www.googleapis.com/compute/v1/projects/cos-cloud/global/images/cos-stable-57-9202-64-0"
type = "PERSISTENT"
}
machine_type = "n1-standard-1"
metadata = {
"cluster-name" = file("${path.module}/data/google_compute_instance_template_master-us-test1-a-ha-gce-example-com_metadata_cluster-name")
"kops-k8s-io-instance-group-name" = file("${path.module}/data/google_compute_instance_template_master-us-test1-a-ha-gce-example-com_metadata_kops-k8s-io-instance-group-name")
"ssh-keys" = file("${path.module}/data/google_compute_instance_template_master-us-test1-a-ha-gce-example-com_metadata_ssh-keys")
"startup-script" = file("${path.module}/data/google_compute_instance_template_master-us-test1-a-ha-gce-example-com_metadata_startup-script")
}
name_prefix = "master-us-test1-a-ha-gce--ke5ah6-"
network_interface {
access_config {
}
network = google_compute_network.default.name
}
scheduling {
automatic_restart = true
on_host_maintenance = "MIGRATE"
preemptible = false
}
service_account {
email = "default"
scopes = ["https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/monitoring", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/devstorage.read_write", "https://www.googleapis.com/auth/ndev.clouddns.readwrite"]
}
tags = ["ha-gce-example-com-k8s-io-role-master"]
}
resource "google_compute_instance_template" "master-us-test1-b-ha-gce-example-com" {
can_ip_forward = true
disk {
auto_delete = true
boot = true
device_name = "persistent-disks-0"
disk_name = ""
disk_size_gb = 64
disk_type = "pd-standard"
interface = ""
mode = "READ_WRITE"
source = ""
source_image = "https://www.googleapis.com/compute/v1/projects/cos-cloud/global/images/cos-stable-57-9202-64-0"
type = "PERSISTENT"
}
machine_type = "n1-standard-1"
metadata = {
"cluster-name" = file("${path.module}/data/google_compute_instance_template_master-us-test1-b-ha-gce-example-com_metadata_cluster-name")
"kops-k8s-io-instance-group-name" = file("${path.module}/data/google_compute_instance_template_master-us-test1-b-ha-gce-example-com_metadata_kops-k8s-io-instance-group-name")
"ssh-keys" = file("${path.module}/data/google_compute_instance_template_master-us-test1-b-ha-gce-example-com_metadata_ssh-keys")
"startup-script" = file("${path.module}/data/google_compute_instance_template_master-us-test1-b-ha-gce-example-com_metadata_startup-script")
}
name_prefix = "master-us-test1-b-ha-gce--c8u7qq-"
network_interface {
access_config {
}
network = google_compute_network.default.name
}
scheduling {
automatic_restart = true
on_host_maintenance = "MIGRATE"
preemptible = false
}
service_account {
email = "default"
scopes = ["https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/monitoring", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/devstorage.read_write", "https://www.googleapis.com/auth/ndev.clouddns.readwrite"]
}
tags = ["ha-gce-example-com-k8s-io-role-master"]
}
resource "google_compute_instance_template" "master-us-test1-c-ha-gce-example-com" {
can_ip_forward = true
disk {
auto_delete = true
boot = true
device_name = "persistent-disks-0"
disk_name = ""
disk_size_gb = 64
disk_type = "pd-standard"
interface = ""
mode = "READ_WRITE"
source = ""
source_image = "https://www.googleapis.com/compute/v1/projects/cos-cloud/global/images/cos-stable-57-9202-64-0"
type = "PERSISTENT"
}
machine_type = "n1-standard-1"
metadata = {
"cluster-name" = file("${path.module}/data/google_compute_instance_template_master-us-test1-c-ha-gce-example-com_metadata_cluster-name")
"kops-k8s-io-instance-group-name" = file("${path.module}/data/google_compute_instance_template_master-us-test1-c-ha-gce-example-com_metadata_kops-k8s-io-instance-group-name")
"ssh-keys" = file("${path.module}/data/google_compute_instance_template_master-us-test1-c-ha-gce-example-com_metadata_ssh-keys")
"startup-script" = file("${path.module}/data/google_compute_instance_template_master-us-test1-c-ha-gce-example-com_metadata_startup-script")
}
name_prefix = "master-us-test1-c-ha-gce--3unp7l-"
network_interface {
access_config {
}
network = google_compute_network.default.name
}
scheduling {
automatic_restart = true
on_host_maintenance = "MIGRATE"
preemptible = false
}
service_account {
email = "default"
scopes = ["https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/monitoring", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/devstorage.read_write", "https://www.googleapis.com/auth/ndev.clouddns.readwrite"]
}
tags = ["ha-gce-example-com-k8s-io-role-master"]
}
resource "google_compute_instance_template" "nodes-ha-gce-example-com" {
can_ip_forward = true
disk {
auto_delete = true
boot = true
device_name = "persistent-disks-0"
disk_name = ""
disk_size_gb = 128
disk_type = "pd-standard"
interface = ""
mode = "READ_WRITE"
source = ""
source_image = "https://www.googleapis.com/compute/v1/projects/cos-cloud/global/images/cos-stable-57-9202-64-0"
type = "PERSISTENT"
}
machine_type = "n1-standard-2"
metadata = {
"cluster-name" = file("${path.module}/data/google_compute_instance_template_nodes-ha-gce-example-com_metadata_cluster-name")
"kops-k8s-io-instance-group-name" = file("${path.module}/data/google_compute_instance_template_nodes-ha-gce-example-com_metadata_kops-k8s-io-instance-group-name")
"ssh-keys" = file("${path.module}/data/google_compute_instance_template_nodes-ha-gce-example-com_metadata_ssh-keys")
"startup-script" = file("${path.module}/data/google_compute_instance_template_nodes-ha-gce-example-com_metadata_startup-script")
}
name_prefix = "nodes-ha-gce-example-com-"
network_interface {
access_config {
}
network = google_compute_network.default.name
}
scheduling {
automatic_restart = true
on_host_maintenance = "MIGRATE"
preemptible = false
}
service_account {
email = "default"
scopes = ["https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/monitoring", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/devstorage.read_only"]
}
tags = ["ha-gce-example-com-k8s-io-role-node"]
}
resource "google_compute_network" "default" {

View File

@ -244,11 +244,83 @@ resource "google_compute_instance_group_manager" "a-nodes-minimal-gce-example-co
}
resource "google_compute_instance_template" "master-us-test1-a-minimal-gce-example-com" {
can_ip_forward = true
disk {
auto_delete = true
boot = true
device_name = "persistent-disks-0"
disk_name = ""
disk_size_gb = 64
disk_type = "pd-standard"
interface = ""
mode = "READ_WRITE"
source = ""
source_image = "https://www.googleapis.com/compute/v1/projects/cos-cloud/global/images/cos-stable-57-9202-64-0"
type = "PERSISTENT"
}
machine_type = "n1-standard-1"
metadata = {
"cluster-name" = file("${path.module}/data/google_compute_instance_template_master-us-test1-a-minimal-gce-example-com_metadata_cluster-name")
"kops-k8s-io-instance-group-name" = file("${path.module}/data/google_compute_instance_template_master-us-test1-a-minimal-gce-example-com_metadata_kops-k8s-io-instance-group-name")
"ssh-keys" = file("${path.module}/data/google_compute_instance_template_master-us-test1-a-minimal-gce-example-com_metadata_ssh-keys")
"startup-script" = file("${path.module}/data/google_compute_instance_template_master-us-test1-a-minimal-gce-example-com_metadata_startup-script")
}
name_prefix = "master-us-test1-a-minimal-do16cp-"
network_interface {
access_config {
}
network = google_compute_network.default.name
}
scheduling {
automatic_restart = true
on_host_maintenance = "MIGRATE"
preemptible = false
}
service_account {
email = "default"
scopes = ["https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/monitoring", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/devstorage.read_write", "https://www.googleapis.com/auth/ndev.clouddns.readwrite"]
}
tags = ["minimal-gce-example-com-k8s-io-role-master"]
}
resource "google_compute_instance_template" "nodes-minimal-gce-example-com" {
can_ip_forward = true
disk {
auto_delete = true
boot = true
device_name = "persistent-disks-0"
disk_name = ""
disk_size_gb = 128
disk_type = "pd-standard"
interface = ""
mode = "READ_WRITE"
source = ""
source_image = "https://www.googleapis.com/compute/v1/projects/cos-cloud/global/images/cos-stable-57-9202-64-0"
type = "PERSISTENT"
}
machine_type = "n1-standard-2"
metadata = {
"cluster-name" = file("${path.module}/data/google_compute_instance_template_nodes-minimal-gce-example-com_metadata_cluster-name")
"kops-k8s-io-instance-group-name" = file("${path.module}/data/google_compute_instance_template_nodes-minimal-gce-example-com_metadata_kops-k8s-io-instance-group-name")
"ssh-keys" = file("${path.module}/data/google_compute_instance_template_nodes-minimal-gce-example-com_metadata_ssh-keys")
"startup-script" = file("${path.module}/data/google_compute_instance_template_nodes-minimal-gce-example-com_metadata_startup-script")
}
name_prefix = "nodes-minimal-gce-example-com-"
network_interface {
access_config {
}
network = google_compute_network.default.name
}
scheduling {
automatic_restart = true
on_host_maintenance = "MIGRATE"
preemptible = false
}
service_account {
email = "default"
scopes = ["https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/monitoring", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/devstorage.read_only"]
}
tags = ["minimal-gce-example-com-k8s-io-role-node"]
}
resource "google_compute_network" "default" {

View File

@ -394,9 +394,29 @@ func ShortenImageURL(defaultProject string, imageURL string) (string, error) {
}
type terraformInstance struct {
terraformInstanceCommon
Name string `json:"name" cty:"name"`
CanIPForward bool `json:"can_ip_forward" cty:"can_ip_forward"`
MachineType string `json:"machine_type,omitempty" cty:"machine_type"`
ServiceAccount *terraformServiceAccount `json:"service_account,omitempty" cty:"service_account"`
Scheduling *terraformScheduling `json:"scheduling,omitempty" cty:"scheduling"`
Disks []*terraformInstanceAttachedDisk `json:"disk,omitempty" cty:"disk"`
NetworkInterfaces []*terraformNetworkInterface `json:"network_interface,omitempty" cty:"network_interface"`
Metadata map[string]*terraform.Literal `json:"metadata,omitempty" cty:"metadata"`
MetadataStartupScript *terraform.Literal `json:"metadata_startup_script,omitempty" cty:"metadata_startup_script"`
Tags []string `json:"tags,omitempty" cty:"tags"`
Zone string `json:"zone,omitempty" cty:"zone"`
}
Name string `json:"name" cty:"name"`
type terraformInstanceAttachedDisk struct {
AutoDelete bool `json:"auto_delete,omitempty" cty:"auto_delete"`
DeviceName string `json:"device_name,omitempty" cty:"device_name"`
// 'pd-standard', 'pd-ssd', 'local-ssd' etc
Type string `json:"type,omitempty" cty:"type"`
Disk string `json:"disk,omitempty" cty:"disk"`
Image string `json:"image,omitempty" cty:"image"`
Scratch bool `json:"scratch,omitempty" cty:"scratch"`
Size int64 `json:"size,omitempty" cty:"size"`
}
func (_ *Instance) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *Instance) error {
@ -426,10 +446,10 @@ func (_ *Instance) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *
tf.Zone = *e.Zone
}
tf.AddServiceAccounts(i.ServiceAccounts)
tf.ServiceAccount = addServiceAccounts(i.ServiceAccounts)
for _, d := range i.Disks {
tfd := &terraformAttachedDisk{
tfd := &terraformInstanceAttachedDisk{
AutoDelete: d.AutoDelete,
Scratch: d.Type == "SCRATCH",
DeviceName: d.DeviceName,
@ -446,9 +466,13 @@ func (_ *Instance) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *
tf.Disks = append(tf.Disks, tfd)
}
tf.AddNetworks(e.Network, e.Subnet, i.NetworkInterfaces)
tf.NetworkInterfaces = addNetworks(e.Network, e.Subnet, i.NetworkInterfaces)
tf.AddMetadata(t, i.Name, i.Metadata)
metadata, err := addMetadata(t, i.Name, i.Metadata)
if err != nil {
return err
}
tf.Metadata = metadata
// Using metadata_startup_script is now mandatory (?)
{

View File

@ -417,23 +417,16 @@ func (_ *InstanceTemplate) RenderGCE(t *gce.GCEAPITarget, a, e, changes *Instanc
}
type terraformInstanceTemplate struct {
terraformInstanceCommon
NamePrefix string `json:"name_prefix" cty:"name_prefix"`
}
type terraformInstanceCommon struct {
CanIPForward bool `json:"can_ip_forward" cty:"can_ip_forward"`
MachineType string `json:"machine_type,omitempty" cty:"machine_type"`
ServiceAccount *terraformServiceAccount `json:"service_account,omitempty" cty:"service_account"`
Scheduling *terraformScheduling `json:"scheduling,omitempty" cty:"scheduling"`
Disks []*terraformAttachedDisk `json:"disk,omitempty" cty:"disk"`
NetworkInterfaces []*terraformNetworkInterface `json:"network_interface,omitempty" cty:"network_interface"`
Metadata map[string]*terraform.Literal `json:"metadata,omitempty" cty:"metadata"`
MetadataStartupScript *terraform.Literal `json:"metadata_startup_script,omitempty" cty:"metadata_startup_script"`
Tags []string `json:"tags,omitempty" cty:"tags"`
// Only for instances:
Zone string `json:"zone,omitempty" cty:"zone"`
NamePrefix string `json:"name_prefix" cty:"name_prefix"`
CanIPForward bool `json:"can_ip_forward" cty:"can_ip_forward"`
MachineType string `json:"machine_type,omitempty" cty:"machine_type"`
ServiceAccount *terraformServiceAccount `json:"service_account,omitempty" cty:"service_account"`
Scheduling *terraformScheduling `json:"scheduling,omitempty" cty:"scheduling"`
Disks []*terraformInstanceTemplateAttachedDisk `json:"disk,omitempty" cty:"disk"`
NetworkInterfaces []*terraformNetworkInterface `json:"network_interface,omitempty" cty:"network_interface"`
Metadata map[string]*terraform.Literal `json:"metadata,omitempty" cty:"metadata"`
MetadataStartupScript *terraform.Literal `json:"metadata_startup_script,omitempty" cty:"metadata_startup_script"`
Tags []string `json:"tags,omitempty" cty:"tags"`
}
type terraformServiceAccount struct {
@ -447,17 +440,12 @@ type terraformScheduling struct {
Preemptible bool `json:"preemptible" cty:"preemptible"`
}
type terraformAttachedDisk struct {
// These values are common
type terraformInstanceTemplateAttachedDisk struct {
AutoDelete bool `json:"auto_delete,omitempty" cty:"auto_delete"`
DeviceName string `json:"device_name,omitempty" cty:"device_name"`
// DANGER - common but different meaning:
// for an instance template this is scratch vs persistent
// for an instance this is 'pd-standard', 'pd-ssd', 'local-ssd' etc
Type string `json:"type,omitempty" cty:"type"`
// These values are only for instance templates:
// scratch vs persistent
Type string `json:"type,omitempty" cty:"type"`
Boot bool `json:"boot,omitempty" cty:"boot"`
DiskName string `json:"disk_name,omitempty" cty:"disk_name"`
SourceImage string `json:"source_image,omitempty" cty:"source_image"`
@ -466,12 +454,6 @@ type terraformAttachedDisk struct {
Mode string `json:"mode,omitempty" cty:"mode"`
DiskType string `json:"disk_type,omitempty" cty:"disk_type"`
DiskSizeGB int64 `json:"disk_size_gb,omitempty" cty:"disk_size_gb"`
// These values are only for instances:
Disk string `json:"disk,omitempty" cty:"disk"`
Image string `json:"image,omitempty" cty:"image"`
Scratch bool `json:"scratch,omitempty" cty:"scratch"`
Size int64 `json:"size,omitempty" cty:"size"`
}
type terraformNetworkInterface struct {
@ -484,8 +466,9 @@ type terraformAccessConfig struct {
NatIP *terraform.Literal `json:"nat_ip,omitempty" cty:"nat_ip"`
}
func (t *terraformInstanceCommon) AddNetworks(network *Network, subnet *Subnet, networkInterfacs []*compute.NetworkInterface) {
for _, g := range networkInterfacs {
func addNetworks(network *Network, subnet *Subnet, networkInterfaces []*compute.NetworkInterface) []*terraformNetworkInterface {
ni := make([]*terraformNetworkInterface, 0)
for _, g := range networkInterfaces {
tf := &terraformNetworkInterface{}
if network != nil {
tf.Network = network.TerraformName()
@ -505,46 +488,44 @@ func (t *terraformInstanceCommon) AddNetworks(network *Network, subnet *Subnet,
tf.AccessConfig = append(tf.AccessConfig, tac)
}
t.NetworkInterfaces = append(t.NetworkInterfaces, tf)
ni = append(ni, tf)
}
return ni
}
func (t *terraformInstanceCommon) AddMetadata(target *terraform.TerraformTarget, name string, metadata *compute.Metadata) error {
if metadata != nil {
if t.Metadata == nil {
t.Metadata = make(map[string]*terraform.Literal)
}
for _, g := range metadata.Items {
v := fi.NewStringResource(fi.StringValue(g.Value))
tfResource, err := target.AddFile("google_compute_instance_template", name, "metadata_"+g.Key, v)
if err != nil {
return err
}
t.Metadata[g.Key] = tfResource
}
func addMetadata(target *terraform.TerraformTarget, name string, metadata *compute.Metadata) (map[string]*terraform.Literal, error) {
if metadata == nil {
return nil, nil
}
m := make(map[string]*terraform.Literal)
for _, g := range metadata.Items {
v := fi.NewStringResource(fi.StringValue(g.Value))
tfResource, err := target.AddFile("google_compute_instance_template", name, "metadata_"+g.Key, v)
if err != nil {
return nil, err
}
return nil
m[g.Key] = tfResource
}
return m, nil
}
func (t *terraformInstanceCommon) AddServiceAccounts(serviceAccounts []*compute.ServiceAccount) {
func addServiceAccounts(serviceAccounts []*compute.ServiceAccount) *terraformServiceAccount {
// there's an inconsistency here- GCP only lets you have one service account per VM
// terraform gets it right, but the golang api doesn't. womp womp :(
if len(serviceAccounts) != 1 {
klog.Fatal("Instances can only have 1 service account assigned.")
} else {
klog.Infof("adding csa: %v", serviceAccounts[0].Email)
csa := serviceAccounts[0]
tsa := &terraformServiceAccount{
Email: csa.Email,
Scopes: csa.Scopes,
}
// for _, scope := range csa.Scopes {
// tsa.Scopes = append(tsa.Scopes, scope)
// }
t.ServiceAccount = tsa
}
klog.Infof("adding csa: %v", serviceAccounts[0].Email)
csa := serviceAccounts[0]
tsa := &terraformServiceAccount{
Email: csa.Email,
Scopes: csa.Scopes,
}
// for _, scope := range csa.Scopes {
// tsa.Scopes = append(tsa.Scopes, scope)
// }
return tsa
}
func (_ *InstanceTemplate) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *InstanceTemplate) error {
project := t.Project
@ -565,10 +546,10 @@ func (_ *InstanceTemplate) RenderTerraform(t *terraform.TerraformTarget, a, e, c
//tf.Zone = i.Properties.Zone
tf.Tags = i.Properties.Tags.Items
tf.AddServiceAccounts(i.Properties.ServiceAccounts)
tf.ServiceAccount = addServiceAccounts(i.Properties.ServiceAccounts)
for _, d := range i.Properties.Disks {
tfd := &terraformAttachedDisk{
tfd := &terraformInstanceTemplateAttachedDisk{
AutoDelete: d.AutoDelete,
Boot: d.Boot,
DeviceName: d.DeviceName,
@ -584,9 +565,13 @@ func (_ *InstanceTemplate) RenderTerraform(t *terraform.TerraformTarget, a, e, c
tf.Disks = append(tf.Disks, tfd)
}
tf.AddNetworks(e.Network, e.Subnet, i.Properties.NetworkInterfaces)
tf.NetworkInterfaces = addNetworks(e.Network, e.Subnet, i.Properties.NetworkInterfaces)
tf.AddMetadata(t, name, i.Properties.Metadata)
metadata, err := addMetadata(t, name, i.Properties.Metadata)
if err != nil {
return err
}
tf.Metadata = metadata
if i.Properties.Scheduling != nil {
tf.Scheduling = &terraformScheduling{

View File

@ -40,5 +40,6 @@ go_test(
"//pkg/diff:go_default_library",
"//vendor/github.com/hashicorp/hcl/v2/hclwrite:go_default_library",
"//vendor/github.com/zclconf/go-cty/cty:go_default_library",
"//vendor/github.com/zclconf/go-cty/cty/gocty:go_default_library",
],
)

View File

@ -177,17 +177,32 @@ func writeMap(body *hclwrite.Body, key string, values map[string]cty.Value) {
}
sort.Strings(keys)
for _, k := range keys {
v := values[k]
tokens = append(tokens, []*hclwrite.Token{
{Type: hclsyntax.TokenOQuote, Bytes: []byte{'"'}, SpacesBefore: 1},
{Type: hclsyntax.TokenQuotedLit, Bytes: []byte(k)},
{Type: hclsyntax.TokenCQuote, Bytes: []byte{'"'}, SpacesBefore: 1},
{Type: hclsyntax.TokenEqual, Bytes: []byte("="), SpacesBefore: 1},
{Type: hclsyntax.TokenOQuote, Bytes: []byte{'"'}, SpacesBefore: 1},
{Type: hclsyntax.TokenQuotedLit, Bytes: []byte(v.AsString())},
{Type: hclsyntax.TokenCQuote, Bytes: []byte{'"'}, SpacesBefore: 1},
{Type: hclsyntax.TokenNewline, Bytes: []byte("\n")},
}...)
v := values[k]
refLiteral := reflect.New(reflect.TypeOf(Literal{}))
err := gocty.FromCtyValue(v, refLiteral.Interface())
// If this is a map of literals then do not surround the value with quotes
if literal, ok := refLiteral.Interface().(*Literal); err == nil && ok {
// For maps of literals we currently only support file references
// If we ever need to support a map of strings to resource property references that can be added here
if literal.FilePath != "" {
tokens = append(tokens, &hclwrite.Token{Type: hclsyntax.TokenIdent, Bytes: []byte(fmt.Sprintf("file(%q)", literal.FilePath))})
}
} else {
tokens = append(tokens, []*hclwrite.Token{
{Type: hclsyntax.TokenOQuote, Bytes: []byte{'"'}, SpacesBefore: 1},
{Type: hclsyntax.TokenQuotedLit, Bytes: []byte(v.AsString())},
{Type: hclsyntax.TokenOQuote, Bytes: []byte{'"'}, SpacesBefore: 1},
}...)
}
tokens = append(tokens, &hclwrite.Token{Type: hclsyntax.TokenNewline, Bytes: []byte("\n")})
}
tokens = append(tokens,
&hclwrite.Token{Type: hclsyntax.TokenCBrace, Bytes: []byte("}")},

View File

@ -22,6 +22,7 @@ import (
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
"k8s.io/kops/pkg/diff"
)
@ -262,3 +263,53 @@ tags = {
})
}
}
func TestWriteMapLiterals(t *testing.T) {
cases := []struct {
name string
values map[string]Literal
expected string
}{
{
name: "literal values",
values: map[string]Literal{
"key1": {FilePath: "${module.path}/path/to/value1"},
"key2": {FilePath: "${module.path}/path/to/value2"},
},
expected: `
metadata = {
"key1" = file("${module.path}/path/to/value1")
"key2" = file("${module.path}/path/to/value2")
}
`,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
literalMap := make(map[string]cty.Value)
for k, v := range tc.values {
literalType, err := gocty.ImpliedType(v)
if err != nil {
t.Errorf("unexpected error %v", err)
}
literalVal, err := gocty.ToCtyValue(v, literalType)
if err != nil {
t.Errorf("unexpected error %v", err)
}
literalMap[k] = literalVal
}
f := hclwrite.NewEmptyFile()
root := f.Body()
writeMap(root, "metadata", literalMap)
actual := strings.TrimSpace(string(f.Bytes()))
expected := strings.TrimSpace(tc.expected)
if actual != expected {
diffString := diff.FormatDiff(expected, string(actual))
t.Logf("diff:\n%s\n", diffString)
t.Errorf("expected: '%s', got: '%s'\n", expected, actual)
}
})
}
}