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 }