github.com/hashicorp/packer@v1.14.3/hcl2template/types.variables.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package hcl2template
     5  
     6  import (
     7  	"fmt"
     8  	"log"
     9  	"strings"
    10  	"unicode"
    11  
    12  	"github.com/hashicorp/hcl/v2"
    13  	"github.com/hashicorp/hcl/v2/ext/typeexpr"
    14  	"github.com/hashicorp/hcl/v2/gohcl"
    15  	"github.com/hashicorp/hcl/v2/hclsyntax"
    16  	"github.com/hashicorp/packer/hcl2template/addrs"
    17  	"github.com/zclconf/go-cty/cty"
    18  	"github.com/zclconf/go-cty/cty/convert"
    19  )
    20  
    21  // A consistent detail message for all "not a valid identifier" diagnostics.
    22  const badIdentifierDetail = "A name must start with a letter or underscore and may contain only letters, digits, underscores, and dashes."
    23  
    24  // Local represents a single entry from a "locals" block in a file.
    25  // The "locals" block itself is not represented, because it serves only to
    26  // provide context for us to interpret its contents.
    27  type LocalBlock struct {
    28  	LocalName string
    29  	Expr      hcl.Expression
    30  	// When Sensitive is set to true Packer will try its best to hide/obfuscate
    31  	// the variable from the output stream. By replacing the text.
    32  	Sensitive bool
    33  
    34  	// dependsOn lists the dependencies for being able to evaluate this local
    35  	//
    36  	// Only `local`/`locals` will be referenced here as we execute all the
    37  	// same component types at once.
    38  	dependencies []refString
    39  	// evaluated toggles to true if it has been evaluated.
    40  	//
    41  	// We use this to determine if we're ready to get the value of the
    42  	// expression.
    43  	evaluated bool
    44  }
    45  
    46  func (l LocalBlock) Name() string {
    47  	return fmt.Sprintf("local.%s", l.LocalName)
    48  }
    49  
    50  // VariableAssignment represents a way a variable was set: the expression
    51  // setting it and the value of that expression. It helps pinpoint were
    52  // something was set in diagnostics.
    53  type VariableAssignment struct {
    54  	// From tells were it was taken from, command/varfile/env/default
    55  	From  string
    56  	Value cty.Value
    57  	Expr  hcl.Expression
    58  }
    59  
    60  type Variable struct {
    61  	// Values contains possible values for the variable; The last value set
    62  	// from these will be the one used. If none is set; an error will be
    63  	// returned by Value().
    64  	Values []VariableAssignment
    65  
    66  	// Validations contains all variables validation rules to be applied to the
    67  	// used value. Only the used value - the last value from Values - is
    68  	// validated.
    69  	Validations []*VariableValidation
    70  
    71  	// Cty Type of the variable. If the default value or a collected value is
    72  	// not of this type nor can be converted to this type an error diagnostic
    73  	// will show up. This allows us to assume that values are valid later in
    74  	// code.
    75  	//
    76  	// When a default value - and no type - is passed in the variable
    77  	// declaration, the type of the default variable will be used. This will
    78  	// allow to ensure that users set this variable correctly.
    79  	Type cty.Type
    80  	// Common name of the variable
    81  	Name string
    82  	// Description of the variable
    83  	Description string
    84  	// When Sensitive is set to true Packer will try it best to hide/obfuscate
    85  	// the variable from the output stream. By replacing the text.
    86  	Sensitive bool
    87  
    88  	Range hcl.Range
    89  }
    90  
    91  func (v *Variable) GoString() string {
    92  	b := &strings.Builder{}
    93  	fmt.Fprintf(b, "{type:%s", v.Type.GoString())
    94  	for _, vv := range v.Values {
    95  		fmt.Fprintf(b, ",%s:%s", vv.From, vv.Value)
    96  	}
    97  	fmt.Fprintf(b, "}")
    98  	return b.String()
    99  }
   100  
   101  // validateValue ensures that all of the configured custom validations for a
   102  // variable value are passing.
   103  func (v *Variable) validateValue(val VariableAssignment) (diags hcl.Diagnostics) {
   104  	if len(v.Validations) == 0 {
   105  		log.Printf("[TRACE] validateValue: not active for %s, so skipping", v.Name)
   106  		return nil
   107  	}
   108  
   109  	hclCtx := &hcl.EvalContext{
   110  		Variables: map[string]cty.Value{
   111  			"var": cty.ObjectVal(map[string]cty.Value{
   112  				v.Name: val.Value,
   113  			}),
   114  		},
   115  		Functions: Functions(""),
   116  	}
   117  
   118  	for _, validation := range v.Validations {
   119  		const errInvalidCondition = "Invalid variable validation result"
   120  
   121  		result, moreDiags := validation.Condition.Value(hclCtx)
   122  		diags = append(diags, moreDiags...)
   123  		if moreDiags.HasErrors() {
   124  			log.Printf("[TRACE] evalVariableValidations: %s rule %s condition expression failed: %s", v.Name, validation.DeclRange, moreDiags.Error())
   125  		}
   126  		if !result.IsKnown() {
   127  			log.Printf("[TRACE] evalVariableValidations: %s rule %s condition value is unknown, so skipping validation for now", v.Name, validation.DeclRange)
   128  			continue // We'll wait until we've learned more, then.
   129  		}
   130  		if result.IsNull() {
   131  			diags = append(diags, &hcl.Diagnostic{
   132  				Severity:    hcl.DiagError,
   133  				Summary:     errInvalidCondition,
   134  				Detail:      "Validation condition expression must return either true or false, not null.",
   135  				Subject:     validation.Condition.Range().Ptr(),
   136  				Expression:  validation.Condition,
   137  				EvalContext: hclCtx,
   138  			})
   139  			continue
   140  		}
   141  		var err error
   142  		result, err = convert.Convert(result, cty.Bool)
   143  		if err != nil {
   144  			diags = append(diags, &hcl.Diagnostic{
   145  				Severity:    hcl.DiagError,
   146  				Summary:     errInvalidCondition,
   147  				Detail:      fmt.Sprintf("Invalid validation condition result value: %s.", err),
   148  				Subject:     validation.Condition.Range().Ptr(),
   149  				Expression:  validation.Condition,
   150  				EvalContext: hclCtx,
   151  			})
   152  			continue
   153  		}
   154  
   155  		if result.False() {
   156  			subj := validation.DeclRange.Ptr()
   157  			if val.Expr != nil {
   158  				subj = val.Expr.Range().Ptr()
   159  			}
   160  			diags = append(diags, &hcl.Diagnostic{
   161  				Severity: hcl.DiagError,
   162  				Summary:  fmt.Sprintf("Invalid value for %s variable", val.From),
   163  				Detail:   fmt.Sprintf("%s\n\nThis was checked by the validation rule at %s.", validation.ErrorMessage, validation.DeclRange.String()),
   164  				Subject:  subj,
   165  			})
   166  		}
   167  	}
   168  
   169  	return diags
   170  }
   171  
   172  // Value returns the last found value from the list of variable settings.
   173  func (v *Variable) Value() cty.Value {
   174  	if len(v.Values) == 0 {
   175  		return cty.UnknownVal(v.Type)
   176  	}
   177  	val := v.Values[len(v.Values)-1]
   178  	return val.Value
   179  }
   180  
   181  // ValidateValue tells if the selected value for the Variable is valid according
   182  // to its validation settings.
   183  func (v *Variable) ValidateValue() hcl.Diagnostics {
   184  	if len(v.Values) == 0 {
   185  		return hcl.Diagnostics{&hcl.Diagnostic{
   186  			Severity: hcl.DiagError,
   187  			Summary:  fmt.Sprintf("Unset variable %q", v.Name),
   188  			Detail: "A used variable must be set or have a default value; see " +
   189  				"https://packer.io/docs/templates/hcl_templates/syntax for " +
   190  				"details.",
   191  			Context: v.Range.Ptr(),
   192  		}}
   193  	}
   194  
   195  	return v.validateValue(v.Values[len(v.Values)-1])
   196  }
   197  
   198  type Variables map[string]*Variable
   199  
   200  func (variables Variables) Keys() []string {
   201  	keys := make([]string, 0, len(variables))
   202  	for key := range variables {
   203  		keys = append(keys, key)
   204  	}
   205  	return keys
   206  }
   207  
   208  func (variables Variables) Values() map[string]cty.Value {
   209  	res := map[string]cty.Value{}
   210  	for k, v := range variables {
   211  		value := v.Value()
   212  		res[k] = value
   213  	}
   214  	return res
   215  }
   216  
   217  func (variables Variables) ValidateValues() hcl.Diagnostics {
   218  	var diags hcl.Diagnostics
   219  	for _, v := range variables {
   220  		diags = append(diags, v.ValidateValue()...)
   221  	}
   222  	return diags
   223  }
   224  
   225  // decodeVariable decodes a variable key and value into Variables
   226  func (variables *Variables) decodeVariable(key string, attr *hcl.Attribute, ectx *hcl.EvalContext) hcl.Diagnostics {
   227  	var diags hcl.Diagnostics
   228  
   229  	if (*variables) == nil {
   230  		(*variables) = Variables{}
   231  	}
   232  
   233  	if _, found := (*variables)[key]; found {
   234  		diags = append(diags, &hcl.Diagnostic{
   235  			Severity: hcl.DiagError,
   236  			Summary:  "Duplicate variable",
   237  			Detail:   "Duplicate " + key + " variable definition found.",
   238  			Subject:  attr.NameRange.Ptr(),
   239  		})
   240  		return diags
   241  	}
   242  
   243  	value, moreDiags := attr.Expr.Value(ectx)
   244  	diags = append(diags, moreDiags...)
   245  	if moreDiags.HasErrors() {
   246  		return diags
   247  	}
   248  
   249  	(*variables)[key] = &Variable{
   250  		Name: key,
   251  		Values: []VariableAssignment{{
   252  			From:  "default",
   253  			Value: value,
   254  			Expr:  attr.Expr,
   255  		}},
   256  		Type:  value.Type(),
   257  		Range: attr.Range,
   258  	}
   259  
   260  	return diags
   261  }
   262  
   263  var variableBlockSchema = &hcl.BodySchema{
   264  	Attributes: []hcl.AttributeSchema{
   265  		{
   266  			Name: "description",
   267  		},
   268  		{
   269  			Name: "default",
   270  		},
   271  		{
   272  			Name: "type",
   273  		},
   274  		{
   275  			Name: "sensitive",
   276  		},
   277  	},
   278  	Blocks: []hcl.BlockHeaderSchema{
   279  		{
   280  			Type: "validation",
   281  		},
   282  	},
   283  }
   284  
   285  var localBlockSchema = &hcl.BodySchema{
   286  	Attributes: []hcl.AttributeSchema{
   287  		{
   288  			Name: "expression",
   289  		},
   290  		{
   291  			Name: "sensitive",
   292  		},
   293  	},
   294  }
   295  
   296  func decodeLocalBlock(block *hcl.Block) (*LocalBlock, hcl.Diagnostics) {
   297  	name := block.Labels[0]
   298  
   299  	content, diags := block.Body.Content(localBlockSchema)
   300  	if !hclsyntax.ValidIdentifier(name) {
   301  		diags = append(diags, &hcl.Diagnostic{
   302  			Severity: hcl.DiagError,
   303  			Summary:  "Invalid local name",
   304  			Detail:   badIdentifierDetail,
   305  			Subject:  &block.LabelRanges[0],
   306  		})
   307  	}
   308  
   309  	l := &LocalBlock{
   310  		LocalName: name,
   311  	}
   312  
   313  	if attr, exists := content.Attributes["sensitive"]; exists {
   314  		valDiags := gohcl.DecodeExpression(attr.Expr, nil, &l.Sensitive)
   315  		diags = append(diags, valDiags...)
   316  	}
   317  
   318  	if def, ok := content.Attributes["expression"]; ok {
   319  		l.Expr = def.Expr
   320  	}
   321  
   322  	return l, diags
   323  }
   324  
   325  // decodeVariableBlock decodes a "variable" block
   326  // ectx is passed only in the evaluation of the default value.
   327  func (variables *Variables) decodeVariableBlock(block *hcl.Block, ectx *hcl.EvalContext) hcl.Diagnostics {
   328  	if (*variables) == nil {
   329  		(*variables) = Variables{}
   330  	}
   331  
   332  	if _, found := (*variables)[block.Labels[0]]; found {
   333  		return []*hcl.Diagnostic{{
   334  			Severity: hcl.DiagError,
   335  			Summary:  "Duplicate variable",
   336  			Detail:   "Duplicate " + block.Labels[0] + " variable definition found.",
   337  			Context:  block.DefRange.Ptr(),
   338  		}}
   339  	}
   340  
   341  	name := block.Labels[0]
   342  
   343  	content, diags := block.Body.Content(variableBlockSchema)
   344  	if !hclsyntax.ValidIdentifier(name) {
   345  		diags = append(diags, &hcl.Diagnostic{
   346  			Severity: hcl.DiagError,
   347  			Summary:  "Invalid variable name",
   348  			Detail:   badIdentifierDetail,
   349  			Subject:  &block.LabelRanges[0],
   350  		})
   351  	}
   352  
   353  	v := &Variable{
   354  		Name:  name,
   355  		Range: block.DefRange,
   356  		Type:  cty.DynamicPseudoType,
   357  	}
   358  
   359  	if attr, exists := content.Attributes["description"]; exists {
   360  		valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Description)
   361  		diags = append(diags, valDiags...)
   362  	}
   363  
   364  	if t, ok := content.Attributes["type"]; ok {
   365  		tp, moreDiags := typeexpr.Type(t.Expr)
   366  		diags = append(diags, moreDiags...)
   367  		if moreDiags.HasErrors() {
   368  			return diags
   369  		}
   370  
   371  		v.Type = tp
   372  	}
   373  
   374  	if attr, exists := content.Attributes["sensitive"]; exists {
   375  		valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Sensitive)
   376  		diags = append(diags, valDiags...)
   377  	}
   378  
   379  	if def, ok := content.Attributes["default"]; ok {
   380  		defaultValue, moreDiags := def.Expr.Value(ectx)
   381  		diags = append(diags, moreDiags...)
   382  		if moreDiags.HasErrors() {
   383  			return diags
   384  		}
   385  
   386  		if v.Type != cty.NilType {
   387  			var err error
   388  			defaultValue, err = convert.Convert(defaultValue, v.Type)
   389  			if err != nil {
   390  				diags = append(diags, &hcl.Diagnostic{
   391  					Severity: hcl.DiagError,
   392  					Summary:  "Invalid default value for variable",
   393  					Detail:   fmt.Sprintf("This default value is not compatible with the variable's type constraint: %s.", err),
   394  					Subject:  def.Expr.Range().Ptr(),
   395  				})
   396  				defaultValue = cty.DynamicVal
   397  			}
   398  		}
   399  
   400  		v.Values = append(v.Values, VariableAssignment{
   401  			From:  "default",
   402  			Value: defaultValue,
   403  			Expr:  def.Expr,
   404  		})
   405  
   406  		// It's possible no type attribute was assigned so lets make sure we
   407  		// have a valid type otherwise there could be issues parsing the value.
   408  		if v.Type == cty.DynamicPseudoType &&
   409  			!defaultValue.Type().Equals(cty.EmptyObject) &&
   410  			!defaultValue.Type().Equals(cty.EmptyTuple) {
   411  			v.Type = defaultValue.Type()
   412  		}
   413  	}
   414  
   415  	for _, block := range content.Blocks {
   416  		switch block.Type {
   417  		case "validation":
   418  			vv, moreDiags := decodeVariableValidationBlock(v.Name, block)
   419  			diags = append(diags, moreDiags...)
   420  			v.Validations = append(v.Validations, vv)
   421  		}
   422  	}
   423  
   424  	(*variables)[name] = v
   425  
   426  	return diags
   427  }
   428  
   429  var variableValidationBlockSchema = &hcl.BodySchema{
   430  	Attributes: []hcl.AttributeSchema{
   431  		{
   432  			Name:     "condition",
   433  			Required: true,
   434  		},
   435  		{
   436  			Name:     "error_message",
   437  			Required: true,
   438  		},
   439  	},
   440  }
   441  
   442  // VariableValidation represents a configuration-defined validation rule
   443  // for a particular input variable, given as a "validation" block inside
   444  // a "variable" block.
   445  type VariableValidation struct {
   446  	// Condition is an expression that refers to the variable being tested and
   447  	// contains no other references. The expression must return true to
   448  	// indicate that the value is valid or false to indicate that it is
   449  	// invalid. If the expression produces an error, that's considered a bug in
   450  	// the block defining the validation rule, not an error in the caller.
   451  	Condition hcl.Expression
   452  
   453  	// ErrorMessage is one or more full sentences, which _should_ be in English
   454  	// for consistency with the rest of the error message output but can in
   455  	// practice be in any language as long as it ends with a period. The
   456  	// message should describe what is required for the condition to return
   457  	// true in a way that would make sense to a caller of the module.
   458  	ErrorMessage string
   459  
   460  	DeclRange hcl.Range
   461  }
   462  
   463  func decodeVariableValidationBlock(varName string, block *hcl.Block) (*VariableValidation, hcl.Diagnostics) {
   464  	var diags hcl.Diagnostics
   465  	vv := &VariableValidation{
   466  		DeclRange: block.DefRange,
   467  	}
   468  
   469  	content, moreDiags := block.Body.Content(variableValidationBlockSchema)
   470  	diags = append(diags, moreDiags...)
   471  
   472  	if attr, exists := content.Attributes["condition"]; exists {
   473  		vv.Condition = attr.Expr
   474  
   475  		// The validation condition must refer to the variable itself and
   476  		// nothing else; to ensure that the variable declaration can't create
   477  		// additional edges in the dependency graph.
   478  		goodRefs := 0
   479  		for _, traversal := range vv.Condition.Variables() {
   480  
   481  			ref, moreDiags := addrs.ParseRef(traversal)
   482  			if !moreDiags.HasErrors() {
   483  				if addr, ok := ref.Subject.(addrs.InputVariable); ok {
   484  					if addr.Name == varName {
   485  						goodRefs++
   486  						continue // Reference is valid
   487  					}
   488  				}
   489  			}
   490  
   491  			// If we fall out here then the reference is invalid.
   492  			diags = diags.Append(&hcl.Diagnostic{
   493  				Severity: hcl.DiagError,
   494  				Summary:  "Invalid reference in variable validation",
   495  				Detail:   fmt.Sprintf("The condition for variable %q can only refer to the variable itself, using var.%s.", varName, varName),
   496  				Subject:  traversal.SourceRange().Ptr(),
   497  			})
   498  		}
   499  		if goodRefs < 1 {
   500  			diags = diags.Append(&hcl.Diagnostic{
   501  				Severity: hcl.DiagError,
   502  				Summary:  "Invalid variable validation condition",
   503  				Detail:   fmt.Sprintf("The condition for variable %q must refer to var.%s in order to test incoming values.", varName, varName),
   504  				Subject:  attr.Expr.Range().Ptr(),
   505  			})
   506  		}
   507  	}
   508  
   509  	if attr, exists := content.Attributes["error_message"]; exists {
   510  		moreDiags := gohcl.DecodeExpression(attr.Expr, nil, &vv.ErrorMessage)
   511  		diags = append(diags, moreDiags...)
   512  		if !moreDiags.HasErrors() {
   513  			const errSummary = "Invalid validation error message"
   514  			switch {
   515  			case vv.ErrorMessage == "":
   516  				diags = diags.Append(&hcl.Diagnostic{
   517  					Severity: hcl.DiagError,
   518  					Summary:  errSummary,
   519  					Detail:   "An empty string is not a valid nor useful error message.",
   520  					Subject:  attr.Expr.Range().Ptr(),
   521  				})
   522  			case !looksLikeSentences(vv.ErrorMessage):
   523  				// Because we're going to include this string verbatim as part
   524  				// of a bigger error message written in our usual style, we'll
   525  				// require the given error message to conform to that. We might
   526  				// relax this in future if e.g. we start presenting these error
   527  				// messages in a different way, or if Packer starts supporting
   528  				// producing error messages in other human languages, etc. For
   529  				// pragmatism we also allow sentences ending with exclamation
   530  				// points, but we don't mention it explicitly here because
   531  				// that's not really consistent with the Packer UI writing
   532  				// style.
   533  				diags = diags.Append(&hcl.Diagnostic{
   534  					Severity: hcl.DiagError,
   535  					Summary:  errSummary,
   536  					Detail:   "Validation error message must be at least one full sentence starting with an uppercase letter ( if the alphabet permits it ) and ending with a period or question mark.",
   537  					Subject:  attr.Expr.Range().Ptr(),
   538  				})
   539  			}
   540  		}
   541  	}
   542  
   543  	return vv, diags
   544  }
   545  
   546  // looksLikeSentence is a simple heuristic that encourages writing error
   547  // messages that will be presentable when included as part of a larger error
   548  // diagnostic whose other text is written in the UI writing style.
   549  //
   550  // This is intentionally not a very strong validation since we're assuming that
   551  // authors want to write good messages and might just need a nudge about
   552  // Packer's specific style, rather than that they are going to try to work
   553  // around these rules to write a lower-quality message.
   554  func looksLikeSentences(s string) bool {
   555  	s = strings.TrimSpace(s)
   556  	if len(s) < 1 {
   557  		return false
   558  	}
   559  	runes := []rune(s) // HCL guarantees that all strings are valid UTF-8
   560  	first := runes[0]
   561  	last := runes[len(runes)-1]
   562  
   563  	// If the first rune is a letter then it must be an uppercase letter. To
   564  	// sort of nudge people into writing sentences. For alphabets that don't
   565  	// have the notion of 'upper', this does nothing.
   566  	if unicode.IsLetter(first) && !unicode.IsUpper(first) {
   567  		return false
   568  	}
   569  
   570  	// The string must be at least one full sentence, which implies having
   571  	// sentence-ending punctuation.
   572  	return last == '.' || last == '?' || last == '!'
   573  }
   574  
   575  // Prefix your environment variables with VarEnvPrefix so that Packer can see
   576  // them.
   577  const VarEnvPrefix = "PKR_VAR_"
   578  
   579  func (cfg *PackerConfig) collectInputVariableValues(env []string, files []*hcl.File, argv map[string]string) hcl.Diagnostics {
   580  	var diags hcl.Diagnostics
   581  	variables := cfg.InputVariables
   582  
   583  	for _, raw := range env {
   584  		if !strings.HasPrefix(raw, VarEnvPrefix) {
   585  			continue
   586  		}
   587  		raw = raw[len(VarEnvPrefix):] // trim the prefix
   588  
   589  		eq := strings.Index(raw, "=")
   590  		if eq == -1 {
   591  			// Seems invalid, so we'll ignore it.
   592  			continue
   593  		}
   594  
   595  		name := raw[:eq]
   596  		value := raw[eq+1:]
   597  
   598  		variable, found := variables[name]
   599  		if !found {
   600  			// this variable was not defined in the hcl files, let's skip it !
   601  			continue
   602  		}
   603  
   604  		fakeFilename := fmt.Sprintf("<value for var.%s from env>", name)
   605  		expr, moreDiags := expressionFromVariableDefinition(fakeFilename, value, variable.Type)
   606  		diags = append(diags, moreDiags...)
   607  		if moreDiags.HasErrors() {
   608  			continue
   609  		}
   610  
   611  		val, valDiags := expr.Value(nil)
   612  		diags = append(diags, valDiags...)
   613  		if variable.Type != cty.NilType {
   614  			var err error
   615  			val, err = convert.Convert(val, variable.Type)
   616  			if err != nil {
   617  				diags = append(diags, &hcl.Diagnostic{
   618  					Severity: hcl.DiagError,
   619  					Summary:  "Invalid value for variable",
   620  					Detail:   fmt.Sprintf("The value for %s is not compatible with the variable's type constraint: %s.", name, err),
   621  					Subject:  expr.Range().Ptr(),
   622  				})
   623  				val = cty.DynamicVal
   624  			}
   625  		}
   626  		variable.Values = append(variable.Values, VariableAssignment{
   627  			From:  "env",
   628  			Value: val,
   629  			Expr:  expr,
   630  		})
   631  	}
   632  
   633  	// files will contain files found in the folder then files passed as
   634  	// arguments.
   635  	for _, file := range files {
   636  		// Before we do our real decode, we'll probe to see if there are any
   637  		// blocks of type "variable" in this body, since it's a common mistake
   638  		// for new users to put variable declarations in pkrvars rather than
   639  		// variable value definitions, and otherwise our error message for that
   640  		// case is not so helpful.
   641  		{
   642  			content, _, _ := file.Body.PartialContent(&hcl.BodySchema{
   643  				Blocks: []hcl.BlockHeaderSchema{
   644  					{
   645  						Type:       "variable",
   646  						LabelNames: []string{"name"},
   647  					},
   648  				},
   649  			})
   650  			for _, block := range content.Blocks {
   651  				name := block.Labels[0]
   652  				diags = append(diags, &hcl.Diagnostic{
   653  					Severity: hcl.DiagError,
   654  					Summary:  "Variable declaration in a .pkrvar file",
   655  					Detail: fmt.Sprintf("A .pkrvar file is used to assign "+
   656  						"values to variables that have already been declared "+
   657  						"in .pkr files, not to declare new variables. To "+
   658  						"declare variable %q, place this block in one of your"+
   659  						" .pkr files, such as variables.pkr.hcl\n\nTo set a "+
   660  						"value for this variable in %s, use the definition "+
   661  						"syntax instead:\n    %s = <value>",
   662  						name, block.TypeRange.Filename, name),
   663  					Subject: &block.TypeRange,
   664  				})
   665  			}
   666  			if diags.HasErrors() {
   667  				// If we already found problems then JustAttributes below will find
   668  				// the same problems with less-helpful messages, so we'll bail for
   669  				// now to let the user focus on the immediate problem.
   670  				return diags
   671  			}
   672  		}
   673  
   674  		attrs, moreDiags := file.Body.JustAttributes()
   675  		diags = append(diags, moreDiags...)
   676  
   677  		for name, attr := range attrs {
   678  			variable, found := variables[name]
   679  			if !found {
   680  				if !cfg.ValidationOptions.WarnOnUndeclaredVar {
   681  					continue
   682  				}
   683  
   684  				diags = append(diags, &hcl.Diagnostic{
   685  					Severity: hcl.DiagWarning,
   686  					Summary:  "Undefined variable",
   687  					Detail: fmt.Sprintf("The variable %[1]q was set but was not declared as an input variable."+
   688  						"\nTo declare variable %[1]q place this block in one of your .pkr.hcl files, "+
   689  						"such as variables.pkr.hcl\n\n"+
   690  						"variable %[1]q {\n"+
   691  						"  type    = string\n"+
   692  						"  default = null\n"+
   693  						"}",
   694  						name),
   695  					Context: attr.Range.Ptr(),
   696  				})
   697  				continue
   698  			}
   699  
   700  			val, moreDiags := attr.Expr.Value(nil)
   701  			diags = append(diags, moreDiags...)
   702  
   703  			if variable.Type != cty.NilType {
   704  				var err error
   705  				val, err = convert.Convert(val, variable.Type)
   706  				if err != nil {
   707  					diags = append(diags, &hcl.Diagnostic{
   708  						Severity: hcl.DiagError,
   709  						Summary:  "Invalid value for variable",
   710  						Detail:   fmt.Sprintf("The value for %s is not compatible with the variable's type constraint: %s.", name, err),
   711  						Subject:  attr.Expr.Range().Ptr(),
   712  					})
   713  					val = cty.DynamicVal
   714  				}
   715  			}
   716  
   717  			variable.Values = append(variable.Values, VariableAssignment{
   718  				From:  "varfile",
   719  				Value: val,
   720  				Expr:  attr.Expr,
   721  			})
   722  		}
   723  	}
   724  
   725  	// Finally we process values given explicitly on the command line.
   726  	for name, value := range argv {
   727  		variable, found := variables[name]
   728  		if !found {
   729  			diags = append(diags, &hcl.Diagnostic{
   730  				Severity: hcl.DiagError,
   731  				Summary:  "Undefined -var variable",
   732  				Detail: fmt.Sprintf("A %q variable was passed in the command "+
   733  					"line but was not found in known variables. "+
   734  					"To declare variable %q, place this block in one of your"+
   735  					" .pkr files, such as variables.pkr.hcl",
   736  					name, name),
   737  			})
   738  			continue
   739  		}
   740  
   741  		fakeFilename := fmt.Sprintf("<value for var.%s from arguments>", name)
   742  		expr, moreDiags := expressionFromVariableDefinition(fakeFilename, value, variable.Type)
   743  		diags = append(diags, moreDiags...)
   744  		if moreDiags.HasErrors() {
   745  			continue
   746  		}
   747  
   748  		val, valDiags := expr.Value(nil)
   749  		diags = append(diags, valDiags...)
   750  
   751  		if variable.Type != cty.NilType {
   752  			var err error
   753  			val, err = convert.Convert(val, variable.Type)
   754  			if err != nil {
   755  				diags = append(diags, &hcl.Diagnostic{
   756  					Severity: hcl.DiagError,
   757  					Summary:  "Invalid argument value for -var variable",
   758  					Detail:   fmt.Sprintf("The received arg value for %s is not compatible with the variable's type constraint: %s.", name, err),
   759  				})
   760  				val = cty.DynamicVal
   761  			}
   762  		}
   763  
   764  		variable.Values = append(variable.Values, VariableAssignment{
   765  			From:  "cmd",
   766  			Value: val,
   767  			Expr:  expr,
   768  		})
   769  	}
   770  
   771  	return diags
   772  }
   773  
   774  // expressionFromVariableDefinition creates an hclsyntax.Expression that is capable of evaluating the specified value for a given cty.Type.
   775  // The specified filename is to identify the source of where value originated from in the diagnostics report, if there is an error.
   776  func expressionFromVariableDefinition(filename string, value string, variableType cty.Type) (hclsyntax.Expression, hcl.Diagnostics) {
   777  	switch variableType {
   778  	case cty.String, cty.Number, cty.NilType, cty.DynamicPseudoType:
   779  		// when the type is nil (not set in a variable block) we default to
   780  		// interpreting everything as a string literal.
   781  		return &hclsyntax.LiteralValueExpr{Val: cty.StringVal(value)}, nil
   782  	default:
   783  		return hclsyntax.ParseExpression([]byte(value), filename, hcl.Pos{Line: 1, Column: 1})
   784  	}
   785  }