github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/configs/named_values.go (about)

     1  package configs
     2  
     3  import (
     4  	"fmt"
     5  	"unicode"
     6  
     7  	"github.com/hashicorp/hcl/v2"
     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/muratcelep/terraform/not-internal/addrs"
    14  	"github.com/muratcelep/terraform/not-internal/typeexpr"
    15  )
    16  
    17  // A consistent detail message for all "not a valid identifier" diagnostics.
    18  const badIdentifierDetail = "A name must start with a letter or underscore and may contain only letters, digits, underscores, and dashes."
    19  
    20  // Variable represents a "variable" block in a module or file.
    21  type Variable struct {
    22  	Name        string
    23  	Description string
    24  	Default     cty.Value
    25  
    26  	// Type is the concrete type of the variable value.
    27  	Type cty.Type
    28  	// ConstraintType is used for decoding and type conversions, and may
    29  	// contain nested ObjectWithOptionalAttr types.
    30  	ConstraintType cty.Type
    31  
    32  	ParsingMode VariableParsingMode
    33  	Validations []*VariableValidation
    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, parseMode, tyDiags := decodeVariableType(attr.Expr)
   107  		diags = append(diags, tyDiags...)
   108  		v.ConstraintType = ty
   109  		v.Type = ty.WithoutOptionalAttributesDeep()
   110  		v.ParsingMode = parseMode
   111  	}
   112  
   113  	if attr, exists := content.Attributes["sensitive"]; exists {
   114  		valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Sensitive)
   115  		diags = append(diags, valDiags...)
   116  		v.SensitiveSet = true
   117  	}
   118  
   119  	if attr, exists := content.Attributes["nullable"]; exists {
   120  		valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Nullable)
   121  		diags = append(diags, valDiags...)
   122  		v.NullableSet = true
   123  	} else {
   124  		// The current default is true, which is subject to change in a future
   125  		// language edition.
   126  		v.Nullable = true
   127  	}
   128  
   129  	if attr, exists := content.Attributes["default"]; exists {
   130  		val, valDiags := attr.Expr.Value(nil)
   131  		diags = append(diags, valDiags...)
   132  
   133  		// Convert the default to the expected type so we can catch invalid
   134  		// defaults early and allow later code to assume validity.
   135  		// Note that this depends on us having already processed any "type"
   136  		// attribute above.
   137  		// However, we can't do this if we're in an override file where
   138  		// the type might not be set; we'll catch that during merge.
   139  		if v.ConstraintType != cty.NilType {
   140  			var err error
   141  			val, err = convert.Convert(val, v.ConstraintType)
   142  			if err != nil {
   143  				diags = append(diags, &hcl.Diagnostic{
   144  					Severity: hcl.DiagError,
   145  					Summary:  "Invalid default value for variable",
   146  					Detail:   fmt.Sprintf("This default value is not compatible with the variable's type constraint: %s.", err),
   147  					Subject:  attr.Expr.Range().Ptr(),
   148  				})
   149  				val = cty.DynamicVal
   150  			}
   151  		}
   152  
   153  		if !v.Nullable && val.IsNull() {
   154  			diags = append(diags, &hcl.Diagnostic{
   155  				Severity: hcl.DiagError,
   156  				Summary:  "Invalid default value for variable",
   157  				Detail:   "A null default value is not valid when nullable=false.",
   158  				Subject:  attr.Expr.Range().Ptr(),
   159  			})
   160  		}
   161  
   162  		v.Default = val
   163  	}
   164  
   165  	for _, block := range content.Blocks {
   166  		switch block.Type {
   167  
   168  		case "validation":
   169  			vv, moreDiags := decodeVariableValidationBlock(v.Name, block, override)
   170  			diags = append(diags, moreDiags...)
   171  			v.Validations = append(v.Validations, vv)
   172  
   173  		default:
   174  			// The above cases should be exhaustive for all block types
   175  			// defined in variableBlockSchema
   176  			panic(fmt.Sprintf("unhandled block type %q", block.Type))
   177  		}
   178  	}
   179  
   180  	return v, diags
   181  }
   182  
   183  func decodeVariableType(expr hcl.Expression) (cty.Type, VariableParsingMode, hcl.Diagnostics) {
   184  	if exprIsNativeQuotedString(expr) {
   185  		// If a user provides the pre-0.12 form of variable type argument where
   186  		// the string values "string", "list" and "map" are accepted, we
   187  		// provide an error to point the user towards using the type system
   188  		// correctly has a hint.
   189  		// Only the native syntax ends up in this codepath; we handle the
   190  		// JSON syntax (which is, of course, quoted within the type system)
   191  		// in the normal codepath below.
   192  		val, diags := expr.Value(nil)
   193  		if diags.HasErrors() {
   194  			return cty.DynamicPseudoType, VariableParseHCL, diags
   195  		}
   196  		str := val.AsString()
   197  		switch str {
   198  		case "string":
   199  			diags = append(diags, &hcl.Diagnostic{
   200  				Severity: hcl.DiagError,
   201  				Summary:  "Invalid quoted type constraints",
   202  				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\".",
   203  				Subject:  expr.Range().Ptr(),
   204  			})
   205  			return cty.DynamicPseudoType, VariableParseLiteral, diags
   206  		case "list":
   207  			diags = append(diags, &hcl.Diagnostic{
   208  				Severity: hcl.DiagError,
   209  				Summary:  "Invalid quoted type constraints",
   210  				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.",
   211  				Subject:  expr.Range().Ptr(),
   212  			})
   213  			return cty.DynamicPseudoType, VariableParseHCL, diags
   214  		case "map":
   215  			diags = append(diags, &hcl.Diagnostic{
   216  				Severity: hcl.DiagError,
   217  				Summary:  "Invalid quoted type constraints",
   218  				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.",
   219  				Subject:  expr.Range().Ptr(),
   220  			})
   221  			return cty.DynamicPseudoType, VariableParseHCL, diags
   222  		default:
   223  			return cty.DynamicPseudoType, VariableParseHCL, hcl.Diagnostics{{
   224  				Severity: hcl.DiagError,
   225  				Summary:  "Invalid legacy variable type hint",
   226  				Detail:   `To provide a full type expression, remove the surrounding quotes and give the type expression directly.`,
   227  				Subject:  expr.Range().Ptr(),
   228  			}}
   229  		}
   230  	}
   231  
   232  	// First we'll deal with some shorthand forms that the HCL-level type
   233  	// expression parser doesn't include. These both emulate pre-0.12 behavior
   234  	// of allowing a list or map of any element type as long as all of the
   235  	// elements are consistent. This is the same as list(any) or map(any).
   236  	switch hcl.ExprAsKeyword(expr) {
   237  	case "list":
   238  		return cty.List(cty.DynamicPseudoType), VariableParseHCL, nil
   239  	case "map":
   240  		return cty.Map(cty.DynamicPseudoType), VariableParseHCL, nil
   241  	}
   242  
   243  	ty, diags := typeexpr.TypeConstraint(expr)
   244  	if diags.HasErrors() {
   245  		return cty.DynamicPseudoType, VariableParseHCL, diags
   246  	}
   247  
   248  	switch {
   249  	case ty.IsPrimitiveType():
   250  		// Primitive types use literal parsing.
   251  		return ty, VariableParseLiteral, diags
   252  	default:
   253  		// Everything else uses HCL parsing
   254  		return ty, VariableParseHCL, diags
   255  	}
   256  }
   257  
   258  // Required returns true if this variable is required to be set by the caller,
   259  // or false if there is a default value that will be used when it isn't set.
   260  func (v *Variable) Required() bool {
   261  	return v.Default == cty.NilVal
   262  }
   263  
   264  // VariableParsingMode defines how values of a particular variable given by
   265  // text-only mechanisms (command line arguments and environment variables)
   266  // should be parsed to produce the final value.
   267  type VariableParsingMode rune
   268  
   269  // VariableParseLiteral is a variable parsing mode that just takes the given
   270  // string directly as a cty.String value.
   271  const VariableParseLiteral VariableParsingMode = 'L'
   272  
   273  // VariableParseHCL is a variable parsing mode that attempts to parse the given
   274  // string as an HCL expression and returns the result.
   275  const VariableParseHCL VariableParsingMode = 'H'
   276  
   277  // Parse uses the receiving parsing mode to process the given variable value
   278  // string, returning the result along with any diagnostics.
   279  //
   280  // A VariableParsingMode does not know the expected type of the corresponding
   281  // variable, so it's the caller's responsibility to attempt to convert the
   282  // result to the appropriate type and return to the user any diagnostics that
   283  // conversion may produce.
   284  //
   285  // The given name is used to create a synthetic filename in case any diagnostics
   286  // must be generated about the given string value. This should be the name
   287  // of the root module variable whose value will be populated from the given
   288  // string.
   289  //
   290  // If the returned diagnostics has errors, the returned value may not be
   291  // valid.
   292  func (m VariableParsingMode) Parse(name, value string) (cty.Value, hcl.Diagnostics) {
   293  	switch m {
   294  	case VariableParseLiteral:
   295  		return cty.StringVal(value), nil
   296  	case VariableParseHCL:
   297  		fakeFilename := fmt.Sprintf("<value for var.%s>", name)
   298  		expr, diags := hclsyntax.ParseExpression([]byte(value), fakeFilename, hcl.Pos{Line: 1, Column: 1})
   299  		if diags.HasErrors() {
   300  			return cty.DynamicVal, diags
   301  		}
   302  		val, valDiags := expr.Value(nil)
   303  		diags = append(diags, valDiags...)
   304  		return val, diags
   305  	default:
   306  		// Should never happen
   307  		panic(fmt.Errorf("Parse called on invalid VariableParsingMode %#v", m))
   308  	}
   309  }
   310  
   311  // VariableValidation represents a configuration-defined validation rule
   312  // for a particular input variable, given as a "validation" block inside
   313  // a "variable" block.
   314  type VariableValidation struct {
   315  	// Condition is an expression that refers to the variable being tested
   316  	// and contains no other references. The expression must return true
   317  	// to indicate that the value is valid or false to indicate that it is
   318  	// invalid. If the expression produces an error, that's considered a bug
   319  	// in the module defining the validation rule, not an error in the caller.
   320  	Condition hcl.Expression
   321  
   322  	// ErrorMessage is one or more full sentences, which would need to be in
   323  	// English for consistency with the rest of the error message output but
   324  	// can in practice be in any language as long as it ends with a period.
   325  	// The message should describe what is required for the condition to return
   326  	// true in a way that would make sense to a caller of the module.
   327  	ErrorMessage string
   328  
   329  	DeclRange hcl.Range
   330  }
   331  
   332  func decodeVariableValidationBlock(varName string, block *hcl.Block, override bool) (*VariableValidation, hcl.Diagnostics) {
   333  	var diags hcl.Diagnostics
   334  	vv := &VariableValidation{
   335  		DeclRange: block.DefRange,
   336  	}
   337  
   338  	if override {
   339  		// For now we'll just forbid overriding validation blocks, to simplify
   340  		// the initial design. If we can find a clear use-case for overriding
   341  		// validations in override files and there's a way to define it that
   342  		// isn't confusing then we could relax this.
   343  		diags = diags.Append(&hcl.Diagnostic{
   344  			Severity: hcl.DiagError,
   345  			Summary:  "Can't override variable validation rules",
   346  			Detail:   "Variable \"validation\" blocks cannot be used in override files.",
   347  			Subject:  vv.DeclRange.Ptr(),
   348  		})
   349  		return vv, diags
   350  	}
   351  
   352  	content, moreDiags := block.Body.Content(variableValidationBlockSchema)
   353  	diags = append(diags, moreDiags...)
   354  
   355  	if attr, exists := content.Attributes["condition"]; exists {
   356  		vv.Condition = attr.Expr
   357  
   358  		// The validation condition can only refer to the variable itself,
   359  		// to ensure that the variable declaration can't create additional
   360  		// edges in the dependency graph.
   361  		goodRefs := 0
   362  		for _, traversal := range vv.Condition.Variables() {
   363  			ref, moreDiags := addrs.ParseRef(traversal)
   364  			if !moreDiags.HasErrors() {
   365  				if addr, ok := ref.Subject.(addrs.InputVariable); ok {
   366  					if addr.Name == varName {
   367  						goodRefs++
   368  						continue // Reference is valid
   369  					}
   370  				}
   371  			}
   372  			// If we fall out here then the reference is invalid.
   373  			diags = diags.Append(&hcl.Diagnostic{
   374  				Severity: hcl.DiagError,
   375  				Summary:  "Invalid reference in variable validation",
   376  				Detail:   fmt.Sprintf("The condition for variable %q can only refer to the variable itself, using var.%s.", varName, varName),
   377  				Subject:  traversal.SourceRange().Ptr(),
   378  			})
   379  		}
   380  		if goodRefs < 1 {
   381  			diags = diags.Append(&hcl.Diagnostic{
   382  				Severity: hcl.DiagError,
   383  				Summary:  "Invalid variable validation condition",
   384  				Detail:   fmt.Sprintf("The condition for variable %q must refer to var.%s in order to test incoming values.", varName, varName),
   385  				Subject:  attr.Expr.Range().Ptr(),
   386  			})
   387  		}
   388  	}
   389  
   390  	if attr, exists := content.Attributes["error_message"]; exists {
   391  		moreDiags := gohcl.DecodeExpression(attr.Expr, nil, &vv.ErrorMessage)
   392  		diags = append(diags, moreDiags...)
   393  		if !moreDiags.HasErrors() {
   394  			const errSummary = "Invalid validation error message"
   395  			switch {
   396  			case vv.ErrorMessage == "":
   397  				diags = diags.Append(&hcl.Diagnostic{
   398  					Severity: hcl.DiagError,
   399  					Summary:  errSummary,
   400  					Detail:   "An empty string is not a valid nor useful error message.",
   401  					Subject:  attr.Expr.Range().Ptr(),
   402  				})
   403  			case !looksLikeSentences(vv.ErrorMessage):
   404  				// Because we're going to include this string verbatim as part
   405  				// of a bigger error message written in our usual style in
   406  				// English, we'll require the given error message to conform
   407  				// to that. We might relax this in future if e.g. we start
   408  				// presenting these error messages in a different way, or if
   409  				// Terraform starts supporting producing error messages in
   410  				// other human languages, etc.
   411  				// For pragmatism we also allow sentences ending with
   412  				// exclamation points, but we don't mention it explicitly here
   413  				// because that's not really consistent with the Terraform UI
   414  				// writing style.
   415  				diags = diags.Append(&hcl.Diagnostic{
   416  					Severity: hcl.DiagError,
   417  					Summary:  errSummary,
   418  					Detail:   "The validation error message must be at least one full sentence starting with an uppercase letter and ending with a period or question mark.\n\nYour given message will be included as part of a larger Terraform error message, written as English prose. For broadly-shared modules we suggest using a similar writing style so that the overall result will be consistent.",
   419  					Subject:  attr.Expr.Range().Ptr(),
   420  				})
   421  			}
   422  		}
   423  	}
   424  
   425  	return vv, diags
   426  }
   427  
   428  // looksLikeSentence is a simple heuristic that encourages writing error
   429  // messages that will be presentable when included as part of a larger
   430  // Terraform error diagnostic whose other text is written in the Terraform
   431  // UI writing style.
   432  //
   433  // This is intentionally not a very strong validation since we're assuming
   434  // that module authors want to write good messages and might just need a nudge
   435  // about Terraform's specific style, rather than that they are going to try
   436  // to work around these rules to write a lower-quality message.
   437  func looksLikeSentences(s string) bool {
   438  	if len(s) < 1 {
   439  		return false
   440  	}
   441  	runes := []rune(s) // HCL guarantees that all strings are valid UTF-8
   442  	first := runes[0]
   443  	last := runes[len(runes)-1]
   444  
   445  	// If the first rune is a letter then it must be an uppercase letter.
   446  	// (This will only see the first rune in a multi-rune combining sequence,
   447  	// but the first rune is generally the letter if any are, and if not then
   448  	// we'll just ignore it because we're primarily expecting English messages
   449  	// right now anyway, for consistency with all of Terraform's other output.)
   450  	if unicode.IsLetter(first) && !unicode.IsUpper(first) {
   451  		return false
   452  	}
   453  
   454  	// The string must be at least one full sentence, which implies having
   455  	// sentence-ending punctuation.
   456  	// (This assumes that if a sentence ends with quotes then the period
   457  	// will be outside the quotes, which is consistent with Terraform's UI
   458  	// writing style.)
   459  	return last == '.' || last == '?' || last == '!'
   460  }
   461  
   462  // Output represents an "output" block in a module or file.
   463  type Output struct {
   464  	Name        string
   465  	Description string
   466  	Expr        hcl.Expression
   467  	DependsOn   []hcl.Traversal
   468  	Sensitive   bool
   469  
   470  	DescriptionSet bool
   471  	SensitiveSet   bool
   472  
   473  	DeclRange hcl.Range
   474  }
   475  
   476  func decodeOutputBlock(block *hcl.Block, override bool) (*Output, hcl.Diagnostics) {
   477  	var diags hcl.Diagnostics
   478  
   479  	o := &Output{
   480  		Name:      block.Labels[0],
   481  		DeclRange: block.DefRange,
   482  	}
   483  
   484  	schema := outputBlockSchema
   485  	if override {
   486  		schema = schemaForOverrides(schema)
   487  	}
   488  
   489  	content, moreDiags := block.Body.Content(schema)
   490  	diags = append(diags, moreDiags...)
   491  
   492  	if !hclsyntax.ValidIdentifier(o.Name) {
   493  		diags = append(diags, &hcl.Diagnostic{
   494  			Severity: hcl.DiagError,
   495  			Summary:  "Invalid output name",
   496  			Detail:   badIdentifierDetail,
   497  			Subject:  &block.LabelRanges[0],
   498  		})
   499  	}
   500  
   501  	if attr, exists := content.Attributes["description"]; exists {
   502  		valDiags := gohcl.DecodeExpression(attr.Expr, nil, &o.Description)
   503  		diags = append(diags, valDiags...)
   504  		o.DescriptionSet = true
   505  	}
   506  
   507  	if attr, exists := content.Attributes["value"]; exists {
   508  		o.Expr = attr.Expr
   509  	}
   510  
   511  	if attr, exists := content.Attributes["sensitive"]; exists {
   512  		valDiags := gohcl.DecodeExpression(attr.Expr, nil, &o.Sensitive)
   513  		diags = append(diags, valDiags...)
   514  		o.SensitiveSet = true
   515  	}
   516  
   517  	if attr, exists := content.Attributes["depends_on"]; exists {
   518  		deps, depsDiags := decodeDependsOn(attr)
   519  		diags = append(diags, depsDiags...)
   520  		o.DependsOn = append(o.DependsOn, deps...)
   521  	}
   522  
   523  	return o, diags
   524  }
   525  
   526  // Local represents a single entry from a "locals" block in a module or file.
   527  // The "locals" block itself is not represented, because it serves only to
   528  // provide context for us to interpret its contents.
   529  type Local struct {
   530  	Name string
   531  	Expr hcl.Expression
   532  
   533  	DeclRange hcl.Range
   534  }
   535  
   536  func decodeLocalsBlock(block *hcl.Block) ([]*Local, hcl.Diagnostics) {
   537  	attrs, diags := block.Body.JustAttributes()
   538  	if len(attrs) == 0 {
   539  		return nil, diags
   540  	}
   541  
   542  	locals := make([]*Local, 0, len(attrs))
   543  	for name, attr := range attrs {
   544  		if !hclsyntax.ValidIdentifier(name) {
   545  			diags = append(diags, &hcl.Diagnostic{
   546  				Severity: hcl.DiagError,
   547  				Summary:  "Invalid local value name",
   548  				Detail:   badIdentifierDetail,
   549  				Subject:  &attr.NameRange,
   550  			})
   551  		}
   552  
   553  		locals = append(locals, &Local{
   554  			Name:      name,
   555  			Expr:      attr.Expr,
   556  			DeclRange: attr.Range,
   557  		})
   558  	}
   559  	return locals, diags
   560  }
   561  
   562  // Addr returns the address of the local value declared by the receiver,
   563  // relative to its containing module.
   564  func (l *Local) Addr() addrs.LocalValue {
   565  	return addrs.LocalValue{
   566  		Name: l.Name,
   567  	}
   568  }
   569  
   570  var variableBlockSchema = &hcl.BodySchema{
   571  	Attributes: []hcl.AttributeSchema{
   572  		{
   573  			Name: "description",
   574  		},
   575  		{
   576  			Name: "default",
   577  		},
   578  		{
   579  			Name: "type",
   580  		},
   581  		{
   582  			Name: "sensitive",
   583  		},
   584  		{
   585  			Name: "nullable",
   586  		},
   587  	},
   588  	Blocks: []hcl.BlockHeaderSchema{
   589  		{
   590  			Type: "validation",
   591  		},
   592  	},
   593  }
   594  
   595  var variableValidationBlockSchema = &hcl.BodySchema{
   596  	Attributes: []hcl.AttributeSchema{
   597  		{
   598  			Name:     "condition",
   599  			Required: true,
   600  		},
   601  		{
   602  			Name:     "error_message",
   603  			Required: true,
   604  		},
   605  	},
   606  }
   607  
   608  var outputBlockSchema = &hcl.BodySchema{
   609  	Attributes: []hcl.AttributeSchema{
   610  		{
   611  			Name: "description",
   612  		},
   613  		{
   614  			Name:     "value",
   615  			Required: true,
   616  		},
   617  		{
   618  			Name: "depends_on",
   619  		},
   620  		{
   621  			Name: "sensitive",
   622  		},
   623  	},
   624  }