github.com/zntrio/harp/v2@v2.0.9/pkg/template/values/hcl2/convert.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package hcl2
    19  
    20  import (
    21  	"fmt"
    22  	"strings"
    23  
    24  	"github.com/hashicorp/hcl/v2"
    25  	"github.com/hashicorp/hcl/v2/hclsyntax"
    26  	"github.com/zclconf/go-cty/cty"
    27  	ctyconvert "github.com/zclconf/go-cty/cty/convert"
    28  	ctyjson "github.com/zclconf/go-cty/cty/json"
    29  )
    30  
    31  // This file is attributed to https://github.com/tmccombs/hcl2json.
    32  // convertBlock() is manipulated for combining the both blocks and labels for one given resource.
    33  
    34  type jsonObj map[string]interface{}
    35  
    36  // Convert an hcl File to a json serializable object
    37  // This assumes that the body is a hclsyntax.Body.
    38  func convertFile(file *hcl.File) (jsonObj, error) {
    39  	c := converter{bytes: file.Bytes}
    40  	body, ok := file.Body.(*hclsyntax.Body)
    41  	if !ok {
    42  		return nil, fmt.Errorf("file has an unexpected body type: %T", file.Body)
    43  	}
    44  	return c.convertBody(body)
    45  }
    46  
    47  type converter struct {
    48  	bytes []byte
    49  }
    50  
    51  func (c *converter) rangeSource(r hcl.Range) string {
    52  	return string(c.bytes[r.Start.Byte:r.End.Byte])
    53  }
    54  
    55  func (c *converter) convertBody(body *hclsyntax.Body) (jsonObj, error) {
    56  	var err error
    57  	out := make(jsonObj)
    58  	for key, value := range body.Attributes {
    59  		out[key], err = c.convertExpression(value.Expr)
    60  		if err != nil {
    61  			return nil, err
    62  		}
    63  	}
    64  
    65  	for _, block := range body.Blocks {
    66  		err = c.convertBlock(block, out)
    67  		if err != nil {
    68  			return nil, err
    69  		}
    70  	}
    71  
    72  	return out, nil
    73  }
    74  
    75  func (c *converter) convertBlock(block *hclsyntax.Block, out jsonObj) error {
    76  	key := block.Type
    77  	value, err := c.convertBody(block.Body)
    78  	if err != nil {
    79  		return err
    80  	}
    81  
    82  	for _, label := range block.Labels {
    83  		if inner, exists := out[key]; exists {
    84  			var ok bool
    85  			out, ok = inner.(jsonObj)
    86  			if !ok {
    87  				return fmt.Errorf("unable to convert Block to JSON: %v.%v", block.Type, strings.Join(block.Labels, "."))
    88  			}
    89  		} else {
    90  			obj := make(jsonObj)
    91  			out[key] = obj
    92  			out = obj
    93  		}
    94  		key = label
    95  	}
    96  
    97  	if current, exists := out[key]; exists {
    98  		if list, ok := current.([]interface{}); ok {
    99  			out[key] = append(list, value)
   100  		} else {
   101  			out[key] = []interface{}{current, value}
   102  		}
   103  	} else {
   104  		out[key] = value
   105  	}
   106  
   107  	return nil
   108  }
   109  
   110  func (c *converter) convertExpression(expr hclsyntax.Expression) (interface{}, error) {
   111  	// assume it is hcl syntax (because, um, it is)
   112  	switch value := expr.(type) {
   113  	case *hclsyntax.LiteralValueExpr:
   114  		return ctyjson.SimpleJSONValue{Value: value.Val}, nil
   115  	case *hclsyntax.TemplateExpr:
   116  		return c.convertTemplate(value)
   117  	case *hclsyntax.TemplateWrapExpr:
   118  		return c.convertExpression(value.Wrapped)
   119  	case *hclsyntax.TupleConsExpr:
   120  		var list []interface{}
   121  		for _, ex := range value.Exprs {
   122  			elem, err := c.convertExpression(ex)
   123  			if err != nil {
   124  				return nil, err
   125  			}
   126  			list = append(list, elem)
   127  		}
   128  		return list, nil
   129  	case *hclsyntax.ObjectConsExpr:
   130  		m := make(jsonObj)
   131  		for _, item := range value.Items {
   132  			key, err := c.convertKey(item.KeyExpr)
   133  			if err != nil {
   134  				return nil, err
   135  			}
   136  			m[key], err = c.convertExpression(item.ValueExpr)
   137  			if err != nil {
   138  				return nil, err
   139  			}
   140  		}
   141  		return m, nil
   142  	default:
   143  		return c.wrapExpr(expr), nil
   144  	}
   145  }
   146  
   147  func (c *converter) convertKey(keyExpr hclsyntax.Expression) (string, error) {
   148  	// a key should never have dynamic input
   149  	if k, isKeyExpr := keyExpr.(*hclsyntax.ObjectConsKeyExpr); isKeyExpr {
   150  		keyExpr = k.Wrapped
   151  		if _, isTraversal := keyExpr.(*hclsyntax.ScopeTraversalExpr); isTraversal {
   152  			return c.rangeSource(keyExpr.Range()), nil
   153  		}
   154  	}
   155  	return c.convertStringPart(keyExpr)
   156  }
   157  
   158  func (c *converter) convertTemplate(t *hclsyntax.TemplateExpr) (string, error) {
   159  	if t.IsStringLiteral() {
   160  		// safe because the value is just the string
   161  		v, err := t.Value(nil)
   162  		if err != nil {
   163  			return "", err
   164  		}
   165  		return v.AsString(), nil
   166  	}
   167  	var builder strings.Builder
   168  	for _, part := range t.Parts {
   169  		s, err := c.convertStringPart(part)
   170  		if err != nil {
   171  			return "", err
   172  		}
   173  		builder.WriteString(s)
   174  	}
   175  	return builder.String(), nil
   176  }
   177  
   178  func (c *converter) convertStringPart(expr hclsyntax.Expression) (string, error) {
   179  	switch v := expr.(type) {
   180  	case *hclsyntax.LiteralValueExpr:
   181  		s, err := ctyconvert.Convert(v.Val, cty.String)
   182  		if err != nil {
   183  			return "", err
   184  		}
   185  		return s.AsString(), nil
   186  	case *hclsyntax.TemplateExpr:
   187  		return c.convertTemplate(v)
   188  	case *hclsyntax.TemplateWrapExpr:
   189  		return c.convertStringPart(v.Wrapped)
   190  	case *hclsyntax.ConditionalExpr:
   191  		return c.convertTemplateConditional(v)
   192  	case *hclsyntax.TemplateJoinExpr:
   193  		return c.convertTemplateFor(v.Tuple.(*hclsyntax.ForExpr))
   194  	default:
   195  		// treating as an embedded expression
   196  		return c.wrapExpr(expr), nil
   197  	}
   198  }
   199  
   200  func (c *converter) convertTemplateConditional(expr *hclsyntax.ConditionalExpr) (string, error) {
   201  	var builder strings.Builder
   202  	builder.WriteString("%{if ")
   203  	builder.WriteString(c.rangeSource(expr.Condition.Range()))
   204  	builder.WriteString("}")
   205  	trueResult, err := c.convertStringPart(expr.TrueResult)
   206  	if err != nil {
   207  		//nolint:nilerr // expected behavior
   208  		return "", nil
   209  	}
   210  	builder.WriteString(trueResult)
   211  	falseResult, err := c.convertStringPart(expr.FalseResult)
   212  	if err != nil {
   213  		//nolint:nilerr // expected behavior
   214  		return "", nil
   215  	}
   216  	if len(falseResult) > 0 {
   217  		builder.WriteString("%{else}")
   218  		builder.WriteString(falseResult)
   219  	}
   220  	builder.WriteString("%{endif}")
   221  
   222  	return builder.String(), nil
   223  }
   224  
   225  func (c *converter) convertTemplateFor(expr *hclsyntax.ForExpr) (string, error) {
   226  	var builder strings.Builder
   227  	builder.WriteString("%{for ")
   228  	if len(expr.KeyVar) > 0 {
   229  		builder.WriteString(expr.KeyVar)
   230  		builder.WriteString(", ")
   231  	}
   232  	builder.WriteString(expr.ValVar)
   233  	builder.WriteString(" in ")
   234  	builder.WriteString(c.rangeSource(expr.CollExpr.Range()))
   235  	builder.WriteString("}")
   236  	templ, err := c.convertStringPart(expr.ValExpr)
   237  	if err != nil {
   238  		return "", err
   239  	}
   240  	builder.WriteString(templ)
   241  	builder.WriteString("%{endfor}")
   242  
   243  	return builder.String(), nil
   244  }
   245  
   246  func (c *converter) wrapExpr(expr hclsyntax.Expression) string {
   247  	return "${" + c.rangeSource(expr.Range()) + "}"
   248  }