command: collect root module variable values for apply/plan/refresh
This connects a missing link left by earlier refactoring: the command package is responsible for gathering up variable values provided by the user and passing them through to the backend to use in operations.
This commit is contained in:
parent
fc2614c939
commit
de3944b9bf
|
@ -179,6 +179,15 @@ func (c *ApplyCommand) Run(args []string) int {
|
|||
c.showDiagnostics(err)
|
||||
return 1
|
||||
}
|
||||
{
|
||||
var moreDiags tfdiags.Diagnostics
|
||||
opReq.Variables, moreDiags = c.collectVariableValues()
|
||||
diags = diags.Append(moreDiags)
|
||||
if moreDiags.HasErrors() {
|
||||
c.showDiagnostics(diags)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
op, err := c.RunOperation(be, opReq)
|
||||
if err != nil {
|
||||
|
|
|
@ -210,27 +210,6 @@ func (m *Meta) initDirFromModule(targetDir string, addr string, hooks configload
|
|||
return diags
|
||||
}
|
||||
|
||||
// loadVarsFile reads a file from the given path and interprets it as a
|
||||
// "vars file", returning the contained values as a map.
|
||||
//
|
||||
// The file is read using the parser associated with the receiver's
|
||||
// configuration loader, which means that the file's contents will be added
|
||||
// to the source cache that is used for config snippets in diagnostic messages.
|
||||
func (m *Meta) loadVarsFile(filename string) (map[string]cty.Value, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
loader, err := m.initConfigLoader()
|
||||
if err != nil {
|
||||
diags = diags.Append(err)
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
parser := loader.Parser()
|
||||
ret, hclDiags := parser.LoadValuesFile(filename)
|
||||
diags = diags.Append(hclDiags)
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
// inputForSchema uses interactive prompts to try to populate any
|
||||
// not-yet-populated required attributes in the given object value to
|
||||
// comply with the given schema.
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||
hcljson "github.com/hashicorp/hcl2/hcl/json"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
// collectVariableValues inspects the various places that root module input variable
|
||||
// values can come from and constructs a map ready to be passed to the
|
||||
// backend as part of a backend.Operation.
|
||||
//
|
||||
// This method returns diagnostics relating to the collection of the values,
|
||||
// but the values themselves may produce additional diagnostics when finally
|
||||
// parsed.
|
||||
func (m *Meta) collectVariableValues() (map[string]backend.UnparsedVariableValue, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
ret := map[string]backend.UnparsedVariableValue{}
|
||||
|
||||
// First we'll deal with environment variables, since they have the lowest
|
||||
// precedence.
|
||||
{
|
||||
env := os.Environ()
|
||||
for _, raw := range env {
|
||||
if !strings.HasPrefix(raw, terraform.VarEnvPrefix) {
|
||||
continue
|
||||
}
|
||||
raw = raw[len(terraform.VarEnvPrefix):] // trim the prefix
|
||||
|
||||
eq := strings.Index(raw, "=")
|
||||
if eq == -1 {
|
||||
// Seems invalid, so we'll ignore it.
|
||||
continue
|
||||
}
|
||||
|
||||
name := raw[:eq]
|
||||
rawVal := raw[eq+1:]
|
||||
|
||||
ret[name] = unparsedVariableValueString{
|
||||
str: rawVal,
|
||||
name: name,
|
||||
sourceType: terraform.ValueFromEnvVar,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Next up we have some implicit files that are loaded automatically
|
||||
// if they are present. There's the original terraform.tfvars
|
||||
// (DefaultVarsFilename) along with the later-added search for all files
|
||||
// ending in .auto.tfvars.
|
||||
if _, err := os.Stat(DefaultVarsFilename); err == nil {
|
||||
moreDiags := m.addVarsFromFile(DefaultVarsFilename, terraform.ValueFromFile, ret)
|
||||
diags = diags.Append(moreDiags)
|
||||
}
|
||||
if infos, err := ioutil.ReadDir("."); err == nil {
|
||||
// "infos" is already sorted by name, so we just need to filter it here.
|
||||
for _, info := range infos {
|
||||
name := info.Name()
|
||||
if !isAutoVarFile(name) {
|
||||
continue
|
||||
}
|
||||
moreDiags := m.addVarsFromFile(name, terraform.ValueFromFile, ret)
|
||||
diags = diags.Append(moreDiags)
|
||||
}
|
||||
}
|
||||
|
||||
// Finally we process values given explicitly on the command line, either
|
||||
// as individual literal settings or as additional files to read.
|
||||
for _, rawFlag := range m.variableArgs.AllItems() {
|
||||
switch rawFlag.Name {
|
||||
case "-var":
|
||||
// Value should be in the form "name=value", where value is a
|
||||
// raw string whose interpretation will depend on the variable's
|
||||
// parsing mode.
|
||||
raw := rawFlag.Value
|
||||
eq := strings.Index(raw, "=")
|
||||
if eq == -1 {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid -var option",
|
||||
fmt.Sprintf("The given -var option %q is not correctly specified. Must be a variable name and value separated by an equals sign, like -var=\"key=value\".", raw),
|
||||
))
|
||||
continue
|
||||
}
|
||||
name := raw[:eq]
|
||||
rawVal := raw[eq+1:]
|
||||
ret[name] = unparsedVariableValueString{
|
||||
str: rawVal,
|
||||
name: name,
|
||||
sourceType: terraform.ValueFromCLIArg,
|
||||
}
|
||||
|
||||
case "-var-file":
|
||||
moreDiags := m.addVarsFromFile(rawFlag.Value, terraform.ValueFromFile, ret)
|
||||
diags = diags.Append(moreDiags)
|
||||
|
||||
default:
|
||||
// Should never happen; always a bug in the code that built up
|
||||
// the contents of m.variableArgs.
|
||||
diags = diags.Append(fmt.Errorf("unsupported variable option name %q (this is a bug in Terraform)", rawFlag.Name))
|
||||
}
|
||||
}
|
||||
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
|
||||
func (m *Meta) addVarsFromFile(filename string, sourceType terraform.ValueSourceType, to map[string]backend.UnparsedVariableValue) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
src, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to read variables file",
|
||||
fmt.Sprintf("Given variables file %s does not exist.", filename),
|
||||
))
|
||||
} else {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to read variables file",
|
||||
fmt.Sprintf("Error while reading %s: %s.", filename, err),
|
||||
))
|
||||
}
|
||||
return diags
|
||||
}
|
||||
|
||||
loader, err := m.initConfigLoader()
|
||||
if err != nil {
|
||||
diags = diags.Append(err)
|
||||
return diags
|
||||
}
|
||||
|
||||
// Record the file source code for snippets in diagnostic messages.
|
||||
loader.Parser().ForceFileSource(filename, src)
|
||||
|
||||
var f *hcl.File
|
||||
if strings.HasSuffix(filename, ".json") {
|
||||
var hclDiags hcl.Diagnostics
|
||||
f, hclDiags = hcljson.Parse(src, filename)
|
||||
diags = diags.Append(hclDiags)
|
||||
if f == nil || f.Body == nil {
|
||||
return diags
|
||||
}
|
||||
} else {
|
||||
var hclDiags hcl.Diagnostics
|
||||
f, hclDiags = hclsyntax.ParseConfig(src, filename, hcl.Pos{Line: 1, Column: 1})
|
||||
diags = diags.Append(hclDiags)
|
||||
if f == nil || f.Body == nil {
|
||||
return diags
|
||||
}
|
||||
}
|
||||
|
||||
attrs, hclDiags := f.Body.JustAttributes()
|
||||
diags = diags.Append(hclDiags)
|
||||
|
||||
for name, attr := range attrs {
|
||||
to[name] = unparsedVariableValueExpression{
|
||||
expr: attr.Expr,
|
||||
sourceType: sourceType,
|
||||
}
|
||||
}
|
||||
return diags
|
||||
}
|
||||
|
||||
// unparsedVariableValueLiteral is a backend.UnparsedVariableValue
|
||||
// implementation that was actually already parsed (!). This is
|
||||
// intended to deal with expressions inside "tfvars" files.
|
||||
type unparsedVariableValueExpression struct {
|
||||
expr hcl.Expression
|
||||
sourceType terraform.ValueSourceType
|
||||
}
|
||||
|
||||
func (v unparsedVariableValueExpression) ParseVariableValue(mode configs.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
val, hclDiags := v.expr.Value(nil) // nil because no function calls or variable references are allowed here
|
||||
diags = diags.Append(hclDiags)
|
||||
|
||||
rng := tfdiags.SourceRangeFromHCL(v.expr.Range())
|
||||
|
||||
return &terraform.InputValue{
|
||||
Value: val,
|
||||
SourceType: v.sourceType,
|
||||
SourceRange: rng,
|
||||
}, diags
|
||||
}
|
||||
|
||||
// unparsedVariableValueString is a backend.UnparsedVariableValue
|
||||
// implementation that parses its value from a string. This can be used
|
||||
// to deal with values given directly on the command line and via environment
|
||||
// variables.
|
||||
type unparsedVariableValueString struct {
|
||||
str string
|
||||
name string
|
||||
sourceType terraform.ValueSourceType
|
||||
}
|
||||
|
||||
func (v unparsedVariableValueString) ParseVariableValue(mode configs.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
val, hclDiags := mode.Parse(v.name, v.str)
|
||||
diags = diags.Append(hclDiags)
|
||||
|
||||
return &terraform.InputValue{
|
||||
Value: val,
|
||||
SourceType: v.sourceType,
|
||||
}, diags
|
||||
}
|
|
@ -108,6 +108,15 @@ func (c *PlanCommand) Run(args []string) int {
|
|||
c.showDiagnostics(err)
|
||||
return 1
|
||||
}
|
||||
{
|
||||
var moreDiags tfdiags.Diagnostics
|
||||
opReq.Variables, moreDiags = c.collectVariableValues()
|
||||
diags = diags.Append(moreDiags)
|
||||
if moreDiags.HasErrors() {
|
||||
c.showDiagnostics(diags)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
// c.Backend above has a non-obvious side-effect of also populating
|
||||
// c.backendState, which is the state-shaped formulation of the effective
|
||||
|
|
|
@ -78,6 +78,15 @@ func (c *RefreshCommand) Run(args []string) int {
|
|||
c.showDiagnostics(err)
|
||||
return 1
|
||||
}
|
||||
{
|
||||
var moreDiags tfdiags.Diagnostics
|
||||
opReq.Variables, moreDiags = c.collectVariableValues()
|
||||
diags = diags.Append(moreDiags)
|
||||
if moreDiags.HasErrors() {
|
||||
c.showDiagnostics(diags)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
op, err := c.RunOperation(b, opReq)
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue