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, &paramWrapper)
   158  	if diags.HasErrors() {
   159  		return "", diags
   160  	}
   161  
   162  	return paramWrapper.Parameter, nil
   163  }