github.com/crossplane/upjet@v1.3.0/pkg/controller/hcl.go (about) 1 // SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io> 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package controller 6 7 import ( 8 "encoding/base64" 9 "fmt" 10 "log" 11 "regexp" 12 "unicode/utf8" 13 14 "github.com/hashicorp/hcl/v2" 15 "github.com/hashicorp/hcl/v2/gohcl" 16 "github.com/hashicorp/hcl/v2/hclparse" 17 ctyyaml "github.com/zclconf/go-cty-yaml" 18 "github.com/zclconf/go-cty/cty" 19 "github.com/zclconf/go-cty/cty/function" 20 ctyfuncstdlib "github.com/zclconf/go-cty/cty/function/stdlib" 21 ) 22 23 var Base64DecodeFunc = function.New(&function.Spec{ 24 Params: []function.Parameter{ 25 { 26 Name: "str", 27 Type: cty.String, 28 AllowMarked: true, 29 }, 30 }, 31 Type: function.StaticReturnType(cty.String), 32 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 33 str, strMarks := args[0].Unmark() 34 s := str.AsString() 35 sDec, err := base64.StdEncoding.DecodeString(s) 36 if err != nil { 37 return cty.UnknownVal(cty.String), fmt.Errorf("failed to decode base64 data %s", s) 38 } 39 if !utf8.Valid(sDec) { 40 log.Printf("[DEBUG] the result of decoding the provided string is not valid UTF-8: %s", s) 41 return cty.UnknownVal(cty.String), fmt.Errorf("the result of decoding the provided string is not valid UTF-8") 42 } 43 return cty.StringVal(string(sDec)).WithMarks(strMarks), nil 44 }, 45 }) 46 47 var Base64EncodeFunc = function.New(&function.Spec{ 48 Params: []function.Parameter{ 49 { 50 Name: "str", 51 Type: cty.String, 52 }, 53 }, 54 Type: function.StaticReturnType(cty.String), 55 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 56 return cty.StringVal(base64.StdEncoding.EncodeToString([]byte(args[0].AsString()))), nil 57 }, 58 }) 59 60 // evalCtx registers the known functions for HCL processing 61 // variable interpolation is not supported, as in our case they are irrelevant 62 var evalCtx = &hcl.EvalContext{ 63 Variables: map[string]cty.Value{}, 64 Functions: map[string]function.Function{ 65 "abs": ctyfuncstdlib.AbsoluteFunc, 66 "ceil": ctyfuncstdlib.CeilFunc, 67 "chomp": ctyfuncstdlib.ChompFunc, 68 "coalescelist": ctyfuncstdlib.CoalesceListFunc, 69 "compact": ctyfuncstdlib.CompactFunc, 70 "concat": ctyfuncstdlib.ConcatFunc, 71 "contains": ctyfuncstdlib.ContainsFunc, 72 "csvdecode": ctyfuncstdlib.CSVDecodeFunc, 73 "distinct": ctyfuncstdlib.DistinctFunc, 74 "element": ctyfuncstdlib.ElementFunc, 75 "chunklist": ctyfuncstdlib.ChunklistFunc, 76 "flatten": ctyfuncstdlib.FlattenFunc, 77 "floor": ctyfuncstdlib.FloorFunc, 78 "format": ctyfuncstdlib.FormatFunc, 79 "formatdate": ctyfuncstdlib.FormatDateFunc, 80 "formatlist": ctyfuncstdlib.FormatListFunc, 81 "indent": ctyfuncstdlib.IndentFunc, 82 "join": ctyfuncstdlib.JoinFunc, 83 "jsondecode": ctyfuncstdlib.JSONDecodeFunc, 84 "jsonencode": ctyfuncstdlib.JSONEncodeFunc, 85 "keys": ctyfuncstdlib.KeysFunc, 86 "log": ctyfuncstdlib.LogFunc, 87 "lower": ctyfuncstdlib.LowerFunc, 88 "max": ctyfuncstdlib.MaxFunc, 89 "merge": ctyfuncstdlib.MergeFunc, 90 "min": ctyfuncstdlib.MinFunc, 91 "parseint": ctyfuncstdlib.ParseIntFunc, 92 "pow": ctyfuncstdlib.PowFunc, 93 "range": ctyfuncstdlib.RangeFunc, 94 "regex": ctyfuncstdlib.RegexFunc, 95 "regexall": ctyfuncstdlib.RegexAllFunc, 96 "reverse": ctyfuncstdlib.ReverseListFunc, 97 "setintersection": ctyfuncstdlib.SetIntersectionFunc, 98 "setproduct": ctyfuncstdlib.SetProductFunc, 99 "setsubtract": ctyfuncstdlib.SetSubtractFunc, 100 "setunion": ctyfuncstdlib.SetUnionFunc, 101 "signum": ctyfuncstdlib.SignumFunc, 102 "slice": ctyfuncstdlib.SliceFunc, 103 "sort": ctyfuncstdlib.SortFunc, 104 "split": ctyfuncstdlib.SplitFunc, 105 "strrev": ctyfuncstdlib.ReverseFunc, 106 "substr": ctyfuncstdlib.SubstrFunc, 107 "timeadd": ctyfuncstdlib.TimeAddFunc, 108 "title": ctyfuncstdlib.TitleFunc, 109 "trim": ctyfuncstdlib.TrimFunc, 110 "trimprefix": ctyfuncstdlib.TrimPrefixFunc, 111 "trimspace": ctyfuncstdlib.TrimSpaceFunc, 112 "trimsuffix": ctyfuncstdlib.TrimSuffixFunc, 113 "upper": ctyfuncstdlib.UpperFunc, 114 "values": ctyfuncstdlib.ValuesFunc, 115 "zipmap": ctyfuncstdlib.ZipmapFunc, 116 "yamldecode": ctyyaml.YAMLDecodeFunc, 117 "yamlencode": ctyyaml.YAMLEncodeFunc, 118 "base64encode": Base64EncodeFunc, 119 "base64decode": Base64DecodeFunc, 120 }, 121 } 122 123 // hclBlock is the target type for decoding the specially-crafted HCL document. 124 // interested in processing HCL snippets for a single parameter 125 type hclBlock struct { 126 Parameter string `hcl:"parameter"` 127 } 128 129 // isHCLSnippetPattern is the regex pattern for determining whether 130 // the param is an HCL template 131 var isHCLSnippetPattern = regexp.MustCompile(`\$\{\w+\s*\([\S\s]*\}`) 132 133 // processHCLParam processes the given string parameter 134 // with HCL format and including HCL functions, 135 // coming from the Managed Resource spec parameters. 136 // It prepares a tailored HCL snippet which consist of only a single attribute 137 // parameter = theGivenParameterValueInHCLSyntax 138 // It only operates on string parameters, and returns a string. 139 // caller should ensure that the given parameter is an HCL snippet 140 func processHCLParam(param string) (string, error) { 141 param = fmt.Sprintf("parameter = \"%s\"\n", param) 142 return processHCLParamBytes([]byte(param)) 143 } 144 145 // processHCLParamBytes parses and decodes the HCL snippet 146 func processHCLParamBytes(paramValueBytes []byte) (string, error) { 147 hclParser := hclparse.NewParser() 148 // here the filename argument is not important, 149 // used by the hcl parser lib for tracking caching purposes 150 // it is just a name reference 151 hclFile, diag := hclParser.ParseHCL(paramValueBytes, "dummy.hcl") 152 if diag.HasErrors() { 153 return "", diag 154 } 155 156 var paramWrapper hclBlock 157 diags := gohcl.DecodeBody(hclFile.Body, evalCtx, ¶mWrapper) 158 if diags.HasErrors() { 159 return "", diags 160 } 161 162 return paramWrapper.Parameter, nil 163 }