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  }