github.com/mcuadros/ascode@v1.3.1/starlark/types/hcl.go (about) 1 package types 2 3 import ( 4 "fmt" 5 "math/big" 6 "regexp" 7 "sort" 8 "unicode" 9 "unicode/utf8" 10 11 "github.com/hashicorp/hcl/v2" 12 "github.com/hashicorp/hcl/v2/hclsyntax" 13 "github.com/hashicorp/hcl/v2/hclwrite" 14 "github.com/zclconf/go-cty/cty" 15 "go.starlark.net/starlark" 16 ) 17 18 // HCLCompatible defines if the struct is suitable of by encoded in HCL. 19 type HCLCompatible interface { 20 ToHCL(b *hclwrite.Body) 21 } 22 23 // BuiltinHCL returns a starlak.Builtin function to generate HCL from objects 24 // implementing the HCLCompatible interface. 25 // 26 // outline: types 27 // functions: 28 // hcl(resource) string 29 // Returns the HCL encoding of the given resource. 30 // params: 31 // resource <resource> 32 // resource to be encoded. 33 // 34 func BuiltinHCL() starlark.Value { 35 return starlark.NewBuiltin("hcl", func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, _ []starlark.Tuple) (starlark.Value, error) { 36 if args.Len() != 1 { 37 return nil, fmt.Errorf("exactly one argument is required") 38 } 39 40 value := args.Index(0) 41 hcl, ok := value.(HCLCompatible) 42 if !ok { 43 return nil, fmt.Errorf("value type %s doesn't support HCL conversion", value.Type()) 44 } 45 46 f := hclwrite.NewEmptyFile() 47 hcl.ToHCL(f.Body()) 48 return starlark.String(string(f.Bytes())), nil 49 }) 50 } 51 52 // ToHCL honors the HCLCompatible interface. 53 func (s *Terraform) ToHCL(b *hclwrite.Body) { 54 if s.b != nil { 55 s.b.ToHCL(b) 56 } 57 58 s.p.ToHCL(b) 59 } 60 61 // ToHCL honors the HCLCompatible interface. 62 func (s *Dict) ToHCL(b *hclwrite.Body) { 63 for _, v := range s.Keys() { 64 p, _, _ := s.Get(v) 65 hcl, ok := p.(HCLCompatible) 66 if !ok { 67 continue 68 } 69 70 hcl.ToHCL(b) 71 } 72 } 73 74 // ToHCL honors the HCLCompatible interface. 75 func (s *Provider) ToHCL(b *hclwrite.Body) { 76 block := b.AppendNewBlock("provider", []string{s.typ}) 77 78 block.Body().SetAttributeValue("alias", cty.StringVal(s.name)) 79 block.Body().SetAttributeValue("version", cty.StringVal(string(s.meta.Version))) 80 s.Resource.doToHCLAttributes(block.Body()) 81 82 s.dataSources.ToHCL(b) 83 s.resources.ToHCL(b) 84 b.AppendNewline() 85 } 86 87 // ToHCL honors the HCLCompatible interface. 88 func (s *Provisioner) ToHCL(b *hclwrite.Body) { 89 block := b.AppendNewBlock("provisioner", []string{s.typ}) 90 s.Resource.doToHCLAttributes(block.Body()) 91 } 92 93 // ToHCL honors the HCLCompatible interface. 94 func (s *Backend) ToHCL(b *hclwrite.Body) { 95 parent := b.AppendNewBlock("terraform", nil) 96 97 block := parent.Body().AppendNewBlock("backend", []string{s.typ}) 98 s.Resource.doToHCLAttributes(block.Body()) 99 b.AppendNewline() 100 } 101 102 // ToHCL honors the HCLCompatible interface. 103 func (t *ResourceCollectionGroup) ToHCL(b *hclwrite.Body) { 104 names := make(sort.StringSlice, len(t.collections)) 105 var i int 106 for name := range t.collections { 107 names[i] = name 108 i++ 109 } 110 111 sort.Sort(names) 112 for _, name := range names { 113 t.collections[name].ToHCL(b) 114 } 115 } 116 117 // ToHCL honors the HCLCompatible interface. 118 func (c *ResourceCollection) ToHCL(b *hclwrite.Body) { 119 for i := 0; i < c.Len(); i++ { 120 c.Index(i).(*Resource).ToHCL(b) 121 } 122 } 123 124 // ToHCL honors the HCLCompatible interface. 125 func (r *Resource) ToHCL(b *hclwrite.Body) { 126 if len(b.Blocks()) != 0 || len(b.Attributes()) != 0 { 127 b.AppendNewline() 128 } 129 130 var block *hclwrite.Block 131 if r.kind != NestedKind { 132 labels := []string{r.typ, r.Name()} 133 block = b.AppendNewBlock(string(r.kind), labels) 134 } else { 135 block = b.AppendNewBlock(r.typ, nil) 136 } 137 138 body := block.Body() 139 140 if r.kind != NestedKind && r.parent != nil && r.parent.kind == ProviderKind { 141 body.SetAttributeTraversal("provider", hcl.Traversal{ 142 hcl.TraverseRoot{Name: r.parent.typ}, 143 hcl.TraverseAttr{Name: r.parent.Name()}, 144 }) 145 } 146 147 r.doToHCLAttributes(body) 148 r.doToHCLDependencies(body) 149 r.doToHCLProvisioner(body) 150 } 151 152 func (r *Resource) doToHCLAttributes(body *hclwrite.Body) { 153 r.values.ForEach(func(v *NamedValue) error { 154 if _, ok := r.block.Attributes[v.Name]; !ok { 155 return nil 156 } 157 158 tokens := appendTokensForValue(v.v, nil) 159 body.SetAttributeRaw(v.Name, tokens) 160 return nil 161 }) 162 163 r.values.ForEach(func(v *NamedValue) error { 164 if _, ok := r.block.BlockTypes[v.Name]; !ok { 165 return nil 166 } 167 168 if collection, ok := v.Starlark().(HCLCompatible); ok { 169 collection.ToHCL(body) 170 } 171 172 return nil 173 }) 174 } 175 176 func (r *Resource) doToHCLDependencies(body *hclwrite.Body) { 177 if len(r.dependencies) == 0 { 178 return 179 } 180 181 toks := []*hclwrite.Token{} 182 toks = append(toks, &hclwrite.Token{ 183 Type: hclsyntax.TokenIdent, 184 Bytes: []byte("depends_on"), 185 }) 186 187 toks = append(toks, &hclwrite.Token{ 188 Type: hclsyntax.TokenEqual, Bytes: []byte{'='}, 189 }, &hclwrite.Token{ 190 Type: hclsyntax.TokenOBrack, Bytes: []byte{'['}, 191 }) 192 193 l := len(r.dependencies) 194 for i, dep := range r.dependencies { 195 name := fmt.Sprintf("%s.%s", dep.typ, dep.Name()) 196 toks = append(toks, &hclwrite.Token{ 197 Type: hclsyntax.TokenIdent, Bytes: []byte(name), 198 }) 199 200 if i+1 == l { 201 break 202 } 203 204 toks = append(toks, &hclwrite.Token{ 205 Type: hclsyntax.TokenComma, Bytes: []byte{','}, 206 }) 207 } 208 209 toks = append(toks, &hclwrite.Token{ 210 Type: hclsyntax.TokenCBrack, Bytes: []byte{']'}, 211 }) 212 213 body.AppendUnstructuredTokens(toks) 214 body.AppendNewline() 215 } 216 217 func (r *Resource) doToHCLProvisioner(body *hclwrite.Body) { 218 if len(r.provisioners) == 0 { 219 return 220 } 221 222 for _, p := range r.provisioners { 223 if len(body.Blocks()) != 0 || len(body.Attributes()) != 0 { 224 body.AppendNewline() 225 } 226 227 p.ToHCL(body) 228 } 229 } 230 231 var containsInterpolation = regexp.MustCompile(`(?mU)\$\{.*\}`) 232 233 func appendTokensForValue(val starlark.Value, toks hclwrite.Tokens) hclwrite.Tokens { 234 switch v := val.(type) { 235 case starlark.NoneType: 236 toks = append(toks, &hclwrite.Token{ 237 Type: hclsyntax.TokenIdent, 238 Bytes: []byte(`null`), 239 }) 240 case starlark.Bool: 241 var src []byte 242 if v { 243 src = []byte(`true`) 244 } else { 245 src = []byte(`false`) 246 } 247 toks = append(toks, &hclwrite.Token{ 248 Type: hclsyntax.TokenIdent, 249 Bytes: src, 250 }) 251 case starlark.Float: 252 bf := big.NewFloat(float64(v)) 253 srcStr := bf.Text('f', -1) 254 toks = append(toks, &hclwrite.Token{ 255 Type: hclsyntax.TokenNumberLit, 256 Bytes: []byte(srcStr), 257 }) 258 case starlark.Int: 259 srcStr := fmt.Sprintf("%d", v) 260 toks = append(toks, &hclwrite.Token{ 261 Type: hclsyntax.TokenNumberLit, 262 Bytes: []byte(srcStr), 263 }) 264 case starlark.String: 265 src := []byte(v.GoString()) 266 if !containsInterpolation.Match(src) { 267 src = escapeQuotedStringLit(v.GoString()) 268 } 269 270 toks = append(toks, &hclwrite.Token{ 271 Type: hclsyntax.TokenOQuote, 272 Bytes: []byte{'"'}, 273 }) 274 if len(src) > 0 { 275 toks = append(toks, &hclwrite.Token{ 276 Type: hclsyntax.TokenQuotedLit, 277 Bytes: []byte(src), 278 }) 279 } 280 toks = append(toks, &hclwrite.Token{ 281 Type: hclsyntax.TokenCQuote, 282 Bytes: []byte{'"'}, 283 }) 284 case *starlark.List: 285 toks = append(toks, &hclwrite.Token{ 286 Type: hclsyntax.TokenOBrack, 287 Bytes: []byte{'['}, 288 }) 289 290 for i := 0; i < v.Len(); i++ { 291 if i > 0 { 292 toks = append(toks, &hclwrite.Token{ 293 Type: hclsyntax.TokenComma, 294 Bytes: []byte{','}, 295 }) 296 } 297 298 toks = appendTokensForValue(v.Index(i), toks) 299 } 300 301 toks = append(toks, &hclwrite.Token{ 302 Type: hclsyntax.TokenCBrack, 303 Bytes: []byte{']'}, 304 }) 305 306 case *starlark.Dict: 307 toks = append(toks, &hclwrite.Token{ 308 Type: hclsyntax.TokenOBrace, 309 Bytes: []byte{'{'}, 310 }) 311 312 i := 0 313 for _, eKey := range v.Keys() { 314 if i > 0 { 315 toks = append(toks, &hclwrite.Token{ 316 Type: hclsyntax.TokenComma, 317 Bytes: []byte{','}, 318 }) 319 } 320 321 eVal, _, _ := v.Get(eKey) 322 if hclsyntax.ValidIdentifier(eKey.(starlark.String).GoString()) { 323 toks = append(toks, &hclwrite.Token{ 324 Type: hclsyntax.TokenIdent, 325 Bytes: []byte(eKey.(starlark.String).GoString()), 326 }) 327 } else { 328 toks = appendTokensForValue(eKey, toks) 329 } 330 toks = append(toks, &hclwrite.Token{ 331 Type: hclsyntax.TokenEqual, 332 Bytes: []byte{'='}, 333 }) 334 toks = appendTokensForValue(eVal, toks) 335 i++ 336 } 337 338 toks = append(toks, &hclwrite.Token{ 339 Type: hclsyntax.TokenCBrace, 340 Bytes: []byte{'}'}, 341 }) 342 case *Attribute: 343 toks = append(toks, &hclwrite.Token{ 344 Type: hclsyntax.TokenIdent, 345 Bytes: []byte(v.sString.String()), 346 }) 347 default: 348 panic(fmt.Sprintf("cannot produce tokens for %#v", val)) 349 } 350 351 return toks 352 } 353 354 func escapeQuotedStringLit(s string) []byte { 355 if len(s) == 0 { 356 return nil 357 } 358 buf := make([]byte, 0, len(s)) 359 for i, r := range s { 360 switch r { 361 case '\n': 362 buf = append(buf, '\\', 'n') 363 case '\r': 364 buf = append(buf, '\\', 'r') 365 case '\t': 366 buf = append(buf, '\\', 't') 367 case '"': 368 buf = append(buf, '\\', '"') 369 case '\\': 370 buf = append(buf, '\\', '\\') 371 case '$', '%': 372 buf = appendRune(buf, r) 373 remain := s[i+1:] 374 if len(remain) > 0 && remain[0] == '{' { 375 // Double up our template introducer symbol to escape it. 376 buf = appendRune(buf, r) 377 } 378 default: 379 if !unicode.IsPrint(r) { 380 var fmted string 381 if r < 65536 { 382 fmted = fmt.Sprintf("\\u%04x", r) 383 } else { 384 fmted = fmt.Sprintf("\\U%08x", r) 385 } 386 buf = append(buf, fmted...) 387 } else { 388 buf = appendRune(buf, r) 389 } 390 } 391 } 392 return buf 393 } 394 395 func appendRune(b []byte, r rune) []byte { 396 l := utf8.RuneLen(r) 397 for i := 0; i < l; i++ { 398 b = append(b, 0) // make room at the end of our buffer 399 } 400 ch := b[len(b)-l:] 401 utf8.EncodeRune(ch, r) 402 return b 403 }