Add support for writing maps of literals, used by gce metadata

This commit is contained in:
Peter Rifel 2020-04-08 21:19:06 -05:00
parent 2a48c70ea8
commit ef76409046
5 changed files with 97 additions and 37 deletions

View File

@ -394,28 +394,25 @@ func ShortenImageURL(defaultProject string, imageURL string) (string, error) {
}
type terraformInstance struct {
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"`
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"`
}
type terraformInstanceAttachedDisk 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"`
// '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"`

View File

@ -417,16 +417,16 @@ func (_ *InstanceTemplate) RenderGCE(t *gce.GCEAPITarget, a, e, changes *Instanc
}
type terraformInstanceTemplate struct {
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"`
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 {
@ -444,12 +444,8 @@ 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"`

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)
}
})
}
}