pkg/template/values/hcl2/convert.go (198 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package hcl2 import ( "fmt" "strings" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/zclconf/go-cty/cty" ctyconvert "github.com/zclconf/go-cty/cty/convert" ctyjson "github.com/zclconf/go-cty/cty/json" ) // This file is attributed to https://github.com/tmccombs/hcl2json. // convertBlock() is manipulated for combining the both blocks and labels for one given resource. type jsonObj map[string]interface{} // Convert an hcl File to a json serializable object // This assumes that the body is a hclsyntax.Body func convertFile(file *hcl.File) (jsonObj, error) { c := converter{bytes: file.Bytes} body, ok := file.Body.(*hclsyntax.Body) if !ok { return nil, fmt.Errorf("file has an unexpected body type: %T", file.Body) } return c.convertBody(body) } type converter struct { bytes []byte } func (c *converter) rangeSource(r hcl.Range) string { return string(c.bytes[r.Start.Byte:r.End.Byte]) } func (c *converter) convertBody(body *hclsyntax.Body) (jsonObj, error) { var err error out := make(jsonObj) for key, value := range body.Attributes { out[key], err = c.convertExpression(value.Expr) if err != nil { return nil, err } } for _, block := range body.Blocks { err = c.convertBlock(block, out) if err != nil { return nil, err } } return out, nil } func (c *converter) convertBlock(block *hclsyntax.Block, out jsonObj) error { key := block.Type value, err := c.convertBody(block.Body) if err != nil { return err } for _, label := range block.Labels { if inner, exists := out[key]; exists { var ok bool out, ok = inner.(jsonObj) if !ok { return fmt.Errorf("unable to convert Block to JSON: %v.%v", block.Type, strings.Join(block.Labels, ".")) } } else { obj := make(jsonObj) out[key] = obj out = obj } key = label } if current, exists := out[key]; exists { if list, ok := current.([]interface{}); ok { out[key] = append(list, value) } else { out[key] = []interface{}{current, value} } } else { out[key] = value } return nil } func (c *converter) convertExpression(expr hclsyntax.Expression) (interface{}, error) { // assume it is hcl syntax (because, um, it is) switch value := expr.(type) { case *hclsyntax.LiteralValueExpr: return ctyjson.SimpleJSONValue{Value: value.Val}, nil case *hclsyntax.TemplateExpr: return c.convertTemplate(value) case *hclsyntax.TemplateWrapExpr: return c.convertExpression(value.Wrapped) case *hclsyntax.TupleConsExpr: var list []interface{} for _, ex := range value.Exprs { elem, err := c.convertExpression(ex) if err != nil { return nil, err } list = append(list, elem) } return list, nil case *hclsyntax.ObjectConsExpr: m := make(jsonObj) for _, item := range value.Items { key, err := c.convertKey(item.KeyExpr) if err != nil { return nil, err } m[key], err = c.convertExpression(item.ValueExpr) if err != nil { return nil, err } } return m, nil default: return c.wrapExpr(expr), nil } } func (c *converter) convertKey(keyExpr hclsyntax.Expression) (string, error) { // a key should never have dynamic input if k, isKeyExpr := keyExpr.(*hclsyntax.ObjectConsKeyExpr); isKeyExpr { keyExpr = k.Wrapped if _, isTraversal := keyExpr.(*hclsyntax.ScopeTraversalExpr); isTraversal { return c.rangeSource(keyExpr.Range()), nil } } return c.convertStringPart(keyExpr) } func (c *converter) convertTemplate(t *hclsyntax.TemplateExpr) (string, error) { if t.IsStringLiteral() { // safe because the value is just the string v, err := t.Value(nil) if err != nil { return "", err } return v.AsString(), nil } var builder strings.Builder for _, part := range t.Parts { s, err := c.convertStringPart(part) if err != nil { return "", err } builder.WriteString(s) } return builder.String(), nil } func (c *converter) convertStringPart(expr hclsyntax.Expression) (string, error) { switch v := expr.(type) { case *hclsyntax.LiteralValueExpr: s, err := ctyconvert.Convert(v.Val, cty.String) if err != nil { return "", err } return s.AsString(), nil case *hclsyntax.TemplateExpr: return c.convertTemplate(v) case *hclsyntax.TemplateWrapExpr: return c.convertStringPart(v.Wrapped) case *hclsyntax.ConditionalExpr: return c.convertTemplateConditional(v) case *hclsyntax.TemplateJoinExpr: return c.convertTemplateFor(v.Tuple.(*hclsyntax.ForExpr)) default: // treating as an embedded expression return c.wrapExpr(expr), nil } } func (c *converter) convertTemplateConditional(expr *hclsyntax.ConditionalExpr) (string, error) { var builder strings.Builder builder.WriteString("%{if ") builder.WriteString(c.rangeSource(expr.Condition.Range())) builder.WriteString("}") trueResult, err := c.convertStringPart(expr.TrueResult) if err != nil { return "", nil } builder.WriteString(trueResult) falseResult, err := c.convertStringPart(expr.FalseResult) if err != nil { return "", nil } if falseResult != "" { builder.WriteString("%{else}") builder.WriteString(falseResult) } builder.WriteString("%{endif}") return builder.String(), nil } func (c *converter) convertTemplateFor(expr *hclsyntax.ForExpr) (string, error) { var builder strings.Builder builder.WriteString("%{for ") if expr.KeyVar != "" { builder.WriteString(expr.KeyVar) builder.WriteString(", ") } builder.WriteString(expr.ValVar) builder.WriteString(" in ") builder.WriteString(c.rangeSource(expr.CollExpr.Range())) builder.WriteString("}") templ, err := c.convertStringPart(expr.ValExpr) if err != nil { return "", err } builder.WriteString(templ) builder.WriteString("%{endfor}") return builder.String(), nil } func (c *converter) wrapExpr(expr hclsyntax.Expression) string { return "${" + c.rangeSource(expr.Range()) + "}" }