1
2
3
4
5
6
7
8
9
10
11
12
13
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
31
32
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
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
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
209
210
211
212 func stringLitToHCL(val string) string {
213 return string(hclwrite.TokensForValue(cty.StringVal(val)).Bytes())
214 }
215
View as plain text