command: Various updates for the new backend package API

This is a rather-messy, complex change to get the "command" package
building again against the new backend API that was updated for
the new configuration loader.

A lot of this is mechanical rewriting to the new API, but
meta_config.go and meta_backend.go in particular saw some major
changes to interface with the new loader APIs and to deal with
the change in order of steps in the backend API.
This commit is contained in:
Martin Atkins 2018-03-27 15:31:05 -07:00
parent 5782357c28
commit ebafa51723
36 changed files with 1335 additions and 1161 deletions

View File

@ -275,3 +275,7 @@ const (
// performed at all.
OperationFailure OperationResult = 1
)
func (r OperationResult) ExitStatus() int {
return int(r)
}

View File

@ -7,12 +7,13 @@ import (
"sort"
"strings"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/tfdiags"
"github.com/hashicorp/go-getter"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/terraform"
)
@ -118,66 +119,75 @@ func (c *ApplyCommand) Run(args []string) int {
var diags tfdiags.Diagnostics
// Load the module if we don't have one yet (not running from plan)
var mod *module.Tree
var backendConfig *configs.Backend
if plan == nil {
var modDiags tfdiags.Diagnostics
mod, modDiags = c.Module(configPath)
diags = diags.Append(modDiags)
if modDiags.HasErrors() {
var configDiags tfdiags.Diagnostics
backendConfig, configDiags = c.loadBackendConfig(configPath)
diags = diags.Append(configDiags)
if configDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
}
var conf *config.Config
if mod != nil {
conf = mod.Config()
}
// Load the backend
b, err := c.Backend(&BackendOpts{
Config: conf,
b, beDiags := c.Backend(&BackendOpts{
Config: backendConfig,
Plan: plan,
})
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
diags = diags.Append(beDiags)
if beDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Before we delegate to the backend, we'll print any warning diagnostics
// we've accumulated here, since the backend will start fresh with its own
// diagnostics.
c.showDiagnostics(diags)
diags = nil
// Build the operation
opReq := c.Operation()
opReq.AutoApprove = autoApprove
opReq.Destroy = c.Destroy
opReq.DestroyForce = destroyForce
opReq.Module = mod
opReq.ConfigDir = configPath
opReq.Plan = plan
opReq.PlanRefresh = refresh
opReq.Type = backend.OperationTypeApply
op, err := c.RunOperation(b, opReq)
opReq.AutoApprove = autoApprove
opReq.DestroyForce = destroyForce
opReq.ConfigLoader, err = c.initConfigLoader()
if err != nil {
diags = diags.Append(err)
}
c.showDiagnostics(diags)
if diags.HasErrors() {
c.showDiagnostics(err)
return 1
}
if !c.Destroy {
// Get the right module that we used. If we ran a plan, then use
// that module.
if plan != nil {
mod = plan.Module
}
if outputs := outputsAsString(op.State, terraform.RootModulePath, mod.Config().Outputs, true); outputs != "" {
c.Ui.Output(c.Colorize().Color(outputs))
}
op, err := c.RunOperation(b, opReq)
if err != nil {
c.showDiagnostics(err)
return 1
}
if op.Result != backend.OperationSuccess {
return op.Result.ExitStatus()
}
return op.ExitCode
if !c.Destroy {
// TODO: Print outputs, once this is updated to use new config types.
/*
// Get the right module that we used. If we ran a plan, then use
// that module.
if plan != nil {
mod = plan.Module
}
if outputs := outputsAsString(op.State, terraform.RootModulePath, mod.Config().Outputs, true); outputs != "" {
c.Ui.Output(c.Colorize().Color(outputs))
}
*/
}
return op.Result.ExitStatus()
}
func (c *ApplyCommand) Help() string {

View File

@ -49,15 +49,15 @@ func (m *Meta) completePredictWorkspaceName() complete.Predictor {
return nil
}
cfg, err := m.Config(configPath)
if err != nil {
backendConfig, diags := m.loadBackendConfig(configPath)
if diags.HasErrors() {
return nil
}
b, err := m.Backend(&BackendOpts{
Config: cfg,
b, diags := m.Backend(&BackendOpts{
Config: backendConfig,
})
if err != nil {
if diags.HasErrors() {
return nil
}

View File

@ -552,9 +552,9 @@ func testBackendState(t *testing.T, s *terraform.State, c int) (*terraform.State
state := terraform.NewState()
state.Backend = &terraform.BackendState{
Type: "http",
Config: map[string]interface{}{"address": srv.URL},
Hash: 2529831861221416334,
Type: "http",
ConfigRaw: json.RawMessage(fmt.Sprintf(`{"address":%q}`, srv.URL)),
Hash: 2529831861221416334,
}
return state, srv

View File

@ -2,11 +2,9 @@ package command
import (
"bufio"
"fmt"
"strings"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/helper/wrappedstreams"
"github.com/hashicorp/terraform/repl"
"github.com/hashicorp/terraform/tfdiags"
@ -41,43 +39,46 @@ func (c *ConsoleCommand) Run(args []string) int {
var diags tfdiags.Diagnostics
// Load the module
mod, diags := c.Module(configPath)
backendConfig, backendDiags := c.loadBackendConfig(configPath)
diags = diags.Append(backendDiags)
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
var conf *config.Config
if mod != nil {
conf = mod.Config()
}
// Load the backend
b, err := c.Backend(&BackendOpts{
Config: conf,
b, backendDiags := c.Backend(&BackendOpts{
Config: backendConfig,
})
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// We require a local backend
local, ok := b.(backend.Local)
if !ok {
c.showDiagnostics(diags) // in case of any warnings in here
c.Ui.Error(ErrUnsupportedLocalOp)
return 1
}
// Build the operation
opReq := c.Operation()
opReq.Module = mod
opReq.ConfigDir = configPath
opReq.ConfigLoader, err = c.initConfigLoader()
if err != nil {
diags = diags.Append(err)
c.showDiagnostics(diags)
return 1
}
// Get the context
ctx, _, err := local.Context(opReq)
if err != nil {
c.Ui.Error(err.Error())
ctx, _, ctxDiags := local.Context(opReq)
diags = diags.Append(ctxDiags)
if ctxDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}

View File

@ -8,8 +8,6 @@ import (
"github.com/hashicorp/terraform/tfdiags"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/dag"
"github.com/hashicorp/terraform/terraform"
)
@ -60,55 +58,47 @@ func (c *GraphCommand) Run(args []string) int {
var diags tfdiags.Diagnostics
// Load the module
var mod *module.Tree
if plan == nil {
var modDiags tfdiags.Diagnostics
mod, modDiags = c.Module(configPath)
diags = diags.Append(modDiags)
if modDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
}
var conf *config.Config
if mod != nil {
conf = mod.Config()
backendConfig, backendDiags := c.loadBackendConfig(configPath)
diags = diags.Append(backendDiags)
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Load the backend
b, err := c.Backend(&BackendOpts{
Config: conf,
Plan: plan,
b, backendDiags := c.Backend(&BackendOpts{
Config: backendConfig,
})
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// We require a local backend
local, ok := b.(backend.Local)
if !ok {
c.showDiagnostics(diags) // in case of any warnings in here
c.Ui.Error(ErrUnsupportedLocalOp)
return 1
}
// Building a graph may require config module to be present, even if it's
// empty.
if mod == nil && plan == nil {
mod = module.NewEmptyTree()
}
// Build the operation
opReq := c.Operation()
opReq.Module = mod
opReq.ConfigDir = configPath
opReq.ConfigLoader, err = c.initConfigLoader()
opReq.Plan = plan
if err != nil {
diags = diags.Append(err)
c.showDiagnostics(diags)
return 1
}
// Get the context
ctx, _, err := local.Context(opReq)
if err != nil {
c.Ui.Error(err.Error())
ctx, _, ctxDiags := local.Context(opReq)
diags = diags.Append(ctxDiags)
if ctxDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}

View File

@ -10,7 +10,7 @@ import (
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
)
@ -76,37 +76,34 @@ func (c *ImportCommand) Run(args []string) int {
var diags tfdiags.Diagnostics
// Load the module
var mod *module.Tree
if configPath != "" {
if empty, _ := config.IsEmptyDir(configPath); empty {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "No Terraform configuration files",
Detail: fmt.Sprintf(
"The directory %s does not contain any Terraform configuration files (.tf or .tf.json). To specify a different configuration directory, use the -config=\"...\" command line option.",
configPath,
),
})
c.showDiagnostics(diags)
return 1
}
if !c.dirIsConfigPath(configPath) {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "No Terraform configuration files",
Detail: fmt.Sprintf(
"The directory %s does not contain any Terraform configuration files (.tf or .tf.json). To specify a different configuration directory, use the -config=\"...\" command line option.",
configPath,
),
})
c.showDiagnostics(diags)
return 1
}
var modDiags tfdiags.Diagnostics
mod, modDiags = c.Module(configPath)
diags = diags.Append(modDiags)
if modDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Load the full config, so we can verify that the target resource is
// already configured.
config, configDiags := c.loadConfig(configPath)
diags = diags.Append(configDiags)
if configDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Verify that the given address points to something that exists in config.
// This is to reduce the risk that a typo in the resource address will
// import something that Terraform will want to immediately destroy on
// the next plan, and generally acts as a reassurance of user intent.
targetMod := mod.Child(addr.Path)
if targetMod == nil {
targetConfig := config.Descendent(addr.Path)
if targetConfig == nil {
modulePath := addr.WholeModuleAddress().String()
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
@ -119,10 +116,11 @@ func (c *ImportCommand) Run(args []string) int {
c.showDiagnostics(diags)
return 1
}
rcs := targetMod.Config().Resources
var rc *config.Resource
targetMod := targetConfig.Module
rcs := targetMod.ManagedResources
var rc *configs.ManagedResource
for _, thisRc := range rcs {
if addr.MatchesConfig(targetMod, thisRc) {
if addr.MatchesManagedResourceConfig(addr.Path, thisRc) {
rc = thisRc
break
}
@ -154,11 +152,12 @@ func (c *ImportCommand) Run(args []string) int {
}
// Load the backend
b, err := c.Backend(&BackendOpts{
Config: mod.Config(),
b, backendDiags := c.Backend(&BackendOpts{
Config: config.Module.Backend,
})
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
@ -175,12 +174,19 @@ func (c *ImportCommand) Run(args []string) int {
// Build the operation
opReq := c.Operation()
opReq.Module = mod
opReq.ConfigDir = configPath
opReq.ConfigLoader, err = c.initConfigLoader()
if err != nil {
diags = diags.Append(err)
c.showDiagnostics(diags)
return 1
}
// Get the context
ctx, state, err := local.Context(opReq)
if err != nil {
c.Ui.Error(err.Error())
ctx, state, ctxDiags := local.Context(opReq)
diags = diags.Append(ctxDiags)
if ctxDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}

View File

@ -7,16 +7,19 @@ import (
"sort"
"strings"
"github.com/posener/complete"
multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/terraform/backend"
backendinit "github.com/hashicorp/terraform/backend/init"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/helper/variables"
"github.com/hashicorp/terraform/config/configschema"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/plugin/discovery"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
"github.com/posener/complete"
"github.com/zclconf/go-cty/cty"
)
// InitCommand is a Command implementation that takes a Terraform
@ -37,9 +40,9 @@ type InitCommand struct {
func (c *InitCommand) Run(args []string) int {
var flagFromModule string
var flagBackend, flagGet, flagUpgrade bool
var flagConfigExtra map[string]interface{}
var flagPluginPath FlagStringSlice
var flagVerifyPlugins bool
flagConfigExtra := newRawFlags("-backend-config")
args, err := c.Meta.process(args, false)
if err != nil {
@ -47,7 +50,7 @@ func (c *InitCommand) Run(args []string) int {
}
cmdFlags := c.flagSet("init")
cmdFlags.BoolVar(&flagBackend, "backend", true, "")
cmdFlags.Var((*variables.FlagAny)(&flagConfigExtra), "backend-config", "")
cmdFlags.Var(flagConfigExtra, "backend-config", "")
cmdFlags.StringVar(&flagFromModule, "from-module", "", "copy the source of the given module into the directory before init")
cmdFlags.BoolVar(&flagGet, "get", true, "")
cmdFlags.BoolVar(&c.getPlugins, "get-plugins", true, "")
@ -64,6 +67,8 @@ func (c *InitCommand) Run(args []string) int {
return 1
}
var diags tfdiags.Diagnostics
if len(flagPluginPath) > 0 {
c.pluginPath = flagPluginPath
c.getPlugins = false
@ -129,18 +134,23 @@ func (c *InitCommand) Run(args []string) int {
)))
header = true
s := module.NewStorage("", c.Services)
if err := s.GetModule(path, src); err != nil {
c.Ui.Error(fmt.Sprintf("Error copying source module: %s", err))
hooks := uiModuleInstallHooks{
Ui: c.Ui,
ShowLocalPaths: false, // since they are in a weird location for init
}
initDiags := c.initDirFromModule(path, src, hooks)
diags = diags.Append(initDiags)
if initDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
}
// If our directory is empty, then we're done. We can't get or setup
// the backend with an empty directory.
empty, err := config.IsEmptyDir(path)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error checking configuration: %s", err))
if empty, err := config.IsEmptyDir(path); err != nil {
diags = diags.Append(fmt.Errorf("Error checking configuration: %s", err))
return 1
}
if empty {
@ -153,32 +163,34 @@ func (c *InitCommand) Run(args []string) int {
// If we're performing a get or loading the backend, then we perform
// some extra tasks.
if flagGet || flagBackend {
conf, err := c.Config(path)
if err != nil {
config, confDiags := c.loadSingleModule(path)
diags = diags.Append(confDiags)
if confDiags.HasErrors() {
// Since this may be the user's first ever interaction with Terraform,
// we'll provide some additional context in this case.
c.Ui.Error(strings.TrimSpace(errInitConfigError))
c.showDiagnostics(err)
c.showDiagnostics(diags)
return 1
}
// If we requested downloading modules and have modules in the config
if flagGet && len(conf.Modules) > 0 {
if flagGet && len(config.ModuleCalls) > 0 {
header = true
getMode := module.GetModeGet
if flagUpgrade {
getMode = module.GetModeUpdate
c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
"[reset][bold]Upgrading modules...")))
c.Ui.Output(c.Colorize().Color(fmt.Sprintf("[reset][bold]Upgrading modules...")))
} else {
c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
"[reset][bold]Initializing modules...")))
c.Ui.Output(c.Colorize().Color(fmt.Sprintf("[reset][bold]Initializing modules...")))
}
if err := getModules(&c.Meta, path, getMode); err != nil {
c.Ui.Error(fmt.Sprintf(
"Error downloading modules: %s", err))
hooks := uiModuleInstallHooks{
Ui: c.Ui,
ShowLocalPaths: true,
}
instDiags := c.installModules(path, flagUpgrade, hooks)
diags = diags.Append(instDiags)
if instDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
}
@ -188,21 +200,52 @@ func (c *InitCommand) Run(args []string) int {
if flagBackend {
header = true
var backendSchema *configschema.Block
// Only output that we're initializing a backend if we have
// something in the config. We can be UNSETTING a backend as well
// in which case we choose not to show this.
if conf.Terraform != nil && conf.Terraform.Backend != nil {
c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
"\n[reset][bold]Initializing the backend...")))
if config.Backend != nil {
c.Ui.Output(c.Colorize().Color(fmt.Sprintf("\n[reset][bold]Initializing the backend...")))
backendType := config.Backend.Type
bf := backendinit.Backend(backendType)
if bf == nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unsupported backend type",
Detail: fmt.Sprintf("There is no backend type named %q.", backendType),
Subject: &config.Backend.TypeRange,
})
c.showDiagnostics()
return 1
}
b := bf()
backendSchema = b.ConfigSchema()
}
var backendConfigOverride hcl.Body
if backendSchema != nil {
var overrideDiags tfdiags.Diagnostics
backendConfigOverride, overrideDiags = c.backendConfigOverrideBody(flagConfigExtra, backendSchema)
diags = diags.Append(overrideDiags)
if overrideDiags.HasErrors() {
c.showDiagnostics()
return 1
}
}
opts := &BackendOpts{
Config: conf,
ConfigExtra: flagConfigExtra,
Init: true,
Config: config.Backend,
ConfigOverride: backendConfigOverride,
Init: true,
}
if back, err = c.Backend(opts); err != nil {
c.Ui.Error(err.Error())
var backDiags tfdiags.Diagnostics
back, backDiags = c.Backend(opts)
diags = diags.Append(backDiags)
if backDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
}
@ -213,8 +256,9 @@ func (c *InitCommand) Run(args []string) int {
// instantiate one. This might fail if it wasn't already initalized
// by a previous run, so we must still expect that "back" may be nil
// in code that follows.
back, err = c.Backend(nil)
if err != nil {
var backDiags tfdiags.Diagnostics
back, backDiags = c.Backend(nil)
if backDiags.HasErrors() {
// This is fine. We'll proceed with no backend, then.
back = nil
}
@ -269,6 +313,75 @@ func (c *InitCommand) Run(args []string) int {
return 0
}
// backendConfigOverrideBody interprets the raw values of -backend-config
// arguments into a hcl Body that should override the backend settings given
// in the configuration.
//
// If the result is nil then no override needs to be provided.
//
// If the returned diagnostics contains errors then the returned body may be
// incomplete or invalid.
func (c *InitCommand) backendConfigOverrideBody(flags rawFlags, schema *configschema.Block) (hcl.Body, tfdiags.Diagnostics) {
items := flags.AllItems()
if len(items) == 0 {
return nil, nil
}
var ret hcl.Body
var diags tfdiags.Diagnostics
synthVals := make(map[string]cty.Value)
mergeBody := func(newBody hcl.Body) {
if ret == nil {
ret = newBody
} else {
ret = configs.MergeBodies(ret, newBody)
}
}
flushVals := func() {
if len(synthVals) == 0 {
return
}
newBody := configs.SynthBody("-backend-config=...", synthVals)
mergeBody(newBody)
synthVals = make(map[string]cty.Value)
}
for _, item := range items {
eq := strings.Index(item.Value, "=")
if eq == -1 {
// The value is interpreted as a filename.
newBody, fileDiags := c.loadHCLFile(item.Value)
diags = diags.Append(fileDiags)
flushVals() // deal with any accumulated individual values first
mergeBody(newBody)
} else {
name := item.Value[:eq]
rawValue := item.Value[eq+1:]
attrS := schema.Attributes[name]
if attrS == nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid backend configuration argument",
fmt.Sprintf("The backend configuration argument %q given on the command line is not expected for the selected backend type.", name),
))
continue
}
value, valueDiags := configValueFromCLI(item.String(), rawValue, attrS.Type)
diags = diags.Append(valueDiags)
if valueDiags.HasErrors() {
continue
}
synthVals[name] = value
}
}
flushVals()
return ret, diags
}
// Load the complete module tree, and fetch any missing providers.
// This method outputs its own Ui.
func (c *InitCommand) getProviders(path string, state *terraform.State, upgrade bool) error {

View File

@ -11,6 +11,9 @@ import (
"strings"
"testing"
"github.com/hashicorp/terraform/configs"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/backend/local"
"github.com/hashicorp/terraform/helper/copy"
"github.com/hashicorp/terraform/plugin/discovery"
@ -312,8 +315,8 @@ func TestInit_backendConfigFile(t *testing.T) {
// Read our saved backend config and verify we have our settings
state := testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
if v := state.Backend.Config["path"]; v != "hello" {
t.Fatalf("bad: %#v", v)
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
}
}
@ -344,8 +347,8 @@ func TestInit_backendConfigFileChange(t *testing.T) {
// Read our saved backend config and verify we have our settings
state := testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
if v := state.Backend.Config["path"]; v != "hello" {
t.Fatalf("bad: %#v", v)
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
}
}
@ -371,8 +374,8 @@ func TestInit_backendConfigKV(t *testing.T) {
// Read our saved backend config and verify we have our settings
state := testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
if v := state.Backend.Config["path"]; v != "hello" {
t.Fatalf("bad: %#v", v)
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
}
}
@ -419,11 +422,13 @@ func TestInit_backendReinitWithExtra(t *testing.T) {
m := testMetaBackend(t, nil)
opts := &BackendOpts{
ConfigExtra: map[string]interface{}{"path": "hello"},
Init: true,
ConfigOverride: configs.SynthBody("synth", map[string]cty.Value{
"path": cty.StringVal("hello"),
}),
Init: true,
}
b, err := m.backendConfig(opts)
_, cHash, err := m.backendConfig(opts)
if err != nil {
t.Fatal(err)
}
@ -443,28 +448,23 @@ func TestInit_backendReinitWithExtra(t *testing.T) {
// Read our saved backend config and verify we have our settings
state := testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
if v := state.Backend.Config["path"]; v != "hello" {
t.Fatalf("bad: %#v", v)
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
}
if state.Backend.Hash != b.Hash {
if state.Backend.Hash != cHash {
t.Fatal("mismatched state and config backend hashes")
}
if state.Backend.Rehash() != b.Rehash() {
t.Fatal("mismatched state and config re-hashes")
}
// init again and make sure nothing changes
if code := c.Run(args); code != 0 {
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
}
state = testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
if v := state.Backend.Config["path"]; v != "hello" {
t.Fatalf("bad: %#v", v)
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
}
if state.Backend.Hash != b.Hash {
if state.Backend.Hash != cHash {
t.Fatal("mismatched state and config backend hashes")
}
}
@ -490,8 +490,8 @@ func TestInit_backendReinitConfigToExtra(t *testing.T) {
// Read our saved backend config and verify we have our settings
state := testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
if v := state.Backend.Config["path"]; v != "foo" {
t.Fatalf("bad: %#v", v)
if got, want := string(state.Backend.ConfigRaw), `{"path":"foo"}`; got != want {
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
}
backendHash := state.Backend.Hash

View File

@ -261,6 +261,15 @@ func (m *Meta) StdinPiped() bool {
return fi.Mode()&os.ModeNamedPipe != 0
}
// RunOperation executes the given operation on the given backend, blocking
// until that operation completes or is inteerrupted, and then returns
// the RunningOperation object representing the completed or
// aborted operation that is, despite the name, no longer running.
//
// An error is returned if the operation either fails to start or is cancelled.
// If the operation runs to completion then no error is returned even if the
// operation itself is unsuccessful. Use the "Result" field of the
// returned operation object to recognize operation-level failure.
func (m *Meta) RunOperation(b backend.Enhanced, opReq *backend.Operation) (*backend.RunningOperation, error) {
op, err := b.Operation(context.Background(), opReq)
if err != nil {
@ -302,10 +311,6 @@ func (m *Meta) RunOperation(b backend.Enhanced, opReq *backend.Operation) (*back
// operation completed normally
}
if op.Err != nil {
return op, op.Err
}
return op, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,9 @@ import (
"strings"
"testing"
"github.com/hashicorp/terraform/configs"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/backend"
backendInit "github.com/hashicorp/terraform/backend/init"
backendLocal "github.com/hashicorp/terraform/backend/local"
@ -28,9 +31,9 @@ func TestMetaBackend_emptyDir(t *testing.T) {
// Get the backend
m := testMetaBackend(t, nil)
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Write some state
@ -98,9 +101,9 @@ func TestMetaBackend_emptyWithDefaultState(t *testing.T) {
// Get the backend
m := testMetaBackend(t, nil)
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -171,9 +174,9 @@ func TestMetaBackend_emptyWithExplicitState(t *testing.T) {
m.statePath = statePath
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -230,9 +233,9 @@ func TestMetaBackend_emptyLegacyRemote(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -297,9 +300,9 @@ func TestMetaBackend_configureNew(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -366,9 +369,9 @@ func TestMetaBackend_configureNewWithState(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -444,9 +447,9 @@ func TestMetaBackend_configureNewWithoutCopy(t *testing.T) {
m.input = false
// init the backend
_, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
_, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Verify the state is where we expect
@ -493,9 +496,9 @@ func TestMetaBackend_configureNewWithStateNoMigrate(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -537,9 +540,9 @@ func TestMetaBackend_configureNewWithStateExisting(t *testing.T) {
m.forceInitCopy = true
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -611,9 +614,9 @@ func TestMetaBackend_configureNewWithStateExistingNoMigrate(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -685,9 +688,9 @@ func TestMetaBackend_configureNewLegacy(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -779,9 +782,9 @@ func TestMetaBackend_configureNewLegacyCopy(t *testing.T) {
m.forceInitCopy = true
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -872,9 +875,9 @@ func TestMetaBackend_configuredUnchanged(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -919,9 +922,9 @@ func TestMetaBackend_configuredChange(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -1007,9 +1010,9 @@ func TestMetaBackend_reconfigureChange(t *testing.T) {
m.reconfigure = true
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -1051,9 +1054,9 @@ func TestMetaBackend_configuredChangeCopy(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -1105,9 +1108,9 @@ func TestMetaBackend_configuredChangeCopy_singleState(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -1160,9 +1163,9 @@ func TestMetaBackend_configuredChangeCopy_multiToSingleDefault(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -1215,9 +1218,9 @@ func TestMetaBackend_configuredChangeCopy_multiToSingle(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -1286,9 +1289,9 @@ func TestMetaBackend_configuredChangeCopy_multiToSingleCurrentEnv(t *testing.T)
}
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -1342,9 +1345,9 @@ func TestMetaBackend_configuredChangeCopy_multiToMulti(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check resulting states
@ -1584,9 +1587,9 @@ func TestMetaBackend_configuredUnset(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -1668,9 +1671,9 @@ func TestMetaBackend_configuredUnsetCopy(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -1747,9 +1750,9 @@ func TestMetaBackend_configuredUnchangedLegacy(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -1848,9 +1851,9 @@ func TestMetaBackend_configuredUnchangedLegacyCopy(t *testing.T) {
m.forceInitCopy = true
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -1951,9 +1954,9 @@ func TestMetaBackend_configuredChangedLegacy(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -2051,9 +2054,9 @@ func TestMetaBackend_configuredChangedLegacyCopyBackend(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -2154,9 +2157,9 @@ func TestMetaBackend_configuredChangedLegacyCopyLegacy(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -2257,9 +2260,9 @@ func TestMetaBackend_configuredChangedLegacyCopyBoth(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -2360,9 +2363,9 @@ func TestMetaBackend_configuredUnsetWithLegacyNoCopy(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -2450,9 +2453,9 @@ func TestMetaBackend_configuredUnsetWithLegacyCopyBackend(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -2548,9 +2551,9 @@ func TestMetaBackend_configuredUnsetWithLegacyCopyLegacy(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -2646,9 +2649,9 @@ func TestMetaBackend_configuredUnsetWithLegacyCopyBoth(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -2747,9 +2750,9 @@ func TestMetaBackend_planLocal(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Plan: plan})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Plan: plan})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -2844,9 +2847,9 @@ func TestMetaBackend_planLocalStatePath(t *testing.T) {
m.stateOutPath = statePath
// Get the backend
b, err := m.Backend(&BackendOpts{Plan: plan})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Plan: plan})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -2930,9 +2933,9 @@ func TestMetaBackend_planLocalMatch(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Plan: plan})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Plan: plan})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -3023,12 +3026,12 @@ func TestMetaBackend_planLocalMismatchLineage(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
_, err := m.Backend(&BackendOpts{Plan: plan})
if err == nil {
_, diags := m.Backend(&BackendOpts{Plan: plan})
if !diags.HasErrors() {
t.Fatal("should have error")
}
if !strings.Contains(err.Error(), "lineage") {
t.Fatalf("bad: %s", err)
if !strings.Contains(diags[0].Description().Summary, "lineage") {
t.Fatalf("wrong diagnostic message %q; want something containing \"lineage\"", diags[0].Description().Summary)
}
// Verify our local state didn't change
@ -3075,12 +3078,12 @@ func TestMetaBackend_planLocalNewer(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
_, err := m.Backend(&BackendOpts{Plan: plan})
if err == nil {
_, diags := m.Backend(&BackendOpts{Plan: plan})
if !diags.HasErrors() {
t.Fatal("should have error")
}
if !strings.Contains(err.Error(), "older") {
t.Fatalf("bad: %s", err)
if !strings.Contains(diags[0].Description().Summary, "older") {
t.Fatalf("wrong diagnostic message %q; want something containing \"older\"", diags[0].Description().Summary)
}
// Verify our local state didn't change
@ -3130,9 +3133,9 @@ func TestMetaBackend_planBackendEmptyDir(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Plan: plan})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Plan: plan})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -3232,9 +3235,9 @@ func TestMetaBackend_planBackendMatch(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Plan: plan})
if err != nil {
t.Fatalf("bad: %s", err)
b, diags := m.Backend(&BackendOpts{Plan: plan})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -3337,12 +3340,12 @@ func TestMetaBackend_planBackendMismatchLineage(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
_, err := m.Backend(&BackendOpts{Plan: plan})
if err == nil {
_, diags := m.Backend(&BackendOpts{Plan: plan})
if !diags.HasErrors() {
t.Fatal("should have error")
}
if !strings.Contains(err.Error(), "lineage") {
t.Fatalf("bad: %s", err)
if !strings.Contains(diags[0].Description().Summary, "lineage") {
t.Fatalf("wrong diagnostic message %q; want something containing \"lineage\"", diags[0].Description().Summary)
}
// Verify our local state didn't change
@ -3395,9 +3398,9 @@ func TestMetaBackend_planLegacy(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, err := m.Backend(&BackendOpts{Plan: plan})
if err != nil {
t.Fatalf("err: %s", err)
b, diags := m.Backend(&BackendOpts{Plan: plan})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
@ -3476,52 +3479,46 @@ func TestMetaBackend_configureWithExtra(t *testing.T) {
defer os.RemoveAll(td)
defer testChdir(t, td)()
extras := map[string]interface{}{"path": "hello"}
extras := map[string]cty.Value{"path": cty.StringVal("hello")}
m := testMetaBackend(t, nil)
opts := &BackendOpts{
ConfigExtra: extras,
Init: true,
ConfigOverride: configs.SynthBody("synth", extras),
Init: true,
}
backendCfg, err := m.backendConfig(opts)
_, cHash, err := m.backendConfig(opts)
if err != nil {
t.Fatal(err)
}
// init the backend
_, err = m.Backend(&BackendOpts{
ConfigExtra: extras,
Init: true,
_, diags := m.Backend(&BackendOpts{
ConfigOverride: configs.SynthBody("synth", extras),
Init: true,
})
if err != nil {
t.Fatalf("bad: %s", err)
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// Check the state
s := testStateRead(t, filepath.Join(DefaultDataDir, backendLocal.DefaultStateFilename))
if s.Backend.Hash != backendCfg.Hash {
s := testStateRead(t, filepath.Join(DefaultDataDir, backendlocal.DefaultStateFilename))
if s.Backend.Hash != cHash {
t.Fatal("mismatched state and config backend hashes")
}
if s.Backend.Rehash() == s.Backend.Hash {
t.Fatal("saved hash should not match actual hash")
}
if s.Backend.Rehash() != backendCfg.Rehash() {
t.Fatal("mismatched state and config re-hashes")
}
// init the backend again with the same options
m = testMetaBackend(t, nil)
_, err = m.Backend(&BackendOpts{
ConfigExtra: extras,
Init: true,
ConfigOverride: configs.SynthBody("synth", extras),
Init: true,
})
if err != nil {
t.Fatalf("bad: %s", err)
}
// Check the state
s = testStateRead(t, filepath.Join(DefaultDataDir, backendLocal.DefaultStateFilename))
if s.Backend.Hash != backendCfg.Hash {
s = testStateRead(t, filepath.Join(DefaultDataDir, backendlocal.DefaultStateFilename))
if s.Backend.Hash != cHash {
t.Fatal("mismatched state and config backend hashes")
}
}
@ -3557,11 +3554,9 @@ func TestMetaBackend_localDoesNotDeleteLocal(t *testing.T) {
m := testMetaBackend(t, nil)
m.forceInitCopy = true
// init the backend
_, err = m.Backend(&BackendOpts{
Init: true,
})
if err != nil {
t.Fatalf("bad: %s", err)
_, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
// check that we can read the state
@ -3599,15 +3594,15 @@ func TestMetaBackend_configToExtra(t *testing.T) {
}
// init the backend again with the options
extras := map[string]interface{}{"path": "hello"}
extras := map[string]cty.Value{"path": cty.StringVal("hello")}
m = testMetaBackend(t, nil)
m.forceInitCopy = true
_, err = m.Backend(&BackendOpts{
ConfigExtra: extras,
Init: true,
_, diags := m.Backend(&BackendOpts{
ConfigOverride: configs.SynthBody("synth", extras),
Init: true,
})
if err != nil {
t.Fatalf("bad: %s", err)
if diags.HasErrors() {
t.Fatal(diags.Err())
}
s = testStateRead(t, filepath.Join(DefaultDataDir, backendLocal.DefaultStateFilename))

View File

@ -4,11 +4,18 @@ import (
"fmt"
"os"
"path/filepath"
"sort"
"github.com/hashicorp/hcl2/hcl/hclsyntax"
"github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/terraform/config/configschema"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/configs/configload"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
)
// normalizePath normalizes a given path so that it is, if possible, relative
@ -80,6 +87,23 @@ func (m *Meta) loadSingleModule(dir string) (*configs.Module, tfdiags.Diagnostic
return module, diags
}
// dirIsConfigPath checks if the given path is a directory that contains at
// least one Terraform configuration file (.tf or .tf.json), returning true
// if so.
//
// In the unlikely event that the underlying config loader cannot be initalized,
// this function optimistically returns true, assuming that the caller will
// then do some other operation that requires the config loader and get an
// error at that point.
func (m *Meta) dirIsConfigPath(dir string) bool {
loader, err := m.initConfigLoader()
if err != nil {
return true
}
return loader.IsConfigDir(dir)
}
// loadBackendConfig reads configuration from the given directory and returns
// the backend configuration defined by that module, if any. Nil is returned
// if the specified module does not have an explicit backend configuration.
@ -99,6 +123,41 @@ func (m *Meta) loadBackendConfig(rootDir string) (*configs.Backend, tfdiags.Diag
return mod.Backend, diags
}
// loadValuesFile loads a file that defines a single map of key/value pairs.
// This is the format used for "tfvars" files.
func (m *Meta) loadValuesFile(filename string) (map[string]cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
filename = m.normalizePath(filename)
loader, err := m.initConfigLoader()
if err != nil {
diags = diags.Append(err)
return nil, diags
}
vals, hclDiags := loader.Parser().LoadValuesFile(filename)
diags = diags.Append(hclDiags)
return vals, diags
}
// loadHCLFile reads an arbitrary HCL file and returns the unprocessed body
// representing its toplevel. Most callers should use one of the more
// specialized "load..." methods to get a higher-level representation.
func (m *Meta) loadHCLFile(filename string) (hcl.Body, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
filename = m.normalizePath(filename)
loader, err := m.initConfigLoader()
if err != nil {
diags = diags.Append(err)
return nil, diags
}
body, hclDiags := loader.Parser().LoadHCLFile(filename)
diags = diags.Append(hclDiags)
return body, diags
}
// installModules reads a root module from the given directory and attempts
// recursively install all of its descendent modules.
//
@ -172,6 +231,67 @@ func (m *Meta) loadVarsFile(filename string) (map[string]cty.Value, tfdiags.Diag
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.
//
// An error will be returned if input is disabled for this meta or if
// values cannot be obtained for some other operational reason. Errors are
// not returned for invalid input since the input loop itself will report
// that interactively.
//
// It is not guaranteed that the result will be valid, since certain attribute
// types and nested blocks are not supported for input.
//
// The given value must conform to the given schema. If not, this method will
// panic.
func (m *Meta) inputForSchema(given cty.Value, schema *configschema.Block) (cty.Value, error) {
if given.IsNull() || !given.IsKnown() {
// This is not reasonable input, but we'll tolerate it anyway and
// just pass it through for the caller to handle downstream.
return given, nil
}
givenVals := given.AsValueMap()
retVals := make(map[string]cty.Value, len(givenVals))
names := make([]string, 0, len(schema.Attributes))
for name, attrS := range schema.Attributes {
retVals[name] = givenVals[name]
if givenVal := givenVals[name]; attrS.Required && givenVal.IsNull() && attrS.Type.IsPrimitiveType() {
names = append(names, name)
}
}
sort.Strings(names)
input := m.UIInput()
for _, name := range names {
attrS := schema.Attributes[name]
for {
strVal, err := input.Input(&terraform.InputOpts{
Id: name,
Query: name,
Description: attrS.Description,
})
if err != nil {
return cty.UnknownVal(schema.ImpliedType()), fmt.Errorf("%s: %s", name, err)
}
val := cty.StringVal(strVal)
val, err = convert.Convert(val, attrS.Type)
if err != nil {
m.showDiagnostics(fmt.Errorf("Invalid value: %s", err))
continue
}
retVals[name] = val
break
}
}
return cty.ObjectVal(retVals), nil
}
// configSources returns the source cache from the receiver's config loader,
// which the caller must not modify.
//
@ -211,3 +331,87 @@ func (m *Meta) initConfigLoader() (*configload.Loader, error) {
}
return m.configLoader, nil
}
// configValueFromCLI parses a configuration value that was provided in a
// context in the CLI where only strings can be provided, such as on the
// command line or in an environment variable, and returns the resulting
// value.
func configValueFromCLI(synthFilename, rawValue string, wantType cty.Type) (cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
switch {
case wantType.IsPrimitiveType():
// Primitive types are handled as conversions from string.
val := cty.StringVal(rawValue)
var err error
val, err = convert.Convert(val, wantType)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid backend configuration value",
fmt.Sprintf("Invalid backend configuration argument %s: %s", synthFilename, err),
))
val = cty.DynamicVal // just so we return something valid-ish
}
return val, diags
default:
// Non-primitives are parsed as HCL expressions
src := []byte(rawValue)
expr, hclDiags := hclsyntax.ParseExpression(src, synthFilename, hcl.Pos{Line: 1, Column: 1})
diags = diags.Append(hclDiags)
if hclDiags.HasErrors() {
return cty.DynamicVal, diags
}
val, hclDiags := expr.Value(nil)
diags = diags.Append(hclDiags)
if hclDiags.HasErrors() {
val = cty.DynamicVal
}
return val, diags
}
}
// rawFlags is a flag.Value implementation that just appends raw flag
// names and values to a slice.
type rawFlags struct {
flagName string
items *[]rawFlag
}
func newRawFlags(flagName string) rawFlags {
return rawFlags{
flagName: flagName,
}
}
func (f rawFlags) AllItems() []rawFlag {
return *f.items
}
func (f rawFlags) Alias(flagName string) rawFlags {
return rawFlags{
flagName: flagName,
items: f.items,
}
}
func (f rawFlags) String() string {
return ""
}
func (f rawFlags) Set(str string) error {
*f.items = append(*f.items, rawFlag{
Name: f.flagName,
Value: str,
})
return nil
}
type rawFlag struct {
Name string
Value string
}
func (f rawFlag) String() string {
return fmt.Sprintf("%s=%q", f.Name, f.Value)
}

View File

@ -7,6 +7,8 @@ import (
"fmt"
"sort"
"strings"
"github.com/hashicorp/terraform/tfdiags"
)
// OutputCommand is a Command implementation that reads an output
@ -46,10 +48,13 @@ func (c *OutputCommand) Run(args []string) int {
name = args[0]
}
var diags tfdiags.Diagnostics
// Load the backend
b, err := c.Backend(nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
b, backendDiags := c.Backend(nil)
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}

View File

@ -5,8 +5,7 @@ import (
"strings"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/tfdiags"
)
@ -70,58 +69,62 @@ func (c *PlanCommand) Run(args []string) int {
var diags tfdiags.Diagnostics
// Load the module if we don't have one yet (not running from plan)
var mod *module.Tree
var backendConfig *configs.Backend
if plan == nil {
var modDiags tfdiags.Diagnostics
mod, modDiags = c.Module(configPath)
diags = diags.Append(modDiags)
if modDiags.HasErrors() {
var configDiags tfdiags.Diagnostics
backendConfig, configDiags = c.loadBackendConfig(configPath)
diags = diags.Append(configDiags)
if configDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
}
var conf *config.Config
if mod != nil {
conf = mod.Config()
}
// Load the backend
b, err := c.Backend(&BackendOpts{
Config: conf,
b, backendDiags := c.Backend(&BackendOpts{
Config: backendConfig,
Plan: plan,
})
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Emit any diagnostics we've accumulated before we delegate to the
// backend, since the backend will handle its own diagnostics internally.
c.showDiagnostics(diags)
diags = nil
// Build the operation
opReq := c.Operation()
opReq.Destroy = destroy
opReq.Module = mod
opReq.ModuleDepth = moduleDepth
opReq.ConfigDir = configPath
opReq.Plan = plan
opReq.PlanOutPath = outPath
opReq.PlanRefresh = refresh
opReq.Type = backend.OperationTypePlan
opReq.ConfigLoader, err = c.initConfigLoader()
if err != nil {
c.showDiagnostics(err)
return 1
}
// Perform the operation
op, err := c.RunOperation(b, opReq)
if err != nil {
diags = diags.Append(err)
}
c.showDiagnostics(diags)
if diags.HasErrors() {
c.showDiagnostics(err)
return 1
}
if op.Result != backend.OperationSuccess {
return op.Result.ExitStatus()
}
if detailed && !op.PlanEmpty {
return 2
}
return op.ExitCode
return op.Result.ExitStatus()
}
func (c *PlanCommand) Help() string {

View File

@ -5,7 +5,7 @@ import (
"sort"
"github.com/hashicorp/terraform/moduledeps"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
"github.com/xlab/treeprint"
)
@ -38,26 +38,22 @@ func (c *ProvidersCommand) Run(args []string) int {
return 1
}
// Load the config
root, diags := c.Module(configPath)
if diags.HasErrors() {
var diags tfdiags.Diagnostics
config, configDiags := c.loadConfig(configPath)
diags = diags.Append(configDiags)
if configDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
if root == nil {
c.Ui.Error(fmt.Sprintf(
"No configuration files found in the directory: %s\n\n"+
"This command requires configuration to run.",
configPath))
return 1
}
// Load the backend
b, err := c.Backend(&BackendOpts{
Config: root.Config(),
b, backendDiags := c.Backend(&BackendOpts{
Config: config.Module.Backend,
})
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
@ -73,9 +69,11 @@ func (c *ProvidersCommand) Run(args []string) int {
return 1
}
s := state.State()
depTree := terraform.ModuleTreeDependencies(root, s)
// FIXME: Restore this once the "terraform" package is updated to deal
// with HCL2 config types.
//s := state.State()
//depTree := terraform.ModuleTreeDependencies(config, s)
var depTree *moduledeps.Module
depTree.SortDescendents()
printRoot := treeprint.New()

View File

@ -5,8 +5,6 @@ import (
"strings"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
)
@ -42,54 +40,62 @@ func (c *RefreshCommand) Run(args []string) int {
var diags tfdiags.Diagnostics
// Load the module
mod, diags := c.Module(configPath)
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Check for user-supplied plugin path
if c.pluginPath, err = c.loadPluginPath(); err != nil {
c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err))
return 1
}
var conf *config.Config
if mod != nil {
conf = mod.Config()
backendConfig, configDiags := c.loadBackendConfig(configPath)
diags = diags.Append(configDiags)
if configDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Load the backend
b, err := c.Backend(&BackendOpts{
Config: conf,
b, backendDiags := c.Backend(&BackendOpts{
Config: backendConfig,
})
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Before we delegate to the backend, we'll print any warning diagnostics
// we've accumulated here, since the backend will start fresh with its own
// diagnostics.
c.showDiagnostics(diags)
diags = nil
// Build the operation
opReq := c.Operation()
opReq.Type = backend.OperationTypeRefresh
opReq.Module = mod
op, err := c.RunOperation(b, opReq)
opReq.ConfigDir = configPath
opReq.ConfigLoader, err = c.initConfigLoader()
if err != nil {
diags = diags.Append(err)
}
c.showDiagnostics(diags)
if diags.HasErrors() {
c.showDiagnostics(err)
return 1
}
// Output the outputs
if outputs := outputsAsString(op.State, terraform.RootModulePath, nil, true); outputs != "" {
c.Ui.Output(c.Colorize().Color(outputs))
op, err := c.RunOperation(b, opReq)
if err != nil {
c.showDiagnostics(err)
return 1
}
if op.Result != backend.OperationSuccess {
return op.Result.ExitStatus()
}
return 0
// TODO: Print outputs, once this is updated to use new config types.
/*
if outputs := outputsAsString(op.State, terraform.RootModulePath, nil, true); outputs != "" {
c.Ui.Output(c.Colorize().Color(outputs))
}
*/
return op.Result.ExitStatus()
}
func (c *RefreshCommand) Help() string {

View File

@ -71,9 +71,9 @@ func (c *ShowCommand) Run(args []string) int {
}
} else {
// Load the backend
b, err := c.Backend(nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
b, backendDiags := c.Backend(nil)
if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags)
return 1
}

View File

@ -30,9 +30,9 @@ func (c *StateListCommand) Run(args []string) int {
args = cmdFlags.Args()
// Load the backend
b, err := c.Backend(nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
b, backendDiags := c.Backend(nil)
if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags)
return 1
}

View File

@ -31,9 +31,9 @@ func (c *StateMeta) State() (state.State, error) {
}
} else {
// Load the backend
b, err := c.Backend(nil)
if err != nil {
return nil, err
b, backendDiags := c.Backend(nil)
if backendDiags.HasErrors() {
return nil, backendDiags.Err()
}
env := c.Workspace()
@ -44,10 +44,10 @@ func (c *StateMeta) State() (state.State, error) {
}
// Get a local backend
localRaw, err := c.Backend(&BackendOpts{ForceLocal: true})
if err != nil {
localRaw, backendDiags := c.Backend(&BackendOpts{ForceLocal: true})
if backendDiags.HasErrors() {
// This should never fail
panic(err)
panic(backendDiags.Err())
}
localB := localRaw.(*backendLocal.Local)
_, stateOutPath, _ = localB.StatePaths(env)

View File

@ -28,9 +28,9 @@ func (c *StatePullCommand) Run(args []string) int {
args = cmdFlags.Args()
// Load the backend
b, err := c.Backend(nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
b, backendDiags := c.Backend(nil)
if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags)
return 1
}

View File

@ -63,9 +63,9 @@ func (c *StatePushCommand) Run(args []string) int {
}
// Load the backend
b, err := c.Backend(nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
b, backendDiags := c.Backend(nil)
if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags)
return 1
}

View File

@ -30,9 +30,9 @@ func (c *StateShowCommand) Run(args []string) int {
args = cmdFlags.Args()
// Load the backend
b, err := c.Backend(nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
b, backendDiags := c.Backend(nil)
if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags)
return 1
}

View File

@ -64,9 +64,9 @@ func (c *TaintCommand) Run(args []string) int {
}
// Load the backend
b, err := c.Backend(nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
b, backendDiags := c.Backend(nil)
if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags)
return 1
}

View File

@ -6,6 +6,7 @@ import (
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli"
)
@ -46,18 +47,22 @@ func (c *UnlockCommand) Run(args []string) int {
return 1
}
conf, err := c.Config(configPath)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err))
var diags tfdiags.Diagnostics
backendConfig, backendDiags := c.loadBackendConfig(configPath)
diags = diags.Append(backendDiags)
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Load the backend
b, err := c.Backend(&BackendOpts{
Config: conf,
b, backendDiags := c.Backend(&BackendOpts{
Config: backendConfig,
})
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}

View File

@ -52,9 +52,9 @@ func (c *UntaintCommand) Run(args []string) int {
}
// Load the backend
b, err := c.Backend(nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
b, backendDiags := c.Backend(nil)
if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags)
return 1
}

View File

@ -6,6 +6,7 @@ import (
"strings"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
@ -49,19 +50,22 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int {
return 1
}
cfg, err := c.Config(configPath)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err))
var diags tfdiags.Diagnostics
backendConfig, backendDiags := c.loadBackendConfig(configPath)
diags = diags.Append(backendDiags)
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Load the backend
b, err := c.Backend(&BackendOpts{
Config: cfg,
b, backendDiags := c.Backend(&BackendOpts{
Config: backendConfig,
})
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}

View File

@ -2,9 +2,9 @@ package command
import (
"bytes"
"fmt"
"strings"
"github.com/hashicorp/terraform/tfdiags"
"github.com/posener/complete"
)
@ -34,19 +34,22 @@ func (c *WorkspaceListCommand) Run(args []string) int {
return 1
}
cfg, err := c.Config(configPath)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err))
var diags tfdiags.Diagnostics
backendConfig, backendDiags := c.loadBackendConfig(configPath)
diags = diags.Append(backendDiags)
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Load the backend
b, err := c.Backend(&BackendOpts{
Config: cfg,
b, backendDiags := c.Backend(&BackendOpts{
Config: backendConfig,
})
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}

View File

@ -8,6 +8,7 @@ import (
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
@ -59,18 +60,22 @@ func (c *WorkspaceNewCommand) Run(args []string) int {
return 1
}
conf, err := c.Config(configPath)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err))
var diags tfdiags.Diagnostics
backendConfig, backendDiags := c.loadBackendConfig(configPath)
diags = diags.Append(backendDiags)
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Load the backend
b, err := c.Backend(&BackendOpts{
Config: conf,
b, backendDiags := c.Backend(&BackendOpts{
Config: backendConfig,
})
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
@ -38,9 +39,13 @@ func (c *WorkspaceSelectCommand) Run(args []string) int {
return 1
}
conf, err := c.Config(configPath)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err))
var diags tfdiags.Diagnostics
backendConfig, backendDiags := c.loadBackendConfig(configPath)
diags = diags.Append(backendDiags)
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
current, isOverridden := c.WorkspaceOverridden()
@ -50,9 +55,14 @@ func (c *WorkspaceSelectCommand) Run(args []string) int {
}
// Load the backend
b, err := c.Backend(&BackendOpts{
Config: conf,
b, backendDiags := c.Backend(&BackendOpts{
Config: backendConfig,
})
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))

View File

@ -0,0 +1,38 @@
package configschema
// NoneRequired returns a deep copy of the receiver with any required
// attributes translated to optional.
func (b *Block) NoneRequired() *Block {
ret := &Block{}
if b.Attributes != nil {
ret.Attributes = make(map[string]*Attribute, len(b.Attributes))
}
for name, attrS := range b.Attributes {
ret.Attributes[name] = attrS.forceOptional()
}
if b.BlockTypes != nil {
ret.BlockTypes = make(map[string]*NestedBlock, len(b.BlockTypes))
}
for name, blockS := range b.BlockTypes {
ret.BlockTypes[name] = blockS.noneRequired()
}
return ret
}
func (b *NestedBlock) noneRequired() *NestedBlock {
ret := *b
ret.Block = *(ret.Block.NoneRequired())
ret.MinItems = 0
ret.MaxItems = 0
return &ret
}
func (a *Attribute) forceOptional() *Attribute {
ret := *a
ret.Optional = true
ret.Required = false
return &ret
}

View File

@ -2,6 +2,9 @@ package configs
import (
"github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hcldec"
"github.com/hashicorp/terraform/config/configschema"
"github.com/zclconf/go-cty/cty"
)
// Backend represents a "backend" block inside a "terraform" block in a module
@ -22,3 +25,31 @@ func decodeBackendBlock(block *hcl.Block) (*Backend, hcl.Diagnostics) {
DeclRange: block.DefRange,
}, nil
}
// Hash produces a hash value for the reciever that covers the type and the
// portions of the config that conform to the given schema.
//
// If the config does not conform to the schema then the result is not
// meaningful for comparison since it will be based on an incomplete result.
//
// As an exception, required attributes in the schema are treated as optional
// for the purpose of hashing, so that an incomplete configuration can still
// be hashed. Other errors, such as extraneous attributes, have no such special
// case.
func (b *Backend) Hash(schema *configschema.Block) int {
// Don't fail if required attributes are not set. Instead, we'll just
// hash them as nulls.
schema = schema.NoneRequired()
spec := schema.DecoderSpec()
val, _ := hcldec.Decode(b.Config, spec, nil)
if val == cty.NilVal {
val = cty.UnknownVal(schema.ImpliedType())
}
toHash := cty.TupleVal([]cty.Value{
cty.StringVal(b.Type),
val,
})
return toHash.Hash()
}

View File

@ -113,3 +113,21 @@ func (c *Config) AllModules() []*Config {
})
return ret
}
// Descendent returns the descendent config that has the given path beneath
// the receiver, or nil if there is no such module.
//
// The path traverses the static module tree, prior to any expansion to handle
// count and for_each arguments.
//
// An empty path will just return the receiver, and is therefore pointless.
func (c *Config) Descendent(path []string) *Config {
current := c
for _, name := range path {
current = current.Children[name]
if current == nil {
return nil
}
}
return current
}

View File

@ -8,7 +8,7 @@ import (
"strings"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/configs"
)
// ResourceAddress is a way of identifying an individual resource (or,
@ -109,30 +109,35 @@ func (r *ResourceAddress) WholeModuleAddress() *ResourceAddress {
}
}
// MatchesConfig returns true if the receiver matches the given
// configuration resource within the given configuration module.
// MatchesManagedResourceConfig returns true if the receiver matches the given
// configuration resource within the given _static_ module path. Note that
// the module path in a resource address is a _dynamic_ module path, and
// multiple dynamic resource paths may map to a single static path if
// count and for_each are in use on module calls.
//
// Since resource configuration blocks represent all of the instances of
// a multi-instance resource, the index of the address (if any) is not
// considered.
func (r *ResourceAddress) MatchesConfig(mod *module.Tree, rc *config.Resource) bool {
func (r *ResourceAddress) MatchesManagedResourceConfig(path []string, rc *configs.ManagedResource) bool {
if r.HasResourceSpec() {
if r.Mode != rc.Mode || r.Type != rc.Type || r.Name != rc.Name {
if r.Mode != config.ManagedResourceMode {
return false
}
if r.Type != rc.Type || r.Name != rc.Name {
return false
}
}
addrPath := r.Path
cfgPath := mod.Path()
// normalize
if len(addrPath) == 0 {
addrPath = nil
}
if len(cfgPath) == 0 {
cfgPath = nil
if len(path) == 0 {
path = nil
}
return reflect.DeepEqual(addrPath, cfgPath)
return reflect.DeepEqual(addrPath, path)
}
// stateId returns the ID that this resource should be entered with

View File

@ -6,7 +6,7 @@ import (
"testing"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/configs"
)
func TestParseResourceAddressInternal(t *testing.T) {
@ -1046,16 +1046,17 @@ func TestResourceAddressWholeModuleAddress(t *testing.T) {
}
}
func TestResourceAddressMatchesConfig(t *testing.T) {
root := testModule(t, "empty-with-child-module")
child := root.Child([]string{"child"})
grandchild := root.Child([]string{"child", "grandchild"})
func TestResourceAddressMatchesManagedResourceConfig(t *testing.T) {
root := []string(nil)
child := []string{"child"}
grandchild := []string{"child", "grandchild"}
irrelevant := []string{"irrelevant"}
tests := []struct {
Addr *ResourceAddress
Module *module.Tree
Resource *config.Resource
Want bool
Addr *ResourceAddress
ModulePath []string
Resource *configs.ManagedResource
Want bool
}{
{
&ResourceAddress{
@ -1065,8 +1066,7 @@ func TestResourceAddressMatchesConfig(t *testing.T) {
Index: -1,
},
root,
&config.Resource{
Mode: config.ManagedResourceMode,
&configs.ManagedResource{
Type: "null_resource",
Name: "baz",
},
@ -1081,8 +1081,7 @@ func TestResourceAddressMatchesConfig(t *testing.T) {
Index: -1,
},
child,
&config.Resource{
Mode: config.ManagedResourceMode,
&configs.ManagedResource{
Type: "null_resource",
Name: "baz",
},
@ -1097,8 +1096,7 @@ func TestResourceAddressMatchesConfig(t *testing.T) {
Index: -1,
},
grandchild,
&config.Resource{
Mode: config.ManagedResourceMode,
&configs.ManagedResource{
Type: "null_resource",
Name: "baz",
},
@ -1110,8 +1108,7 @@ func TestResourceAddressMatchesConfig(t *testing.T) {
Index: -1,
},
child,
&config.Resource{
Mode: config.ManagedResourceMode,
&configs.ManagedResource{
Type: "null_resource",
Name: "baz",
},
@ -1123,8 +1120,7 @@ func TestResourceAddressMatchesConfig(t *testing.T) {
Index: -1,
},
grandchild,
&config.Resource{
Mode: config.ManagedResourceMode,
&configs.ManagedResource{
Type: "null_resource",
Name: "baz",
},
@ -1137,9 +1133,8 @@ func TestResourceAddressMatchesConfig(t *testing.T) {
Name: "baz",
Index: -1,
},
module.NewEmptyTree(),
&config.Resource{
Mode: config.ManagedResourceMode,
irrelevant,
&configs.ManagedResource{
Type: "null_resource",
Name: "baz",
},
@ -1152,9 +1147,8 @@ func TestResourceAddressMatchesConfig(t *testing.T) {
Name: "baz",
Index: -1,
},
module.NewEmptyTree(),
&config.Resource{
Mode: config.ManagedResourceMode,
irrelevant,
&configs.ManagedResource{
Type: "null_resource",
Name: "pizza",
},
@ -1167,9 +1161,8 @@ func TestResourceAddressMatchesConfig(t *testing.T) {
Name: "baz",
Index: -1,
},
module.NewEmptyTree(),
&config.Resource{
Mode: config.ManagedResourceMode,
irrelevant,
&configs.ManagedResource{
Type: "aws_instance",
Name: "baz",
},
@ -1184,8 +1177,7 @@ func TestResourceAddressMatchesConfig(t *testing.T) {
Index: -1,
},
child,
&config.Resource{
Mode: config.ManagedResourceMode,
&configs.ManagedResource{
Type: "null_resource",
Name: "baz",
},
@ -1200,8 +1192,7 @@ func TestResourceAddressMatchesConfig(t *testing.T) {
Index: -1,
},
grandchild,
&config.Resource{
Mode: config.ManagedResourceMode,
&configs.ManagedResource{
Type: "null_resource",
Name: "baz",
},
@ -1211,11 +1202,11 @@ func TestResourceAddressMatchesConfig(t *testing.T) {
for i, test := range tests {
t.Run(fmt.Sprintf("%02d-%s", i, test.Addr), func(t *testing.T) {
got := test.Addr.MatchesConfig(test.Module, test.Resource)
got := test.Addr.MatchesManagedResourceConfig(test.ModulePath, test.Resource)
if got != test.Want {
t.Errorf(
"wrong result\naddr: %s\nmod: %#v\nrsrc: %#v\ngot: %#v\nwant: %#v",
test.Addr, test.Module.Path(), test.Resource, got, test.Want,
test.Addr, test.ModulePath, test.Resource, got, test.Want,
)
}
})

View File

@ -20,9 +20,12 @@ import (
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform/config"
"github.com/mitchellh/copystructure"
"github.com/hashicorp/terraform/config/configschema"
tfversion "github.com/hashicorp/terraform/version"
"github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"
"github.com/mitchellh/copystructure"
)
const (
@ -811,13 +814,9 @@ func (s *State) String() string {
// BackendState stores the configuration to connect to a remote backend.
type BackendState struct {
Type string `json:"type"` // Backend type
Config map[string]interface{} `json:"config"` // Backend raw config
// Hash is the hash code to uniquely identify the original source
// configuration. We use this to detect when there is a change in
// configuration even when "type" isn't changed.
Hash uint64 `json:"hash"`
Type string `json:"type"` // Backend type
ConfigRaw json.RawMessage `json:"config"` // Backend raw config
Hash int `json:"hash"` // Hash of portion of configuration from config files
}
// Empty returns true if BackendState has no state.
@ -825,25 +824,29 @@ func (s *BackendState) Empty() bool {
return s == nil || s.Type == ""
}
// Rehash returns a unique content hash for this backend's configuration
// as a uint64 value.
// The Hash stored in the backend state needs to match the config itself, but
// we need to compare the backend config after it has been combined with all
// options.
// This function must match the implementation used by config.Backend.
func (s *BackendState) Rehash() uint64 {
if s == nil {
return 0
}
// Config decodes the type-specific configuration object using the provided
// schema and returns the result as a cty.Value.
//
// An error is returned if the stored configuration does not conform to the
// given schema.
func (s *BackendState) Config(schema *configschema.Block) (cty.Value, error) {
ty := schema.ImpliedType()
return ctyjson.Unmarshal(s.ConfigRaw, ty)
}
cfg := config.Backend{
Type: s.Type,
RawConfig: &config.RawConfig{
Raw: s.Config,
},
// SetConfig replaces (in-place) the type-specific configuration object using
// the provided value and associated schema.
//
// An error is returned if the given value does not conform to the implied
// type of the schema.
func (s *BackendState) SetConfig(val cty.Value, schema *configschema.Block) error {
ty := schema.ImpliedType()
buf, err := ctyjson.Marshal(val, ty)
if err != nil {
return err
}
return cfg.Rehash()
s.ConfigRaw = buf
return nil
}
// RemoteState is used to track the information about a remote