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  }