mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-21 14:12:57 -06:00
681d94ae20
Terraform 0.7 introduces lists and maps as first-class values for variables, in addition to string values which were previously available. However, there was previously no way to override the default value of a list or map, and the functionality for overriding specific map keys was broken. Using the environment variable method for setting variable values, there was previously no way to give a variable a value of a list or map. These now support HCL for individual values - specifying: TF_VAR_test='["Hello", "World"]' will set the variable `test` to a two-element list containing "Hello" and "World". Specifying TF_VAR_test_map='{"Hello = "World", "Foo" = "bar"}' will set the variable `test_map` to a two-element map with keys "Hello" and "Foo", and values "World" and "bar" respectively. The same logic is applied to `-var` flags, and the file parsed by `-var-files` ("autoVariables"). Note that care must be taken to not run into shell expansion for `-var-` flags and environment variables. We also merge map keys where appropriate. The override syntax has changed (to be noted in CHANGELOG as a breaking change), so several tests needed their syntax updating from the old `amis.us-east-1 = "newValue"` style to `amis = "{ "us-east-1" = "newValue"}"` style as defined in TF-002. In order to continue supporting the `-var "foo=bar"` type of variable flag (which is not valid HCL), a special case error is checked after HCL parsing fails, and the old code path runs instead.
141 lines
3.6 KiB
Go
141 lines
3.6 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/hashicorp/terraform/config"
|
|
"github.com/hashicorp/terraform/dag"
|
|
)
|
|
|
|
// GraphSemanticChecker is the interface that semantic checks across
|
|
// the entire Terraform graph implement.
|
|
//
|
|
// The graph should NOT be modified by the semantic checker.
|
|
type GraphSemanticChecker interface {
|
|
Check(*dag.Graph) error
|
|
}
|
|
|
|
// UnorderedSemanticCheckRunner is an implementation of GraphSemanticChecker
|
|
// that runs a list of SemanticCheckers against the vertices of the graph
|
|
// in no specified order.
|
|
type UnorderedSemanticCheckRunner struct {
|
|
Checks []SemanticChecker
|
|
}
|
|
|
|
func (sc *UnorderedSemanticCheckRunner) Check(g *dag.Graph) error {
|
|
var err error
|
|
for _, v := range g.Vertices() {
|
|
for _, check := range sc.Checks {
|
|
if e := check.Check(g, v); e != nil {
|
|
err = multierror.Append(err, e)
|
|
}
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// SemanticChecker is the interface that semantic checks across the
|
|
// Terraform graph implement. Errors are accumulated. Even after an error
|
|
// is returned, child vertices in the graph will still be visited.
|
|
//
|
|
// The graph should NOT be modified by the semantic checker.
|
|
//
|
|
// The order in which vertices are visited is left unspecified, so the
|
|
// semantic checks should not rely on that.
|
|
type SemanticChecker interface {
|
|
Check(*dag.Graph, dag.Vertex) error
|
|
}
|
|
|
|
// SemanticCheckModulesExist is an implementation of SemanticChecker that
|
|
// verifies that all the modules that are referenced in the graph exist.
|
|
type SemanticCheckModulesExist struct{}
|
|
|
|
// TODO: test
|
|
func (*SemanticCheckModulesExist) Check(g *dag.Graph, v dag.Vertex) error {
|
|
mn, ok := v.(*GraphNodeConfigModule)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
if mn.Tree == nil {
|
|
return fmt.Errorf(
|
|
"module '%s' not found", mn.Module.Name)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// smcUserVariables does all the semantic checks to verify that the
|
|
// variables given satisfy the configuration itself.
|
|
func smcUserVariables(c *config.Config, vs map[string]interface{}) []error {
|
|
var errs []error
|
|
|
|
cvs := make(map[string]*config.Variable)
|
|
for _, v := range c.Variables {
|
|
cvs[v.Name] = v
|
|
}
|
|
|
|
// Check that all required variables are present
|
|
required := make(map[string]struct{})
|
|
for _, v := range c.Variables {
|
|
if v.Required() {
|
|
required[v.Name] = struct{}{}
|
|
}
|
|
}
|
|
for k, _ := range vs {
|
|
delete(required, k)
|
|
}
|
|
if len(required) > 0 {
|
|
for k, _ := range required {
|
|
errs = append(errs, fmt.Errorf(
|
|
"Required variable not set: %s", k))
|
|
}
|
|
}
|
|
|
|
// Check that types match up
|
|
for name, proposedValue := range vs {
|
|
schema, ok := cvs[name]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
declaredType := schema.Type()
|
|
|
|
switch declaredType {
|
|
case config.VariableTypeString:
|
|
switch proposedValue.(type) {
|
|
case string:
|
|
continue
|
|
default:
|
|
errs = append(errs, fmt.Errorf("variable %s should be type %s, got %s",
|
|
name, declaredType.Printable(), hclTypeName(proposedValue)))
|
|
}
|
|
case config.VariableTypeMap:
|
|
switch proposedValue.(type) {
|
|
case map[string]interface{}:
|
|
continue
|
|
default:
|
|
errs = append(errs, fmt.Errorf("variable %s should be type %s, got %s",
|
|
name, declaredType.Printable(), hclTypeName(proposedValue)))
|
|
}
|
|
case config.VariableTypeList:
|
|
switch proposedValue.(type) {
|
|
case []interface{}:
|
|
continue
|
|
default:
|
|
errs = append(errs, fmt.Errorf("variable %s should be type %s, got %s",
|
|
name, declaredType.Printable(), hclTypeName(proposedValue)))
|
|
}
|
|
default:
|
|
errs = append(errs, fmt.Errorf("variable %s should be type %s, got %s",
|
|
name, declaredType.Printable(), hclTypeName(proposedValue)))
|
|
}
|
|
}
|
|
|
|
// TODO(mitchellh): variables that are unknown
|
|
|
|
return errs
|
|
}
|