github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/jobspec2/types.variables.go (about)

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