github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/configs/named_values.go (about)

     1  package configs
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/hcl/v2"
     7  	"github.com/hashicorp/hcl/v2/ext/typeexpr"
     8  	"github.com/hashicorp/hcl/v2/gohcl"
     9  	"github.com/hashicorp/hcl/v2/hclsyntax"
    10  	"github.com/zclconf/go-cty/cty"
    11  	"github.com/zclconf/go-cty/cty/convert"
    12  
    13  	"github.com/hashicorp/terraform/internal/addrs"
    14  )
    15  
    16  // A consistent detail message for all "not a valid identifier" diagnostics.
    17  const badIdentifierDetail = "A name must start with a letter or underscore and may contain only letters, digits, underscores, and dashes."
    18  
    19  // Variable represents a "variable" block in a module or file.
    20  type Variable struct {
    21  	Name        string
    22  	Description string
    23  	Default     cty.Value
    24  
    25  	// Type is the concrete type of the variable value.
    26  	Type cty.Type
    27  	// ConstraintType is used for decoding and type conversions, and may
    28  	// contain nested ObjectWithOptionalAttr types.
    29  	ConstraintType cty.Type
    30  	TypeDefaults   *typeexpr.Defaults
    31  
    32  	ParsingMode VariableParsingMode
    33  	Validations []*CheckRule
    34  	Sensitive   bool
    35  
    36  	DescriptionSet bool
    37  	SensitiveSet   bool
    38  
    39  	// Nullable indicates that null is a valid value for this variable. Setting
    40  	// Nullable to false means that the module can expect this variable to
    41  	// never be null.
    42  	Nullable    bool
    43  	NullableSet bool
    44  
    45  	DeclRange hcl.Range
    46  }
    47  
    48  func decodeVariableBlock(block *hcl.Block, override bool) (*Variable, hcl.Diagnostics) {
    49  	v := &Variable{
    50  		Name:      block.Labels[0],
    51  		DeclRange: block.DefRange,
    52  	}
    53  
    54  	// Unless we're building an override, we'll set some defaults
    55  	// which we might override with attributes below. We leave these
    56  	// as zero-value in the override case so we can recognize whether
    57  	// or not they are set when we merge.
    58  	if !override {
    59  		v.Type = cty.DynamicPseudoType
    60  		v.ConstraintType = cty.DynamicPseudoType
    61  		v.ParsingMode = VariableParseLiteral
    62  	}
    63  
    64  	content, diags := block.Body.Content(variableBlockSchema)
    65  
    66  	if !hclsyntax.ValidIdentifier(v.Name) {
    67  		diags = append(diags, &hcl.Diagnostic{
    68  			Severity: hcl.DiagError,
    69  			Summary:  "Invalid variable name",
    70  			Detail:   badIdentifierDetail,
    71  			Subject:  &block.LabelRanges[0],
    72  		})
    73  	}
    74  
    75  	// Don't allow declaration of variables that would conflict with the
    76  	// reserved attribute and block type names in a "module" block, since
    77  	// these won't be usable for child modules.
    78  	for _, attr := range moduleBlockSchema.Attributes {
    79  		if attr.Name == v.Name {
    80  			diags = append(diags, &hcl.Diagnostic{
    81  				Severity: hcl.DiagError,
    82  				Summary:  "Invalid variable name",
    83  				Detail:   fmt.Sprintf("The variable name %q is reserved due to its special meaning inside module blocks.", attr.Name),
    84  				Subject:  &block.LabelRanges[0],
    85  			})
    86  		}
    87  	}
    88  	for _, blockS := range moduleBlockSchema.Blocks {
    89  		if blockS.Type == v.Name {
    90  			diags = append(diags, &hcl.Diagnostic{
    91  				Severity: hcl.DiagError,
    92  				Summary:  "Invalid variable name",
    93  				Detail:   fmt.Sprintf("The variable name %q is reserved due to its special meaning inside module blocks.", blockS.Type),
    94  				Subject:  &block.LabelRanges[0],
    95  			})
    96  		}
    97  	}
    98  
    99  	if attr, exists := content.Attributes["description"]; exists {
   100  		valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Description)
   101  		diags = append(diags, valDiags...)
   102  		v.DescriptionSet = true
   103  	}
   104  
   105  	if attr, exists := content.Attributes["type"]; exists {
   106  		ty, tyDefaults, parseMode, tyDiags := decodeVariableType(attr.Expr)
   107  		diags = append(diags, tyDiags...)
   108  		v.ConstraintType = ty
   109  		v.TypeDefaults = tyDefaults
   110  		v.Type = ty.WithoutOptionalAttributesDeep()
   111  		v.ParsingMode = parseMode
   112  	}
   113  
   114  	if attr, exists := content.Attributes["sensitive"]; exists {
   115  		valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Sensitive)
   116  		diags = append(diags, valDiags...)
   117  		v.SensitiveSet = true
   118  	}
   119  
   120  	if attr, exists := content.Attributes["nullable"]; exists {
   121  		valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Nullable)
   122  		diags = append(diags, valDiags...)
   123  		v.NullableSet = true
   124  	} else {
   125  		// The current default is true, which is subject to change in a future
   126  		// language edition.
   127  		v.Nullable = true
   128  	}
   129  
   130  	if attr, exists := content.Attributes["default"]; exists {
   131  		val, valDiags := attr.Expr.Value(nil)
   132  		diags = append(diags, valDiags...)
   133  
   134  		// Convert the default to the expected type so we can catch invalid
   135  		// defaults early and allow later code to assume validity.
   136  		// Note that this depends on us having already processed any "type"
   137  		// attribute above.
   138  		// However, we can't do this if we're in an override file where
   139  		// the type might not be set; we'll catch that during merge.
   140  		if v.ConstraintType != cty.NilType {
   141  			var err error
   142  			// If the type constraint has defaults, we must apply those
   143  			// defaults to the variable default value before type conversion,
   144  			// unless the default value is null. Null is excluded from the
   145  			// type default application process as a special case, to allow
   146  			// nullable variables to have a null default value.
   147  			if v.TypeDefaults != nil && !val.IsNull() {
   148  				val = v.TypeDefaults.Apply(val)
   149  			}
   150  			val, err = convert.Convert(val, v.ConstraintType)
   151  			if err != nil {
   152  				diags = append(diags, &hcl.Diagnostic{
   153  					Severity: hcl.DiagError,
   154  					Summary:  "Invalid default value for variable",
   155  					Detail:   fmt.Sprintf("This default value is not compatible with the variable's type constraint: %s.", err),
   156  					Subject:  attr.Expr.Range().Ptr(),
   157  				})
   158  				val = cty.DynamicVal
   159  			}
   160  		}
   161  
   162  		if !v.Nullable && val.IsNull() {
   163  			diags = append(diags, &hcl.Diagnostic{
   164  				Severity: hcl.DiagError,
   165  				Summary:  "Invalid default value for variable",
   166  				Detail:   "A null default value is not valid when nullable=false.",
   167  				Subject:  attr.Expr.Range().Ptr(),
   168  			})
   169  		}
   170  
   171  		v.Default = val
   172  	}
   173  
   174  	for _, block := range content.Blocks {
   175  		switch block.Type {
   176  
   177  		case "validation":
   178  			vv, moreDiags := decodeVariableValidationBlock(v.Name, block, override)
   179  			diags = append(diags, moreDiags...)
   180  			v.Validations = append(v.Validations, vv)
   181  
   182  		default:
   183  			// The above cases should be exhaustive for all block types
   184  			// defined in variableBlockSchema
   185  			panic(fmt.Sprintf("unhandled block type %q", block.Type))
   186  		}
   187  	}
   188  
   189  	return v, diags
   190  }
   191  
   192  func decodeVariableType(expr hcl.Expression) (cty.Type, *typeexpr.Defaults, VariableParsingMode, hcl.Diagnostics) {
   193  	if exprIsNativeQuotedString(expr) {
   194  		// If a user provides the pre-0.12 form of variable type argument where
   195  		// the string values "string", "list" and "map" are accepted, we
   196  		// provide an error to point the user towards using the type system
   197  		// correctly has a hint.
   198  		// Only the native syntax ends up in this codepath; we handle the
   199  		// JSON syntax (which is, of course, quoted within the type system)
   200  		// in the normal codepath below.
   201  		val, diags := expr.Value(nil)
   202  		if diags.HasErrors() {
   203  			return cty.DynamicPseudoType, nil, VariableParseHCL, diags
   204  		}
   205  		str := val.AsString()
   206  		switch str {
   207  		case "string":
   208  			diags = append(diags, &hcl.Diagnostic{
   209  				Severity: hcl.DiagError,
   210  				Summary:  "Invalid quoted type constraints",
   211  				Detail:   "Terraform 0.11 and earlier required type constraints to be given in quotes, but that form is now deprecated and will be removed in a future version of Terraform. Remove the quotes around \"string\".",
   212  				Subject:  expr.Range().Ptr(),
   213  			})
   214  			return cty.DynamicPseudoType, nil, VariableParseLiteral, diags
   215  		case "list":
   216  			diags = append(diags, &hcl.Diagnostic{
   217  				Severity: hcl.DiagError,
   218  				Summary:  "Invalid quoted type constraints",
   219  				Detail:   "Terraform 0.11 and earlier required type constraints to be given in quotes, but that form is now deprecated and will be removed in a future version of Terraform. Remove the quotes around \"list\" and write list(string) instead to explicitly indicate that the list elements are strings.",
   220  				Subject:  expr.Range().Ptr(),
   221  			})
   222  			return cty.DynamicPseudoType, nil, VariableParseHCL, diags
   223  		case "map":
   224  			diags = append(diags, &hcl.Diagnostic{
   225  				Severity: hcl.DiagError,
   226  				Summary:  "Invalid quoted type constraints",
   227  				Detail:   "Terraform 0.11 and earlier required type constraints to be given in quotes, but that form is now deprecated and will be removed in a future version of Terraform. Remove the quotes around \"map\" and write map(string) instead to explicitly indicate that the map elements are strings.",
   228  				Subject:  expr.Range().Ptr(),
   229  			})
   230  			return cty.DynamicPseudoType, nil, VariableParseHCL, diags
   231  		default:
   232  			return cty.DynamicPseudoType, nil, VariableParseHCL, hcl.Diagnostics{{
   233  				Severity: hcl.DiagError,
   234  				Summary:  "Invalid legacy variable type hint",
   235  				Detail:   `To provide a full type expression, remove the surrounding quotes and give the type expression directly.`,
   236  				Subject:  expr.Range().Ptr(),
   237  			}}
   238  		}
   239  	}
   240  
   241  	// First we'll deal with some shorthand forms that the HCL-level type
   242  	// expression parser doesn't include. These both emulate pre-0.12 behavior
   243  	// of allowing a list or map of any element type as long as all of the
   244  	// elements are consistent. This is the same as list(any) or map(any).
   245  	switch hcl.ExprAsKeyword(expr) {
   246  	case "list":
   247  		return cty.List(cty.DynamicPseudoType), nil, VariableParseHCL, nil
   248  	case "map":
   249  		return cty.Map(cty.DynamicPseudoType), nil, VariableParseHCL, nil
   250  	}
   251  
   252  	ty, typeDefaults, diags := typeexpr.TypeConstraintWithDefaults(expr)
   253  	if diags.HasErrors() {
   254  		return cty.DynamicPseudoType, nil, VariableParseHCL, diags
   255  	}
   256  
   257  	switch {
   258  	case ty.IsPrimitiveType():
   259  		// Primitive types use literal parsing.
   260  		return ty, typeDefaults, VariableParseLiteral, diags
   261  	default:
   262  		// Everything else uses HCL parsing
   263  		return ty, typeDefaults, VariableParseHCL, diags
   264  	}
   265  }
   266  
   267  func (v *Variable) Addr() addrs.InputVariable {
   268  	return addrs.InputVariable{Name: v.Name}
   269  }
   270  
   271  // Required returns true if this variable is required to be set by the caller,
   272  // or false if there is a default value that will be used when it isn't set.
   273  func (v *Variable) Required() bool {
   274  	return v.Default == cty.NilVal
   275  }
   276  
   277  // VariableParsingMode defines how values of a particular variable given by
   278  // text-only mechanisms (command line arguments and environment variables)
   279  // should be parsed to produce the final value.
   280  type VariableParsingMode rune
   281  
   282  // VariableParseLiteral is a variable parsing mode that just takes the given
   283  // string directly as a cty.String value.
   284  const VariableParseLiteral VariableParsingMode = 'L'
   285  
   286  // VariableParseHCL is a variable parsing mode that attempts to parse the given
   287  // string as an HCL expression and returns the result.
   288  const VariableParseHCL VariableParsingMode = 'H'
   289  
   290  // Parse uses the receiving parsing mode to process the given variable value
   291  // string, returning the result along with any diagnostics.
   292  //
   293  // A VariableParsingMode does not know the expected type of the corresponding
   294  // variable, so it's the caller's responsibility to attempt to convert the
   295  // result to the appropriate type and return to the user any diagnostics that
   296  // conversion may produce.
   297  //
   298  // The given name is used to create a synthetic filename in case any diagnostics
   299  // must be generated about the given string value. This should be the name
   300  // of the root module variable whose value will be populated from the given
   301  // string.
   302  //
   303  // If the returned diagnostics has errors, the returned value may not be
   304  // valid.
   305  func (m VariableParsingMode) Parse(name, value string) (cty.Value, hcl.Diagnostics) {
   306  	switch m {
   307  	case VariableParseLiteral:
   308  		return cty.StringVal(value), nil
   309  	case VariableParseHCL:
   310  		fakeFilename := fmt.Sprintf("<value for var.%s>", name)
   311  		expr, diags := hclsyntax.ParseExpression([]byte(value), fakeFilename, hcl.Pos{Line: 1, Column: 1})
   312  		if diags.HasErrors() {
   313  			return cty.DynamicVal, diags
   314  		}
   315  		val, valDiags := expr.Value(nil)
   316  		diags = append(diags, valDiags...)
   317  		return val, diags
   318  	default:
   319  		// Should never happen
   320  		panic(fmt.Errorf("Parse called on invalid VariableParsingMode %#v", m))
   321  	}
   322  }
   323  
   324  // decodeVariableValidationBlock is a wrapper around decodeCheckRuleBlock
   325  // that imposes the additional rule that the condition expression can refer
   326  // only to an input variable of the given name.
   327  func decodeVariableValidationBlock(varName string, block *hcl.Block, override bool) (*CheckRule, hcl.Diagnostics) {
   328  	vv, diags := decodeCheckRuleBlock(block, override)
   329  	if vv.Condition != nil {
   330  		// The validation condition can only refer to the variable itself,
   331  		// to ensure that the variable declaration can't create additional
   332  		// edges in the dependency graph.
   333  		goodRefs := 0
   334  		for _, traversal := range vv.Condition.Variables() {
   335  			ref, moreDiags := addrs.ParseRef(traversal)
   336  			if !moreDiags.HasErrors() {
   337  				if addr, ok := ref.Subject.(addrs.InputVariable); ok {
   338  					if addr.Name == varName {
   339  						goodRefs++
   340  						continue // Reference is valid
   341  					}
   342  				}
   343  			}
   344  			// If we fall out here then the reference is invalid.
   345  			diags = diags.Append(&hcl.Diagnostic{
   346  				Severity: hcl.DiagError,
   347  				Summary:  "Invalid reference in variable validation",
   348  				Detail:   fmt.Sprintf("The condition for variable %q can only refer to the variable itself, using var.%s.", varName, varName),
   349  				Subject:  traversal.SourceRange().Ptr(),
   350  			})
   351  		}
   352  		if goodRefs < 1 {
   353  			diags = diags.Append(&hcl.Diagnostic{
   354  				Severity: hcl.DiagError,
   355  				Summary:  "Invalid variable validation condition",
   356  				Detail:   fmt.Sprintf("The condition for variable %q must refer to var.%s in order to test incoming values.", varName, varName),
   357  				Subject:  vv.Condition.Range().Ptr(),
   358  			})
   359  		}
   360  	}
   361  
   362  	if vv.ErrorMessage != nil {
   363  		// The same applies to the validation error message, except that
   364  		// references are not required. A string literal is a valid error
   365  		// message.
   366  		goodRefs := 0
   367  		for _, traversal := range vv.ErrorMessage.Variables() {
   368  			ref, moreDiags := addrs.ParseRef(traversal)
   369  			if !moreDiags.HasErrors() {
   370  				if addr, ok := ref.Subject.(addrs.InputVariable); ok {
   371  					if addr.Name == varName {
   372  						goodRefs++
   373  						continue // Reference is valid
   374  					}
   375  				}
   376  			}
   377  			// If we fall out here then the reference is invalid.
   378  			diags = diags.Append(&hcl.Diagnostic{
   379  				Severity: hcl.DiagError,
   380  				Summary:  "Invalid reference in variable validation",
   381  				Detail:   fmt.Sprintf("The error message for variable %q can only refer to the variable itself, using var.%s.", varName, varName),
   382  				Subject:  traversal.SourceRange().Ptr(),
   383  			})
   384  		}
   385  	}
   386  
   387  	return vv, diags
   388  }
   389  
   390  // Output represents an "output" block in a module or file.
   391  type Output struct {
   392  	Name        string
   393  	Description string
   394  	Expr        hcl.Expression
   395  	DependsOn   []hcl.Traversal
   396  	Sensitive   bool
   397  
   398  	Preconditions []*CheckRule
   399  
   400  	DescriptionSet bool
   401  	SensitiveSet   bool
   402  
   403  	DeclRange hcl.Range
   404  }
   405  
   406  func decodeOutputBlock(block *hcl.Block, override bool) (*Output, hcl.Diagnostics) {
   407  	var diags hcl.Diagnostics
   408  
   409  	o := &Output{
   410  		Name:      block.Labels[0],
   411  		DeclRange: block.DefRange,
   412  	}
   413  
   414  	schema := outputBlockSchema
   415  	if override {
   416  		schema = schemaForOverrides(schema)
   417  	}
   418  
   419  	content, moreDiags := block.Body.Content(schema)
   420  	diags = append(diags, moreDiags...)
   421  
   422  	if !hclsyntax.ValidIdentifier(o.Name) {
   423  		diags = append(diags, &hcl.Diagnostic{
   424  			Severity: hcl.DiagError,
   425  			Summary:  "Invalid output name",
   426  			Detail:   badIdentifierDetail,
   427  			Subject:  &block.LabelRanges[0],
   428  		})
   429  	}
   430  
   431  	if attr, exists := content.Attributes["description"]; exists {
   432  		valDiags := gohcl.DecodeExpression(attr.Expr, nil, &o.Description)
   433  		diags = append(diags, valDiags...)
   434  		o.DescriptionSet = true
   435  	}
   436  
   437  	if attr, exists := content.Attributes["value"]; exists {
   438  		o.Expr = attr.Expr
   439  	}
   440  
   441  	if attr, exists := content.Attributes["sensitive"]; exists {
   442  		valDiags := gohcl.DecodeExpression(attr.Expr, nil, &o.Sensitive)
   443  		diags = append(diags, valDiags...)
   444  		o.SensitiveSet = true
   445  	}
   446  
   447  	if attr, exists := content.Attributes["depends_on"]; exists {
   448  		deps, depsDiags := decodeDependsOn(attr)
   449  		diags = append(diags, depsDiags...)
   450  		o.DependsOn = append(o.DependsOn, deps...)
   451  	}
   452  
   453  	for _, block := range content.Blocks {
   454  		switch block.Type {
   455  		case "precondition":
   456  			cr, moreDiags := decodeCheckRuleBlock(block, override)
   457  			diags = append(diags, moreDiags...)
   458  			o.Preconditions = append(o.Preconditions, cr)
   459  		case "postcondition":
   460  			diags = append(diags, &hcl.Diagnostic{
   461  				Severity: hcl.DiagError,
   462  				Summary:  "Postconditions are not allowed",
   463  				Detail:   "Output values can only have preconditions, not postconditions.",
   464  				Subject:  block.TypeRange.Ptr(),
   465  			})
   466  		default:
   467  			// The cases above should be exhaustive for all block types
   468  			// defined in the block type schema, so this shouldn't happen.
   469  			panic(fmt.Sprintf("unexpected lifecycle sub-block type %q", block.Type))
   470  		}
   471  	}
   472  
   473  	return o, diags
   474  }
   475  
   476  func (o *Output) Addr() addrs.OutputValue {
   477  	return addrs.OutputValue{Name: o.Name}
   478  }
   479  
   480  // Local represents a single entry from a "locals" block in a module or file.
   481  // The "locals" block itself is not represented, because it serves only to
   482  // provide context for us to interpret its contents.
   483  type Local struct {
   484  	Name string
   485  	Expr hcl.Expression
   486  
   487  	DeclRange hcl.Range
   488  }
   489  
   490  func decodeLocalsBlock(block *hcl.Block) ([]*Local, hcl.Diagnostics) {
   491  	attrs, diags := block.Body.JustAttributes()
   492  	if len(attrs) == 0 {
   493  		return nil, diags
   494  	}
   495  
   496  	locals := make([]*Local, 0, len(attrs))
   497  	for name, attr := range attrs {
   498  		if !hclsyntax.ValidIdentifier(name) {
   499  			diags = append(diags, &hcl.Diagnostic{
   500  				Severity: hcl.DiagError,
   501  				Summary:  "Invalid local value name",
   502  				Detail:   badIdentifierDetail,
   503  				Subject:  &attr.NameRange,
   504  			})
   505  		}
   506  
   507  		locals = append(locals, &Local{
   508  			Name:      name,
   509  			Expr:      attr.Expr,
   510  			DeclRange: attr.Range,
   511  		})
   512  	}
   513  	return locals, diags
   514  }
   515  
   516  // Addr returns the address of the local value declared by the receiver,
   517  // relative to its containing module.
   518  func (l *Local) Addr() addrs.LocalValue {
   519  	return addrs.LocalValue{
   520  		Name: l.Name,
   521  	}
   522  }
   523  
   524  var variableBlockSchema = &hcl.BodySchema{
   525  	Attributes: []hcl.AttributeSchema{
   526  		{
   527  			Name: "description",
   528  		},
   529  		{
   530  			Name: "default",
   531  		},
   532  		{
   533  			Name: "type",
   534  		},
   535  		{
   536  			Name: "sensitive",
   537  		},
   538  		{
   539  			Name: "nullable",
   540  		},
   541  	},
   542  	Blocks: []hcl.BlockHeaderSchema{
   543  		{
   544  			Type: "validation",
   545  		},
   546  	},
   547  }
   548  
   549  var outputBlockSchema = &hcl.BodySchema{
   550  	Attributes: []hcl.AttributeSchema{
   551  		{
   552  			Name: "description",
   553  		},
   554  		{
   555  			Name:     "value",
   556  			Required: true,
   557  		},
   558  		{
   559  			Name: "depends_on",
   560  		},
   561  		{
   562  			Name: "sensitive",
   563  		},
   564  	},
   565  	Blocks: []hcl.BlockHeaderSchema{
   566  		{Type: "precondition"},
   567  		{Type: "postcondition"},
   568  	},
   569  }