Refactor writing Terraform resources

This commit is contained in:
John Gardiner Myers 2022-12-04 15:54:54 -08:00
parent 2fc25219be
commit 4573a690f2
3 changed files with 116 additions and 29 deletions

View File

@ -21,6 +21,7 @@ import (
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"unicode"
@ -71,13 +72,46 @@ func (o *object) Write(buffer *bytes.Buffer, indent int, key string) {
maxKeyLen = 0
}
o.field[key].Write(buffer, indent+2, key)
buffer.WriteString("\n")
}
writeIndent(buffer, indent)
buffer.WriteString("}")
buffer.WriteString("}\n")
}
type sliceObject struct {
members []element
}
func (s *sliceObject) IsSingleValue() bool {
return false
}
func (s *sliceObject) Write(buffer *bytes.Buffer, indent int, key string) {
for _, member := range s.members {
member.Write(buffer, indent, key)
}
}
type mapStringLiteral struct {
members map[string]*terraformWriter.Literal
}
func (m *mapStringLiteral) IsSingleValue() bool {
return false
}
func (m *mapStringLiteral) Write(buffer *bytes.Buffer, indent int, key string) {
writeMap(buffer, indent, key, m.members)
}
var literalType = reflect.TypeOf(terraformWriter.Literal{})
func ToElement(item interface{}) (element, error) {
if literal, ok := item.(*terraformWriter.Literal); ok {
if literal == nil {
return nil, nil
}
return literal, nil
}
v := reflect.ValueOf(item)
if v.Kind() == reflect.Pointer {
if v.IsNil() {
@ -85,10 +119,34 @@ func ToElement(item interface{}) (element, error) {
}
v = v.Elem()
}
if v.Kind() == reflect.String {
switch v.Kind() {
case reflect.Bool:
return terraformWriter.LiteralTokens(strconv.FormatBool(v.Bool())), nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return terraformWriter.LiteralTokens(strconv.FormatInt(v.Int(), 10)), nil
case reflect.Map:
if v.Type().Key().Kind() != reflect.String {
panic(fmt.Sprintf("unhandled map key type %s", v.Type().Key().Kind()))
}
elemType := v.Type().Elem()
if elemType.Kind() == reflect.Pointer && elemType.Elem() == literalType {
o := &mapStringLiteral{members: make(map[string]*terraformWriter.Literal, v.Len())}
for _, key := range v.MapKeys() {
o.members[key.String()] = v.MapIndex(key).Interface().(*terraformWriter.Literal)
}
return o, nil
}
if elemType.Kind() != reflect.String {
panic(fmt.Sprintf("unhandled map value type %s", elemType.Kind()))
}
o := &mapStringLiteral{members: make(map[string]*terraformWriter.Literal, v.Len())}
for _, key := range v.MapKeys() {
o.members[key.String()] = terraformWriter.LiteralFromStringValue(v.MapIndex(key).String())
}
return o, nil
case reflect.String:
return terraformWriter.LiteralFromStringValue(v.String()), nil
}
if v.Kind() == reflect.Struct {
case reflect.Struct:
o := &object{
field: map[string]element{},
}
@ -102,8 +160,50 @@ func ToElement(item interface{}) (element, error) {
}
}
return o, nil
case reflect.Slice:
if v.Len() == 0 {
return nil, nil
}
elemType := v.Type().Elem()
if elemType.Kind() == reflect.Pointer {
elemType = elemType.Elem()
if elemType == literalType {
elements := make([]*terraformWriter.Literal, v.Len())
for i := range elements {
elem := v.Index(i)
elements[i] = elem.Interface().(*terraformWriter.Literal)
}
return terraformWriter.LiteralListExpression(elements...), nil
}
}
switch elemType.Kind() {
case reflect.String:
elements := make([]*terraformWriter.Literal, v.Len())
for i := range elements {
elem := v.Index(i)
if elem.Kind() == reflect.Pointer {
// TODO can these ever be nil?
elem = elem.Elem()
}
elements[i] = terraformWriter.LiteralFromStringValue(elem.String())
}
return terraformWriter.LiteralListExpression(elements...), nil
case reflect.Struct:
o := &sliceObject{members: make([]element, v.Len())}
for i := range o.members {
elem, err := ToElement(v.Index(i).Interface())
if err != nil {
return nil, fmt.Errorf("converting slice member %d to element: %w", i, err)
}
o.members[i] = elem
}
return o, nil
default:
panic(fmt.Sprintf("unhandled slice member kind %s", elemType.Kind()))
}
default:
panic(fmt.Sprintf("unhandled kind %s", v.Kind()))
}
panic(fmt.Sprintf("unhandled kind %s", v.Kind()))
}
func fieldKey(field reflect.StructField) string {

View File

@ -23,7 +23,6 @@ import (
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/featureflag"
"k8s.io/kops/upup/pkg/fi/cloudup/terraformWriter"
@ -63,6 +62,8 @@ func (t *TerraformTarget) finishHCL2() error {
return err
}
buf := bytes.NewBuffer(hclwrite.Format(f.Bytes()))
resourcesByType, err := t.GetResourcesByType()
if err != nil {
return err
@ -81,31 +82,16 @@ func (t *TerraformTarget) finishHCL2() error {
}
sort.Strings(resourceNames)
for _, resourceName := range resourceNames {
item := resources[resourceName]
element, err := ToElement(resources[resourceName])
if err != nil {
return fmt.Errorf("resource %q %q: %w", resourceType, resourceName, err)
}
resBlock := rootBody.AppendNewBlock("resource", []string{resourceType, resourceName})
resBody := resBlock.Body()
resType, err := gocty.ImpliedType(item)
if err != nil {
return err
}
resVal, err := gocty.ToCtyValue(item, resType)
if err != nil {
return err
}
if resVal.IsNull() {
continue
}
resVal.ForEachElement(func(key cty.Value, value cty.Value) bool {
writeValue(resBody, key.AsString(), value)
return false
})
rootBody.AppendNewline()
element.Write(buf, 0, fmt.Sprintf("resource %q %q", resourceType, resourceName))
buf.WriteString("\n")
}
}
buf := bytes.NewBuffer(hclwrite.Format(f.Bytes()))
dataSourcesByType, err := t.GetDataSourcesByType()
if err != nil {
return err
@ -130,7 +116,7 @@ func (t *TerraformTarget) finishHCL2() error {
}
element.Write(buf, 0, fmt.Sprintf("data %q %q", dataSourceType, dataSourceName))
buf.WriteString("\n\n")
buf.WriteString("\n")
}
}

View File

@ -45,6 +45,7 @@ func (l *Literal) IsSingleValue() bool {
func (l *Literal) Write(buffer *bytes.Buffer, indent int, key string) {
buffer.WriteString("= ")
buffer.WriteString(l.String)
buffer.WriteString("\n")
}
// LiteralFunctionExpression constructs a Literal representing the result of