github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/backend/unparsed_value.go (about) 1 package backend 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl/v2" 7 "github.com/hashicorp/terraform/internal/configs" 8 "github.com/hashicorp/terraform/internal/terraform" 9 "github.com/hashicorp/terraform/internal/tfdiags" 10 "github.com/zclconf/go-cty/cty" 11 ) 12 13 // UnparsedVariableValue represents a variable value provided by the caller 14 // whose parsing must be deferred until configuration is available. 15 // 16 // This exists to allow processing of variable-setting arguments (e.g. in the 17 // command package) to be separated from parsing (in the backend package). 18 type UnparsedVariableValue interface { 19 // ParseVariableValue information in the provided variable configuration 20 // to parse (if necessary) and return the variable value encapsulated in 21 // the receiver. 22 // 23 // If error diagnostics are returned, the resulting value may be invalid 24 // or incomplete. 25 ParseVariableValue(mode configs.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics) 26 } 27 28 // ParseVariableValues processes a map of unparsed variable values by 29 // correlating each one with the given variable declarations which should 30 // be from a root module. 31 // 32 // The map of unparsed variable values should include variables from all 33 // possible root module declarations sources such that it is as complete as 34 // it can possibly be for the current operation. If any declared variables 35 // are not included in the map, ParseVariableValues will either substitute 36 // a configured default value or produce an error. 37 // 38 // If this function returns without any errors in the diagnostics, the 39 // resulting input values map is guaranteed to be valid and ready to pass 40 // to terraform.NewContext. If the diagnostics contains errors, the returned 41 // InputValues may be incomplete but will include the subset of variables 42 // that were successfully processed, allowing for careful analysis of the 43 // partial result. 44 func ParseVariableValues(vv map[string]UnparsedVariableValue, decls map[string]*configs.Variable) (terraform.InputValues, tfdiags.Diagnostics) { 45 var diags tfdiags.Diagnostics 46 ret := make(terraform.InputValues, len(vv)) 47 48 // Currently we're generating only warnings for undeclared variables 49 // defined in files (see below) but we only want to generate a few warnings 50 // at a time because existing deployments may have lots of these and 51 // the result can therefore be overwhelming. 52 seenUndeclaredInFile := 0 53 54 for name, rv := range vv { 55 var mode configs.VariableParsingMode 56 config, declared := decls[name] 57 if declared { 58 mode = config.ParsingMode 59 } else { 60 mode = configs.VariableParseLiteral 61 } 62 63 val, valDiags := rv.ParseVariableValue(mode) 64 diags = diags.Append(valDiags) 65 if valDiags.HasErrors() { 66 continue 67 } 68 69 if !declared { 70 switch val.SourceType { 71 case terraform.ValueFromConfig, terraform.ValueFromAutoFile, terraform.ValueFromNamedFile: 72 // We allow undeclared names for variable values from files and warn in case 73 // users have forgotten a variable {} declaration or have a typo in their var name. 74 // Some users will actively ignore this warning because they use a .tfvars file 75 // across multiple configurations. 76 if seenUndeclaredInFile < 2 { 77 diags = diags.Append(tfdiags.Sourceless( 78 tfdiags.Warning, 79 "Value for undeclared variable", 80 fmt.Sprintf("The root module does not declare a variable named %q but a value was found in file %q. If you meant to use this value, add a \"variable\" block to the configuration.\n\nTo silence these warnings, use TF_VAR_... environment variables to provide certain \"global\" settings to all configurations in your organization. To reduce the verbosity of these warnings, use the -compact-warnings option.", name, val.SourceRange.Filename), 81 )) 82 } 83 seenUndeclaredInFile++ 84 85 case terraform.ValueFromEnvVar: 86 // We allow and ignore undeclared names for environment 87 // variables, because users will often set these globally 88 // when they are used across many (but not necessarily all) 89 // configurations. 90 case terraform.ValueFromCLIArg: 91 diags = diags.Append(tfdiags.Sourceless( 92 tfdiags.Error, 93 "Value for undeclared variable", 94 fmt.Sprintf("A variable named %q was assigned on the command line, but the root module does not declare a variable of that name. To use this value, add a \"variable\" block to the configuration.", name), 95 )) 96 default: 97 // For all other source types we are more vague, but other situations 98 // don't generally crop up at this layer in practice. 99 diags = diags.Append(tfdiags.Sourceless( 100 tfdiags.Error, 101 "Value for undeclared variable", 102 fmt.Sprintf("A variable named %q was assigned a value, but the root module does not declare a variable of that name. To use this value, add a \"variable\" block to the configuration.", name), 103 )) 104 } 105 continue 106 } 107 108 ret[name] = val 109 } 110 111 if seenUndeclaredInFile > 2 { 112 extras := seenUndeclaredInFile - 2 113 diags = diags.Append(&hcl.Diagnostic{ 114 Severity: hcl.DiagWarning, 115 Summary: "Values for undeclared variables", 116 Detail: fmt.Sprintf("In addition to the other similar warnings shown, %d other variable(s) defined without being declared.", extras), 117 }) 118 } 119 120 // By this point we should've gathered all of the required root module 121 // variables from one of the many possible sources. We'll now populate 122 // any we haven't gathered as their defaults and fail if any of the 123 // missing ones are required. 124 for name, vc := range decls { 125 if _, defined := ret[name]; defined { 126 continue 127 } 128 129 if vc.Required() { 130 diags = diags.Append(&hcl.Diagnostic{ 131 Severity: hcl.DiagError, 132 Summary: "No value for required variable", 133 Detail: fmt.Sprintf("The root module input variable %q is not set, and has no default value. Use a -var or -var-file command line argument to provide a value for this variable.", name), 134 Subject: vc.DeclRange.Ptr(), 135 }) 136 137 // We'll include a placeholder value anyway, just so that our 138 // result is complete for any calling code that wants to cautiously 139 // analyze it for diagnostic purposes. Since our diagnostics now 140 // includes an error, normal processing will ignore this result. 141 ret[name] = &terraform.InputValue{ 142 Value: cty.DynamicVal, 143 SourceType: terraform.ValueFromConfig, 144 SourceRange: tfdiags.SourceRangeFromHCL(vc.DeclRange), 145 } 146 } else { 147 ret[name] = &terraform.InputValue{ 148 Value: vc.Default, 149 SourceType: terraform.ValueFromConfig, 150 SourceRange: tfdiags.SourceRangeFromHCL(vc.DeclRange), 151 } 152 } 153 } 154 155 return ret, diags 156 }