...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/tf/serialization/tf_resource.go

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/pkg/tf/serialization

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package serialization
    16  
    17  import (
    18  	"fmt"
    19  	"sort"
    20  	"strconv"
    21  	"strings"
    22  
    23  	"github.com/hashicorp/hcl/hcl/printer"
    24  	"github.com/hashicorp/hcl/v2/hclwrite"
    25  	tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
    26  	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
    27  	"github.com/zclconf/go-cty/cty"
    28  )
    29  
    30  // InstanceStateToHCL converts a terraform.InstanceState into the HCL that represents the maximally hydrated form.  The generated
    31  // HCL will not include any references to other resources, since that information cannot be reconstructed without a full view
    32  // of all resources present..
    33  func InstanceStateToHCL(state *terraform.InstanceState, info *terraform.InstanceInfo, provider *tfschema.Provider) (string, error) {
    34  	providerSchema := provider.ResourcesMap[info.Type].Schema
    35  	str, err := resourceOrSubresourceHCL(providerSchema, state.Attributes)
    36  	if err != nil {
    37  		return "", err
    38  	}
    39  	str = fmt.Sprintf("resource %q %q {\n%s\n}\n", info.Type, info.Id, str)
    40  	hBytes, err := printer.Format([]byte(str))
    41  	if err != nil {
    42  		return "", fmt.Errorf("could not pretty print hcl: %w", err)
    43  	}
    44  	return string(hBytes), nil
    45  }
    46  
    47  func resourceOrSubresourceHCL(schema map[string]*tfschema.Schema, attributes map[string]string) (string, error) {
    48  	var hcl strings.Builder
    49  	keys := make([]string, 0, len(schema))
    50  	for k := range schema {
    51  		keys = append(keys, k)
    52  	}
    53  	sort.Strings(keys)
    54  	for _, aname := range keys {
    55  		aschema := schema[aname]
    56  		if !aschema.Optional && !aschema.Required {
    57  			continue
    58  		}
    59  		switch aschema.Type {
    60  		case tfschema.TypeFloat:
    61  			fallthrough
    62  		case tfschema.TypeInt:
    63  			fallthrough
    64  		case tfschema.TypeBool:
    65  			if attributes[aname] == "" {
    66  				continue
    67  			}
    68  			hcl.WriteString(fmt.Sprintf("%s = %s\n", aname, attributes[aname]))
    69  		case tfschema.TypeString:
    70  			if attributes[aname] == "" {
    71  				continue
    72  			}
    73  			hcl.WriteString(fmt.Sprintf("%s = %s\n", aname, stringLitToHCL(attributes[aname])))
    74  		// for each non-primitive, find if there are any values and set them.
    75  		case tfschema.TypeMap:
    76  			if val, ok := attributes[aname+".%"]; !ok || val == "0" {
    77  				continue
    78  			}
    79  			hcl.WriteString(fmt.Sprintf("%s = {\n", aname))
    80  			m, err := mapFromPrefix(attributes, aname)
    81  			if err != nil {
    82  				return "", err
    83  			}
    84  			var keys []string
    85  			for k := range m {
    86  				keys = append(keys, k)
    87  			}
    88  			sort.Strings(keys)
    89  			for _, k := range keys {
    90  				v := m[k]
    91  				hcl.WriteString(fmt.Sprintf("%s = %q\n", k, v))
    92  			}
    93  			hcl.WriteString("}\n")
    94  		case tfschema.TypeSet:
    95  			fallthrough
    96  		case tfschema.TypeList:
    97  			if val, ok := attributes[aname+".#"]; !ok || val == "0" {
    98  				continue
    99  			}
   100  			if subtype, ok := aschema.Elem.(*tfschema.Schema); ok {
   101  				hcl.WriteString(fmt.Sprintf("%s = [", aname))
   102  				l, err := listFromPrefix(attributes, aname, aschema.Type)
   103  				if err != nil {
   104  					return "", err
   105  				}
   106  				for _, v := range l {
   107  					switch subtype.Type {
   108  					case tfschema.TypeFloat:
   109  						fallthrough
   110  					case tfschema.TypeInt:
   111  						fallthrough
   112  					case tfschema.TypeBool:
   113  						hcl.WriteString(fmt.Sprintf("%s, ", v))
   114  					case tfschema.TypeString:
   115  						hcl.WriteString(fmt.Sprintf("%q, ", v))
   116  					}
   117  				}
   118  				hcl.WriteString("]\n")
   119  			} else if subtype, ok := aschema.Elem.(*tfschema.Resource); ok {
   120  				cnt, err := strconv.Atoi(attributes[aname+".#"])
   121  				if err != nil {
   122  					return "", fmt.Errorf("could not parse count of %s, %w", aname, err)
   123  				}
   124  				for i := 0; i < cnt; i++ {
   125  					subAttrs, err := mapsFromPrefix(attributes, fmt.Sprintf("%s.%d", aname, i))
   126  					if err != nil {
   127  						return "", fmt.Errorf("could not get subresource attributes for %s: %w", aname, err)
   128  					}
   129  					subresource, err := resourceOrSubresourceHCL(subtype.Schema, subAttrs)
   130  					if err != nil {
   131  						return "", fmt.Errorf("could not create subresource %s: %w", aname, err)
   132  					}
   133  					hcl.WriteString(fmt.Sprintf("%s {\n%s\n}\n", aname, subresource))
   134  				}
   135  			}
   136  		}
   137  	}
   138  	return hcl.String(), nil
   139  }
   140  
   141  func mapsFromPrefix(attributes map[string]string, prefix string) (map[string]string, error) {
   142  	a := make(map[string]string)
   143  	for k, v := range attributes {
   144  		if strings.HasPrefix(k, prefix+".") {
   145  			a[strings.TrimPrefix(k, prefix+".")] = v
   146  		}
   147  	}
   148  	return a, nil
   149  }
   150  
   151  func listFromPrefix(attributes map[string]string, prefix string, listType tfschema.ValueType) ([]string, error) {
   152  	size, err := strconv.Atoi(attributes[prefix+".#"])
   153  	if err != nil {
   154  		return nil, fmt.Errorf("could not parse size of list %s: %w", prefix, err)
   155  	}
   156  	switch listType {
   157  	case tfschema.TypeList:
   158  		return listListFromPrefix(attributes, prefix, size)
   159  	case tfschema.TypeSet:
   160  		return setListFromPrefix(attributes, prefix, size), nil
   161  	default:
   162  		return nil, fmt.Errorf("unhandled list type: %v", listType)
   163  	}
   164  }
   165  
   166  func listListFromPrefix(attributes map[string]string, prefix string, size int) ([]string, error) {
   167  	out := make([]string, size)
   168  	for k, v := range attributes {
   169  		if strings.HasPrefix(k, prefix+".") && k != prefix+".#" {
   170  			kparts := strings.Split(k, ".")
   171  			c, err := strconv.Atoi(kparts[len(kparts)-1])
   172  			if err != nil {
   173  				return nil, fmt.Errorf("could not parse index of %s: %w", k, err)
   174  			}
   175  			out[c] = v
   176  		}
   177  	}
   178  	return out, nil
   179  }
   180  
   181  func setListFromPrefix(attributes map[string]string, prefix string, size int) []string {
   182  	out := make([]string, 0, size)
   183  	for k, v := range attributes {
   184  		if strings.HasPrefix(k, prefix+".") && k != prefix+".#" {
   185  			out = append(out, v)
   186  		}
   187  	}
   188  	// sort the list so that the ordering is deterministic for tests
   189  	sort.Strings(out)
   190  	return out
   191  }
   192  
   193  func mapFromPrefix(attributes map[string]string, prefix string) (map[string]string, error) {
   194  	size, err := strconv.Atoi(attributes[prefix+".%"])
   195  	if err != nil {
   196  		return nil, fmt.Errorf("could not parse size of map %s: %w", prefix, err)
   197  	}
   198  	out := make(map[string]string, size)
   199  	for k, v := range attributes {
   200  		if strings.HasPrefix(k, prefix+".") && k != prefix+".%" {
   201  			kparts := strings.Split(k, ".")
   202  			out[kparts[len(kparts)-1]] = v
   203  		}
   204  	}
   205  	return out, nil
   206  }
   207  
   208  // stringLitToHCL converts a string literal value into a quoted, HCL-compatible
   209  // format. It currently uses the `TokensForValue()` function from the
   210  // `hclwrite` library to stay consistent with the HCL spec, particularly in
   211  // cases where special characters need to be escaped.
   212  func stringLitToHCL(val string) string {
   213  	return string(hclwrite.TokensForValue(cty.StringVal(val)).Bytes())
   214  }
   215  

View as plain text