github.com/hashicorp/packer@v1.14.3/hcl2template/utils.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package hcl2template 5 6 import ( 7 "fmt" 8 "os" 9 "path/filepath" 10 "strings" 11 12 "github.com/gobwas/glob" 13 "github.com/hashicorp/hcl/v2" 14 "github.com/hashicorp/hcl/v2/hclsyntax" 15 "github.com/hashicorp/packer/hcl2template/repl" 16 hcl2shim "github.com/hashicorp/packer/hcl2template/shim" 17 "github.com/zclconf/go-cty/cty" 18 ) 19 20 func warningErrorsToDiags(block *hcl.Block, warnings []string, err error) hcl.Diagnostics { 21 var diags hcl.Diagnostics 22 23 for _, warning := range warnings { 24 diags = append(diags, &hcl.Diagnostic{ 25 Summary: warning, 26 Subject: &block.DefRange, 27 Severity: hcl.DiagWarning, 28 }) 29 } 30 if err != nil { 31 diags = append(diags, &hcl.Diagnostic{ 32 Summary: err.Error(), 33 Subject: &block.DefRange, 34 Severity: hcl.DiagError, 35 }) 36 } 37 return diags 38 } 39 40 func isDir(name string) (bool, error) { 41 s, err := os.Stat(name) 42 if err != nil { 43 return false, err 44 } 45 return s.IsDir(), nil 46 } 47 48 // GetHCL2Files returns two slices of json formatted and hcl formatted files, 49 // hclSuffix and jsonSuffix tell which file is what. Filename can be a folder 50 // or a file. 51 // 52 // When filename is a folder all files of folder matching the suffixes will be 53 // returned. Otherwise if filename references a file and filename matches one 54 // of the suffixes it is returned in the according slice. 55 func GetHCL2Files(filename, hclSuffix, jsonSuffix string) (hclFiles, jsonFiles []string, diags hcl.Diagnostics) { 56 if filename == "" { 57 return 58 } 59 isDir, err := isDir(filename) 60 if err != nil { 61 diags = append(diags, &hcl.Diagnostic{ 62 Severity: hcl.DiagError, 63 Detail: err.Error(), 64 }) 65 return nil, nil, diags 66 } 67 if !isDir { 68 if strings.HasSuffix(filename, jsonSuffix) { 69 return nil, []string{filename}, diags 70 } 71 if strings.HasSuffix(filename, hclSuffix) { 72 return []string{filename}, nil, diags 73 } 74 return nil, nil, diags 75 } 76 77 fileInfos, err := os.ReadDir(filename) 78 if err != nil { 79 diag := &hcl.Diagnostic{ 80 Severity: hcl.DiagError, 81 Summary: "Cannot read hcl directory", 82 Detail: err.Error(), 83 } 84 diags = append(diags, diag) 85 return nil, nil, diags 86 } 87 for _, fileInfo := range fileInfos { 88 if fileInfo.IsDir() { 89 continue 90 } 91 filename := filepath.Join(filename, fileInfo.Name()) 92 if strings.HasSuffix(filename, hclSuffix) { 93 hclFiles = append(hclFiles, filename) 94 } else if strings.HasSuffix(filename, jsonSuffix) { 95 jsonFiles = append(jsonFiles, filename) 96 } 97 } 98 99 return hclFiles, jsonFiles, diags 100 } 101 102 // Convert -only and -except globs to glob.Glob instances. 103 func convertFilterOption(patterns []string, optionName string) ([]glob.Glob, hcl.Diagnostics) { 104 var globs []glob.Glob 105 var diags hcl.Diagnostics 106 107 for _, pattern := range patterns { 108 g, err := glob.Compile(pattern) 109 if err != nil { 110 diags = append(diags, &hcl.Diagnostic{ 111 Summary: fmt.Sprintf("Invalid -%s pattern %s: %s", optionName, pattern, err), 112 Severity: hcl.DiagError, 113 }) 114 } 115 globs = append(globs, g) 116 } 117 118 return globs, diags 119 } 120 121 func PrintableCtyValue(v cty.Value) string { 122 if !v.IsWhollyKnown() { 123 return "<unknown>" 124 } 125 gval := hcl2shim.ConfigValueFromHCL2(v) 126 str := repl.FormatResult(gval) 127 return str 128 } 129 130 func ConvertPluginConfigValueToHCLValue(v interface{}) (cty.Value, error) { 131 var buildValue cty.Value 132 switch v := v.(type) { 133 case bool: 134 buildValue = cty.BoolVal(v) 135 case string: 136 buildValue = cty.StringVal(v) 137 case uint8: 138 buildValue = cty.NumberUIntVal(uint64(v)) 139 case float64: 140 buildValue = cty.NumberFloatVal(v) 141 case int64: 142 buildValue = cty.NumberIntVal(v) 143 case uint64: 144 buildValue = cty.NumberUIntVal(v) 145 case []string: 146 vals := make([]cty.Value, len(v)) 147 for i, ev := range v { 148 vals[i] = cty.StringVal(ev) 149 } 150 if len(vals) == 0 { 151 buildValue = cty.ListValEmpty(cty.String) 152 } else { 153 buildValue = cty.ListVal(vals) 154 } 155 case []uint8: 156 vals := make([]cty.Value, len(v)) 157 for i, ev := range v { 158 vals[i] = cty.NumberUIntVal(uint64(ev)) 159 } 160 if len(vals) == 0 { 161 buildValue = cty.ListValEmpty(cty.Number) 162 } else { 163 buildValue = cty.ListVal(vals) 164 } 165 case []int64: 166 vals := make([]cty.Value, len(v)) 167 for i, ev := range v { 168 vals[i] = cty.NumberIntVal(ev) 169 } 170 if len(vals) == 0 { 171 buildValue = cty.ListValEmpty(cty.Number) 172 } else { 173 buildValue = cty.ListVal(vals) 174 } 175 case []uint64: 176 vals := make([]cty.Value, len(v)) 177 for i, ev := range v { 178 vals[i] = cty.NumberUIntVal(ev) 179 } 180 if len(vals) == 0 { 181 buildValue = cty.ListValEmpty(cty.Number) 182 } else { 183 buildValue = cty.ListVal(vals) 184 } 185 default: 186 return cty.Value{}, fmt.Errorf("unhandled buildvar type: %T", v) 187 } 188 return buildValue, nil 189 } 190 191 // GetVarsByType walks through a hcl body, and gathers all the Traversals that 192 // have a root type matching one of the specified top-level labels. 193 // 194 // This will only work on finite, expanded, HCL bodies. 195 func GetVarsByType(block *hcl.Block, topLevelLabels ...string) []hcl.Traversal { 196 var travs []hcl.Traversal 197 198 switch body := block.Body.(type) { 199 case *hclsyntax.Body: 200 travs = getVarsByTypeForHCLSyntaxBody(body) 201 default: 202 attrs, _ := body.JustAttributes() 203 for _, attr := range attrs { 204 travs = append(travs, attr.Expr.Variables()...) 205 } 206 } 207 208 return FilterTraversalsByType(travs, topLevelLabels...) 209 } 210 211 // FilterTraversalsByType lets the caller filter the traversals per top-level type. 212 // 213 // This can then be used to detect dependencies between block types. 214 func FilterTraversalsByType(travs []hcl.Traversal, topLevelLabels ...string) []hcl.Traversal { 215 var rets []hcl.Traversal 216 for _, t := range travs { 217 varRootname := t.RootName() 218 for _, lbl := range topLevelLabels { 219 if varRootname == lbl { 220 rets = append(rets, t) 221 break 222 } 223 } 224 } 225 226 return rets 227 } 228 229 func getVarsByTypeForHCLSyntaxBody(body *hclsyntax.Body) []hcl.Traversal { 230 var rets []hcl.Traversal 231 232 for _, attr := range body.Attributes { 233 rets = append(rets, attr.Expr.Variables()...) 234 } 235 236 for _, block := range body.Blocks { 237 rets = append(rets, getVarsByTypeForHCLSyntaxBody(block.Body)...) 238 } 239 240 return rets 241 }