2017-01-19 05:50:04 +01:00
package command
// This file contains all the Backend-related function calls on Meta,
// exported and private.
import (
2017-04-01 21:42:13 +02:00
"context"
2018-03-28 00:31:05 +02:00
"encoding/json"
2017-01-19 05:50:04 +01:00
"errors"
"fmt"
"log"
"path/filepath"
"strings"
2018-03-28 00:31:05 +02:00
"github.com/hashicorp/errwrap"
"github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hcldec"
2017-01-19 05:50:04 +01:00
"github.com/hashicorp/terraform/backend"
2018-03-28 00:31:05 +02:00
backendinit "github.com/hashicorp/terraform/backend/init"
backendlocal "github.com/hashicorp/terraform/backend/local"
2017-04-01 20:58:19 +02:00
"github.com/hashicorp/terraform/command/clistate"
2018-03-21 02:43:02 +01:00
"github.com/hashicorp/terraform/configs"
2017-01-19 05:50:04 +01:00
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform"
2018-03-28 00:31:05 +02:00
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"
2017-01-19 05:50:04 +01:00
)
// BackendOpts are the options used to initialize a backend.Backend.
type BackendOpts struct {
2018-03-21 02:43:02 +01:00
// Config is a representation of the backend configuration block given in
// the root module, or nil if no such block is present.
Config * configs . Backend
2017-01-19 05:50:04 +01:00
2018-03-28 00:31:05 +02:00
// ConfigOverride is an hcl.Body that, if non-nil, will be used with
// configs.MergeBodies to override the type-specific backend configuration
// arguments in Config.
ConfigOverride hcl . Body
2017-03-17 07:27:05 +01:00
2017-01-19 05:50:04 +01:00
// Plan is a plan that is being used. If this is set, the backend
// configuration and output configuration will come from this plan.
Plan * terraform . Plan
// Init should be set to true if initialization is allowed. If this is
// false, then any configuration that requires configuration will show
// an error asking the user to reinitialize.
Init bool
// ForceLocal will force a purely local backend, including state.
// You probably don't want to set this.
ForceLocal bool
}
// Backend initializes and returns the backend for this CLI session.
//
// The backend is used to perform the actual Terraform operations. This
// abstraction enables easily sliding in new Terraform behavior such as
// remote state storage, remote operations, etc. while allowing the CLI
// to remain mostly identical.
//
// This will initialize a new backend for each call, which can carry some
// overhead with it. Please reuse the returned value for optimal behavior.
//
// Only one backend should be used per Meta. This function is stateful
// and is unsafe to create multiple backends used at once. This function
// can be called multiple times with each backend being "live" (usable)
// one at a time.
2018-03-28 00:31:05 +02:00
func ( m * Meta ) Backend ( opts * BackendOpts ) ( backend . Enhanced , tfdiags . Diagnostics ) {
var diags tfdiags . Diagnostics
2017-01-19 05:50:04 +01:00
// If no opts are set, then initialize
if opts == nil {
opts = & BackendOpts { }
}
// Initialize a backend from the config unless we're forcing a purely
// local operation.
var b backend . Backend
if ! opts . ForceLocal {
// If we have a plan then, we get the the backend from there. Otherwise,
// the backend comes from the configuration.
if opts . Plan != nil {
2018-03-28 00:31:05 +02:00
var backendDiags tfdiags . Diagnostics
b , backendDiags = m . backendFromPlan ( opts )
diags = diags . Append ( backendDiags )
2017-01-19 05:50:04 +01:00
} else {
2018-03-28 00:31:05 +02:00
var backendDiags tfdiags . Diagnostics
b , backendDiags = m . backendFromConfig ( opts )
diags = diags . Append ( backendDiags )
2017-01-19 05:50:04 +01:00
}
2018-03-28 00:31:05 +02:00
if diags . HasErrors ( ) {
return nil , diags
2017-01-19 05:50:04 +01:00
}
log . Printf ( "[INFO] command: backend initialized: %T" , b )
}
2018-07-04 12:07:44 +02:00
// Setup the CLI opts we pass into backends that support it.
2017-02-28 19:58:29 +01:00
cliOpts := & backend . CLIOpts {
2017-09-09 02:14:37 +02:00
CLI : m . Ui ,
CLIColor : m . Colorize ( ) ,
2018-03-28 00:31:05 +02:00
ShowDiagnostics : m . showDiagnostics ,
2017-09-09 02:14:37 +02:00
StatePath : m . statePath ,
StateOutPath : m . stateOutPath ,
StateBackupPath : m . backupPath ,
ContextOpts : m . contextOpts ( ) ,
Input : m . Input ( ) ,
RunningInAutomation : m . RunningInAutomation ,
2017-02-28 19:58:29 +01:00
}
2018-07-04 12:07:44 +02:00
// Don't validate if we have a plan. Validation is normally harmless here,
2017-03-27 23:11:26 +02:00
// but validation requires interpolation, and `file()` function calls may
// not have the original files in the current execution context.
cliOpts . Validation = opts . Plan == nil
2017-02-28 19:58:29 +01:00
// If the backend supports CLI initialization, do it.
if cli , ok := b . ( backend . CLI ) ; ok {
if err := cli . CLIInit ( cliOpts ) ; err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf (
2017-02-28 19:58:29 +01:00
"Error initializing backend %T: %s\n\n" +
2018-03-28 00:31:05 +02:00
"This is a bug; please report it to the backend developer" ,
b , err ,
) )
return nil , diags
2017-02-28 19:58:29 +01:00
}
}
2017-01-19 05:50:04 +01:00
// If the result of loading the backend is an enhanced backend,
// then return that as-is. This works even if b == nil (it will be !ok).
if enhanced , ok := b . ( backend . Enhanced ) ; ok {
return enhanced , nil
}
// We either have a non-enhanced backend or no backend configured at
// all. In either case, we use local as our enhanced backend and the
// non-enhanced (if any) as the state backend.
if ! opts . ForceLocal {
log . Printf ( "[INFO] command: backend %T is not enhanced, wrapping in local" , b )
}
// Build the local backend
2018-07-04 12:11:35 +02:00
local := backendLocal . NewWithBackend ( b )
2017-02-28 19:58:29 +01:00
if err := local . CLIInit ( cliOpts ) ; err != nil {
// Local backend isn't allowed to fail. It would be a bug.
panic ( err )
}
return local , nil
2017-01-19 05:50:04 +01:00
}
2017-03-16 18:47:48 +01:00
// IsLocalBackend returns true if the backend is a local backend. We use this
// for some checks that require a remote backend.
func ( m * Meta ) IsLocalBackend ( b backend . Backend ) bool {
// Is it a local backend?
2018-07-04 12:07:44 +02:00
bLocal , ok := b . ( * backendLocal . Local )
2017-03-16 18:47:48 +01:00
// If it is, does it not have an alternate state backend?
if ok {
ok = bLocal . Backend == nil
}
return ok
}
2017-01-19 05:50:04 +01:00
// Operation initializes a new backend.Operation struct.
//
// This prepares the operation. After calling this, the caller is expected
// to modify fields of the operation such as Sequence to specify what will
// be called.
func ( m * Meta ) Operation ( ) * backend . Operation {
return & backend . Operation {
2017-04-01 21:42:13 +02:00
PlanOutBackend : m . backendState ,
2018-10-05 15:25:17 +02:00
Parallelism : m . parallelism ,
2017-04-01 21:42:13 +02:00
Targets : m . targets ,
UIIn : m . UIInput ( ) ,
2016-06-21 03:06:28 +02:00
UIOut : m . Ui ,
2018-10-05 15:25:17 +02:00
Variables : m . variables ,
2017-05-31 18:37:31 +02:00
Workspace : m . Workspace ( ) ,
2017-04-01 22:19:59 +02:00
LockState : m . stateLock ,
2017-04-01 21:42:13 +02:00
StateLockTimeout : m . stateLockTimeout ,
2017-01-19 05:50:04 +01:00
}
}
// backendConfig returns the local configuration for the backend
2018-03-28 00:31:05 +02:00
func ( m * Meta ) backendConfig ( opts * BackendOpts ) ( * configs . Backend , int , tfdiags . Diagnostics ) {
var diags tfdiags . Diagnostics
2017-05-01 23:47:53 +02:00
if opts . Config == nil {
// check if the config was missing, or just not required
2018-03-28 00:31:05 +02:00
conf , err := m . loadBackendConfig ( "." )
2017-01-19 05:50:04 +01:00
if err != nil {
2018-03-28 00:31:05 +02:00
return nil , 0 , err
2017-01-19 05:50:04 +01:00
}
2017-05-01 23:47:53 +02:00
if conf == nil {
log . Println ( "[INFO] command: no config, returning nil" )
2018-03-28 00:31:05 +02:00
return nil , 0 , nil
2017-01-19 05:50:04 +01:00
}
2018-01-17 03:05:26 +01:00
log . Println ( "[WARN] BackendOpts.Config not set, but config found" )
2017-05-01 23:47:53 +02:00
opts . Config = conf
2017-01-19 05:50:04 +01:00
}
2017-05-01 23:47:53 +02:00
c := opts . Config
2017-01-19 05:50:04 +01:00
2018-03-28 00:31:05 +02:00
if c == nil {
log . Println ( "[INFO] command: no explicit backend config" )
return nil , 0 , nil
2017-03-17 07:27:05 +01:00
}
2018-03-28 00:31:05 +02:00
bf := backendinit . Backend ( c . Type )
if bf == nil {
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid backend type" ,
Detail : fmt . Sprintf ( "There is no backend type named %q." , c . Type ) ,
Subject : & c . TypeRange ,
} )
return nil , 0 , diags
2017-01-19 05:50:04 +01:00
}
2018-03-28 00:31:05 +02:00
b := bf ( )
2017-01-19 05:50:04 +01:00
2018-03-28 00:31:05 +02:00
configSchema := b . ConfigSchema ( )
configBody := c . Config
configHash := c . Hash ( configSchema )
2017-01-19 05:50:04 +01:00
2018-03-28 00:31:05 +02:00
// If we have an override configuration body then we must apply it now.
if opts . ConfigOverride != nil {
configBody = configs . MergeBodies ( configBody , opts . ConfigOverride )
2017-01-19 05:50:04 +01:00
}
2018-03-28 00:31:05 +02:00
// We'll shallow-copy configs.Backend here so that we can replace the
// body without affecting others that hold this reference.
configCopy := * c
c . Config = configBody
return & configCopy , configHash , diags
2017-01-19 05:50:04 +01:00
}
// backendFromConfig returns the initialized (not configured) backend
// directly from the config/state..
//
// This function handles any edge cases around backend config loading. For
// example: legacy remote state, new config changes, backend type changes,
// etc.
//
// This function may query the user for input unless input is disabled, in
// which case this function will error.
2018-03-28 00:31:05 +02:00
func ( m * Meta ) backendFromConfig ( opts * BackendOpts ) ( backend . Backend , tfdiags . Diagnostics ) {
2017-01-19 05:50:04 +01:00
// Get the local backend configuration.
2018-03-28 00:31:05 +02:00
c , cHash , diags := m . backendConfig ( opts )
if diags . HasErrors ( ) {
return nil , diags
2017-03-16 19:47:59 +01:00
}
2017-01-19 05:50:04 +01:00
// Get the path to where we store a local cache of backend configuration
// if we're using a remote backend. This may not yet exist which means
// we haven't used a non-local backend before. That is okay.
statePath := filepath . Join ( m . DataDir ( ) , DefaultStateFilename )
sMgr := & state . LocalState { Path : statePath }
if err := sMgr . RefreshState ( ) ; err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( "Failed to load state: %s" , err ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
// Load the state, it must be non-nil for the tests below but can be empty
s := sMgr . State ( )
if s == nil {
log . Printf ( "[DEBUG] command: no data state file found for backend config" )
s = terraform . NewState ( )
}
2017-04-20 23:26:50 +02:00
// if we want to force reconfiguration of the backend, we set the backend
// state to nil on this copy. This will direct us through the correct
// configuration path in the switch statement below.
if m . reconfigure {
s . Backend = nil
}
2017-01-19 05:50:04 +01:00
// Upon return, we want to set the state we're using in-memory so that
// we can access it for commands.
m . backendState = nil
defer func ( ) {
if s := sMgr . State ( ) ; s != nil && ! s . Backend . Empty ( ) {
m . backendState = s . Backend
}
} ( )
// This giant switch statement covers all eight possible combinations
// of state settings between: configuring new backends, saved (previously-
// configured) backends, and legacy remote state.
switch {
// No configuration set at all. Pure local state.
case c == nil && s . Remote . Empty ( ) && s . Backend . Empty ( ) :
return nil , nil
// We're unsetting a backend (moving from backend => local)
case c == nil && s . Remote . Empty ( ) && ! s . Backend . Empty ( ) :
if ! opts . Init {
initReason := fmt . Sprintf (
"Unsetting the previously set backend %q" ,
s . Backend . Type )
m . backendInitRequired ( initReason )
2018-03-28 00:31:05 +02:00
diags = diags . Append ( errBackendInitRequired )
return nil , diags
2017-01-19 05:50:04 +01:00
}
2018-03-28 00:31:05 +02:00
return m . backend_c_r_S ( c , cHash , sMgr , true )
2017-01-19 05:50:04 +01:00
// We have a legacy remote state configuration but no new backend config
case c == nil && ! s . Remote . Empty ( ) && s . Backend . Empty ( ) :
return m . backend_c_R_s ( c , sMgr )
// We have a legacy remote state configuration simultaneously with a
// saved backend configuration while at the same time disabling backend
// configuration.
//
// This is a naturally impossible case: Terraform will never put you
// in this state, though it is theoretically possible through manual edits
case c == nil && ! s . Remote . Empty ( ) && ! s . Backend . Empty ( ) :
if ! opts . Init {
initReason := fmt . Sprintf (
"Unsetting the previously set backend %q" ,
s . Backend . Type )
m . backendInitRequired ( initReason )
2018-03-28 00:31:05 +02:00
diags = diags . Append ( errBackendInitRequired )
return nil , diags
2017-01-19 05:50:04 +01:00
}
2018-03-28 00:31:05 +02:00
return m . backend_c_R_S ( c , cHash , sMgr )
2017-01-19 05:50:04 +01:00
// Configuring a backend for the first time.
case c != nil && s . Remote . Empty ( ) && s . Backend . Empty ( ) :
if ! opts . Init {
initReason := fmt . Sprintf (
"Initial configuration of the requested backend %q" ,
c . Type )
m . backendInitRequired ( initReason )
2018-03-28 00:31:05 +02:00
diags = diags . Append ( errBackendInitRequired )
return nil , diags
2017-01-19 05:50:04 +01:00
}
2018-03-28 00:31:05 +02:00
return m . backend_C_r_s ( c , cHash , sMgr )
2017-01-19 05:50:04 +01:00
// Potentially changing a backend configuration
case c != nil && s . Remote . Empty ( ) && ! s . Backend . Empty ( ) :
// If our configuration is the same, then we're just initializing
// a previously configured remote backend.
2017-03-29 18:50:20 +02:00
if ! s . Backend . Empty ( ) {
2018-03-28 00:31:05 +02:00
storedHash := s . Backend . Hash
if storedHash == cHash {
return m . backend_C_r_S_unchanged ( c , cHash , sMgr )
2017-03-29 18:50:20 +02:00
}
2017-01-19 05:50:04 +01:00
}
if ! opts . Init {
initReason := fmt . Sprintf (
"Backend configuration changed for %q" ,
c . Type )
m . backendInitRequired ( initReason )
2018-03-28 00:31:05 +02:00
diags = diags . Append ( errBackendInitRequired )
return nil , diags
2017-01-19 05:50:04 +01:00
}
log . Printf (
"[WARN] command: backend config change! saved: %d, new: %d" ,
2017-03-16 19:47:59 +01:00
s . Backend . Hash , cHash )
2018-03-28 00:31:05 +02:00
return m . backend_C_r_S_changed ( c , cHash , sMgr , true )
2017-01-19 05:50:04 +01:00
// Configuring a backend for the first time while having legacy
// remote state. This is very possible if a Terraform user configures
// a backend prior to ever running Terraform on an old state.
case c != nil && ! s . Remote . Empty ( ) && s . Backend . Empty ( ) :
if ! opts . Init {
initReason := fmt . Sprintf (
"Initial configuration for backend %q" ,
c . Type )
m . backendInitRequired ( initReason )
2018-03-28 00:31:05 +02:00
diags = diags . Append ( errBackendInitRequired )
return nil , diags
2017-01-19 05:50:04 +01:00
}
return m . backend_C_R_s ( c , sMgr )
// Configuring a backend with both a legacy remote state set
// and a pre-existing backend saved.
case c != nil && ! s . Remote . Empty ( ) && ! s . Backend . Empty ( ) :
// If the hashes are the same, we have a legacy remote state with
// an unchanged stored backend state.
2018-03-28 00:31:05 +02:00
storedHash := s . Backend . Hash
if storedHash == cHash {
2017-01-19 05:50:04 +01:00
if ! opts . Init {
initReason := fmt . Sprintf (
"Legacy remote state found with configured backend %q" ,
c . Type )
m . backendInitRequired ( initReason )
2018-03-28 00:31:05 +02:00
diags = diags . Append ( errBackendInitRequired )
return nil , diags
2017-01-19 05:50:04 +01:00
}
return m . backend_C_R_S_unchanged ( c , sMgr , true )
}
if ! opts . Init {
initReason := fmt . Sprintf (
"Reconfiguring the backend %q" ,
c . Type )
m . backendInitRequired ( initReason )
2018-03-28 00:31:05 +02:00
diags = diags . Append ( errBackendInitRequired )
return nil , diags
2017-01-19 05:50:04 +01:00
}
// We have change in all three
return m . backend_C_R_S_changed ( c , sMgr )
default :
// This should be impossible since all state possibilties are
// tested above, but we need a default case anyways and we should
// protect against the scenario where a case is somehow removed.
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf (
2017-01-19 05:50:04 +01:00
"Unhandled backend configuration state. This is a bug. Please\n" +
"report this error with the following information.\n\n" +
"Config Nil: %v\n" +
"Saved Backend Empty: %v\n" +
"Legacy Remote Empty: %v\n" ,
2018-03-28 00:31:05 +02:00
c == nil , s . Backend . Empty ( ) , s . Remote . Empty ( ) ,
) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
}
// backendFromPlan loads the backend from a given plan file.
2018-03-28 00:31:05 +02:00
func ( m * Meta ) backendFromPlan ( opts * BackendOpts ) ( backend . Backend , tfdiags . Diagnostics ) {
2017-01-19 05:50:04 +01:00
if opts . Plan == nil {
panic ( "plan should not be nil" )
}
2018-03-28 00:31:05 +02:00
var diags tfdiags . Diagnostics
2017-01-19 05:50:04 +01:00
// We currently don't allow "-state" to be specified.
if m . statePath != "" {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf (
2017-01-19 05:50:04 +01:00
"State path cannot be specified with a plan file. The plan itself contains\n" +
"the state to use. If you wish to change that, please create a new plan\n" +
2018-03-28 00:31:05 +02:00
"and specify the state path when creating the plan." ,
) )
2017-01-19 05:50:04 +01:00
}
2017-03-16 23:42:32 +01:00
planBackend := opts . Plan . Backend
2017-01-19 05:50:04 +01:00
planState := opts . Plan . State
if planState == nil {
// The state can be nil, we just have to make it empty for the logic
// in this function.
planState = terraform . NewState ( )
}
// Validation only for non-local plans
2017-03-16 23:42:32 +01:00
local := planState . Remote . Empty ( ) && planBackend . Empty ( )
2017-01-19 05:50:04 +01:00
if ! local {
// We currently don't allow "-state-out" to be specified.
if m . stateOutPath != "" {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( strings . TrimSpace ( errBackendPlanStateFlag ) ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
}
// If we have a stateOutPath, we must also specify it as the
// input path so we can check it properly. We restore it after this
// function exits.
original := m . statePath
m . statePath = m . stateOutPath
defer func ( ) { m . statePath = original } ( )
var b backend . Backend
switch {
// No remote state at all, all local
2017-03-16 23:42:32 +01:00
case planState . Remote . Empty ( ) && planBackend . Empty ( ) :
log . Printf ( "[INFO] command: initializing local backend from plan (not set)" )
2017-01-19 05:50:04 +01:00
// Get the local backend
2018-03-28 00:31:05 +02:00
var backendDiags tfdiags . Diagnostics
b , backendDiags = m . Backend ( & BackendOpts { ForceLocal : true } )
diags = diags . Append ( backendDiags )
2017-01-19 05:50:04 +01:00
// New backend configuration set
2017-03-16 23:42:32 +01:00
case planState . Remote . Empty ( ) && ! planBackend . Empty ( ) :
log . Printf (
"[INFO] command: initializing backend from plan: %s" ,
planBackend . Type )
2018-03-28 00:31:05 +02:00
var backendDiags tfdiags . Diagnostics
b , backendDiags = m . backendInitFromSaved ( planBackend )
diags = diags . Append ( backendDiags )
2017-01-19 05:50:04 +01:00
// Legacy remote state set
2017-03-16 23:42:32 +01:00
case ! planState . Remote . Empty ( ) && planBackend . Empty ( ) :
log . Printf (
"[INFO] command: initializing legacy remote backend from plan: %s" ,
planState . Remote . Type )
2017-01-19 05:50:04 +01:00
// Write our current state to an inmemory state just so that we
// have it in the format of state.State
inmem := & state . InmemState { }
inmem . WriteState ( planState )
// Get the backend through the normal means of legacy state
2018-03-28 00:31:05 +02:00
var moreDiags tfdiags . Diagnostics
b , moreDiags = m . backend_c_R_s ( nil , inmem )
diags = diags . Append ( moreDiags )
2017-01-19 05:50:04 +01:00
// Both set, this can't happen in a plan.
2017-03-16 23:42:32 +01:00
case ! planState . Remote . Empty ( ) && ! planBackend . Empty ( ) :
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( strings . TrimSpace ( errBackendPlanBoth ) ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
// If we had an error, return that
2018-03-28 00:31:05 +02:00
if diags . HasErrors ( ) {
return nil , diags
2017-01-19 05:50:04 +01:00
}
2017-05-31 02:13:43 +02:00
env := m . Workspace ( )
2017-02-28 19:13:03 +01:00
2017-01-19 05:50:04 +01:00
// Get the state so we can determine the effect of using this plan
2017-02-28 19:13:03 +01:00
realMgr , err := b . State ( env )
2017-01-19 05:50:04 +01:00
if err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( "Error reading state: %s" , err ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
2017-02-09 21:35:49 +01:00
2017-04-01 21:42:13 +02:00
if m . stateLock {
2018-02-23 17:28:47 +01:00
stateLocker := clistate . NewLocker ( context . Background ( ) , m . stateLockTimeout , m . Ui , m . Colorize ( ) )
if err := stateLocker . Lock ( realMgr , "backend from plan" ) ; err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( "Error locking state: %s" , err ) )
return nil , diags
2017-04-01 21:42:13 +02:00
}
2018-02-23 17:28:47 +01:00
defer stateLocker . Unlock ( nil )
2017-02-09 21:35:49 +01:00
}
2017-01-19 05:50:04 +01:00
if err := realMgr . RefreshState ( ) ; err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( "Error reading state: %s" , err ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
real := realMgr . State ( )
if real != nil {
// If they're not the same lineage, don't allow this
if ! real . SameLineage ( planState ) {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( strings . TrimSpace ( errBackendPlanLineageDiff ) ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
// Compare ages
comp , err := real . CompareAges ( planState )
if err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( "Error comparing state ages for safety: %s" , err ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
switch comp {
case terraform . StateAgeEqual :
// State ages are equal, this is perfect
case terraform . StateAgeReceiverOlder :
// Real state is somehow older, this is okay.
case terraform . StateAgeReceiverNewer :
// If we have an older serial it is a problem but if we have a
// differing serial but are still identical, just let it through.
if real . Equal ( planState ) {
2018-03-28 00:31:05 +02:00
log . Printf ( "[WARN] command: state in plan has older serial, but Equal is true" )
2017-01-19 05:50:04 +01:00
break
}
// The real state is newer, this is not allowed.
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf (
2017-01-19 05:50:04 +01:00
strings . TrimSpace ( errBackendPlanOlder ) ,
2018-03-28 00:31:05 +02:00
planState . Serial , real . Serial ,
) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
}
// Write the state
newState := opts . Plan . State . DeepCopy ( )
if newState != nil {
newState . Remote = nil
newState . Backend = nil
}
2017-02-09 21:35:49 +01:00
// realMgr locked above
2017-01-19 05:50:04 +01:00
if err := realMgr . WriteState ( newState ) ; err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( "Error writing state: %s" , err ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
if err := realMgr . PersistState ( ) ; err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( "Error writing state: %s" , err ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
2018-03-28 00:31:05 +02:00
return b , diags
2017-01-19 05:50:04 +01:00
}
//-------------------------------------------------------------------
// Backend Config Scenarios
//
// The functions below cover handling all the various scenarios that
// can exist when loading a backend. They are named in the format of
// "backend_C_R_S" where C, R, S may be upper or lowercase. Lowercase
// means it is false, uppercase means it is true. The full set of eight
// possible cases is handled.
//
// The fields are:
//
// * C - Backend configuration is set and changed in TF files
// * R - Legacy remote state is set
// * S - Backend configuration is set in the state
//
//-------------------------------------------------------------------
// Unconfiguring a backend (moving from backend => local).
2018-03-28 00:31:05 +02:00
func ( m * Meta ) backend_c_r_S ( c * configs . Backend , cHash int , sMgr state . State , output bool ) ( backend . Backend , tfdiags . Diagnostics ) {
2017-01-19 05:50:04 +01:00
s := sMgr . State ( )
// Get the backend type for output
backendType := s . Backend . Type
2017-12-18 17:11:09 +01:00
m . Ui . Output ( fmt . Sprintf ( strings . TrimSpace ( outputBackendMigrateLocal ) , s . Backend . Type ) )
2017-01-19 05:50:04 +01:00
2017-12-18 17:11:09 +01:00
// Grab a purely local backend to get the local state if it exists
2018-03-28 00:31:05 +02:00
localB , diags := m . Backend ( & BackendOpts { ForceLocal : true } )
if diags . HasErrors ( ) {
return nil , diags
2017-12-18 17:11:09 +01:00
}
2017-02-28 19:13:03 +01:00
2017-12-18 17:11:09 +01:00
// Initialize the configured backend
2018-03-28 00:31:05 +02:00
b , moreDiags := m . backend_C_r_S_unchanged ( c , cHash , sMgr )
diags = diags . Append ( moreDiags )
if moreDiags . HasErrors ( ) {
return nil , diags
2017-12-18 17:11:09 +01:00
}
2017-01-19 05:50:04 +01:00
2017-12-18 17:11:09 +01:00
// Perform the migration
2018-03-28 00:31:05 +02:00
err := m . backendMigrateState ( & backendMigrateOpts {
2017-12-18 17:11:09 +01:00
OneType : s . Backend . Type ,
TwoType : "local" ,
One : b ,
Two : localB ,
} )
if err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( err )
return nil , diags
2017-01-19 05:50:04 +01:00
}
// Remove the stored metadata
s . Backend = nil
if err := sMgr . WriteState ( s ) ; err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( strings . TrimSpace ( errBackendClearSaved ) , err ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
if err := sMgr . PersistState ( ) ; err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( strings . TrimSpace ( errBackendClearSaved ) , err ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
if output {
m . Ui . Output ( m . Colorize ( ) . Color ( fmt . Sprintf (
"[reset][green]\n\n" +
strings . TrimSpace ( successBackendUnset ) , backendType ) ) )
}
// Return no backend
2018-03-28 00:31:05 +02:00
return nil , diags
2017-01-19 05:50:04 +01:00
}
// Legacy remote state
2018-03-28 00:31:05 +02:00
func ( m * Meta ) backend_c_R_s ( c * configs . Backend , sMgr state . State ) ( backend . Backend , tfdiags . Diagnostics ) {
var diags tfdiags . Diagnostics
2017-01-19 05:50:04 +01:00
2018-03-28 00:31:05 +02:00
m . Ui . Error ( strings . TrimSpace ( errBackendLegacy ) + "\n" )
2017-01-19 05:50:04 +01:00
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( "Cannot initialize legacy remote state" ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
// Unsetting backend, saved backend, legacy remote state
2018-03-28 00:31:05 +02:00
func ( m * Meta ) backend_c_R_S ( c * configs . Backend , cHash int , sMgr state . State ) ( backend . Backend , tfdiags . Diagnostics ) {
var diags tfdiags . Diagnostics
2017-01-19 05:50:04 +01:00
2018-03-28 00:31:05 +02:00
m . Ui . Error ( strings . TrimSpace ( errBackendLegacy ) + "\n" )
2017-01-19 05:50:04 +01:00
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( "Cannot initialize legacy remote state" ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
// Configuring a backend for the first time with legacy remote state.
2018-03-28 00:31:05 +02:00
func ( m * Meta ) backend_C_R_s ( c * configs . Backend , sMgr state . State ) ( backend . Backend , tfdiags . Diagnostics ) {
var diags tfdiags . Diagnostics
2017-01-19 05:50:04 +01:00
2018-03-28 00:31:05 +02:00
m . Ui . Error ( strings . TrimSpace ( errBackendLegacy ) + "\n" )
2017-01-19 05:50:04 +01:00
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( "Cannot initialize legacy remote state" ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
// Configuring a backend for the first time.
2018-03-28 00:31:05 +02:00
func ( m * Meta ) backend_C_r_s ( c * configs . Backend , cHash int , sMgr state . State ) ( backend . Backend , tfdiags . Diagnostics ) {
2017-01-19 05:50:04 +01:00
// Get the backend
2018-03-28 00:31:05 +02:00
b , configVal , diags := m . backendInitFromConfig ( c )
if diags . HasErrors ( ) {
return nil , diags
2017-01-19 05:50:04 +01:00
}
// Grab a purely local backend to get the local state if it exists
2018-03-28 00:31:05 +02:00
localB , localBDiags := m . Backend ( & BackendOpts { ForceLocal : true } )
if localBDiags . HasErrors ( ) {
diags = diags . Append ( localBDiags )
return nil , diags
2017-01-19 05:50:04 +01:00
}
2017-02-28 19:13:03 +01:00
2018-08-28 14:08:47 +02:00
workspaces , err := localB . States ( )
2017-01-19 05:50:04 +01:00
if err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( errBackendLocalRead , err ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
2018-03-28 00:31:05 +02:00
if err := localState . RefreshState ( ) ; err != nil {
diags = diags . Append ( fmt . Errorf ( errBackendLocalRead , err ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
2018-08-28 14:08:47 +02:00
if len ( localStates ) > 0 {
2017-01-19 05:50:04 +01:00
// Perform the migration
err = m . backendMigrateState ( & backendMigrateOpts {
OneType : "local" ,
TwoType : c . Type ,
2017-03-01 19:59:17 +01:00
One : localB ,
Two : b ,
2017-01-19 05:50:04 +01:00
} )
if err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( err )
return nil , diags
2017-01-19 05:50:04 +01:00
}
2017-03-31 21:21:32 +02:00
// we usually remove the local state after migration to prevent
// confusion, but adding a default local backend block to the config
// can get us here too. Don't delete our state if the old and new paths
// are the same.
erase := true
2018-07-04 12:07:44 +02:00
if newLocalB , ok := b . ( * backendLocal . Local ) ; ok {
if localB , ok := localB . ( * backendLocal . Local ) ; ok {
2017-03-31 21:21:32 +02:00
if newLocalB . StatePath == localB . StatePath {
erase = false
}
}
2017-01-19 05:50:04 +01:00
}
2017-03-31 21:21:32 +02:00
if erase {
2018-03-28 00:31:05 +02:00
// We always delete the local state, unless that was our new state too.
if err := localState . WriteState ( nil ) ; err != nil {
diags = diags . Append ( fmt . Errorf ( errBackendMigrateLocalDelete , err ) )
return nil , diags
}
if err := localState . PersistState ( ) ; err != nil {
diags = diags . Append ( fmt . Errorf ( errBackendMigrateLocalDelete , err ) )
return nil , diags
2017-03-31 21:21:32 +02:00
}
2017-01-19 05:50:04 +01:00
}
}
2017-04-01 21:42:13 +02:00
if m . stateLock {
2018-02-23 17:28:47 +01:00
stateLocker := clistate . NewLocker ( context . Background ( ) , m . stateLockTimeout , m . Ui , m . Colorize ( ) )
if err := stateLocker . Lock ( sMgr , "backend from plan" ) ; err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( "Error locking state: %s" , err ) )
return nil , diags
2017-04-01 21:42:13 +02:00
}
2018-02-23 17:28:47 +01:00
defer stateLocker . Unlock ( nil )
2017-02-09 21:35:49 +01:00
}
2018-03-28 00:31:05 +02:00
configJSON , err := ctyjson . Marshal ( configVal , b . ConfigSchema ( ) . ImpliedType ( ) )
if err != nil {
diags = diags . Append ( fmt . Errorf ( "Can't serialize backend configuration as JSON: %s" , err ) )
return nil , diags
}
2017-01-19 05:50:04 +01:00
// Store the metadata in our saved state location
s := sMgr . State ( )
if s == nil {
s = terraform . NewState ( )
}
s . Backend = & terraform . BackendState {
2018-03-28 00:31:05 +02:00
Type : c . Type ,
ConfigRaw : json . RawMessage ( configJSON ) ,
Hash : cHash ,
2017-01-19 05:50:04 +01:00
}
2017-02-09 21:35:49 +01:00
2017-01-19 05:50:04 +01:00
if err := sMgr . WriteState ( s ) ; err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( errBackendWriteSaved , err ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
if err := sMgr . PersistState ( ) ; err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( errBackendWriteSaved , err ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
m . Ui . Output ( m . Colorize ( ) . Color ( fmt . Sprintf (
2017-06-19 16:24:43 +02:00
"[reset][green]\n" + strings . TrimSpace ( successBackendSet ) , s . Backend . Type ) ) )
2017-01-19 05:50:04 +01:00
// Return the backend
2018-03-28 00:31:05 +02:00
return b , diags
2017-01-19 05:50:04 +01:00
}
// Changing a previously saved backend.
2018-03-28 00:31:05 +02:00
func ( m * Meta ) backend_C_r_S_changed ( c * configs . Backend , cHash int , sMgr state . State , output bool ) ( backend . Backend , tfdiags . Diagnostics ) {
2017-01-19 05:50:04 +01:00
if output {
// Notify the user
m . Ui . Output ( m . Colorize ( ) . Color ( fmt . Sprintf (
"[reset]%s\n\n" ,
strings . TrimSpace ( outputBackendReconfigure ) ) ) )
}
2017-02-09 21:35:49 +01:00
// Get the old state
s := sMgr . State ( )
2017-01-19 05:50:04 +01:00
// Get the backend
2018-03-28 00:31:05 +02:00
b , configVal , diags := m . backendInitFromConfig ( c )
if diags . HasErrors ( ) {
return nil , diags
2017-01-19 05:50:04 +01:00
}
2017-12-20 23:50:37 +01:00
// no need to confuse the user if the backend types are the same
if s . Backend . Type != c . Type {
m . Ui . Output ( strings . TrimSpace ( fmt . Sprintf ( outputBackendMigrateChange , s . Backend . Type , c . Type ) ) )
}
2017-01-19 05:50:04 +01:00
2017-12-18 17:11:09 +01:00
// Grab the existing backend
2018-03-28 00:31:05 +02:00
oldB , oldBDiags := m . backend_C_r_S_unchanged ( c , cHash , sMgr )
diags = diags . Append ( oldBDiags )
if oldBDiags . HasErrors ( ) {
return nil , diags
2017-12-18 17:11:09 +01:00
}
2017-01-19 05:50:04 +01:00
2017-12-18 17:11:09 +01:00
// Perform the migration
2018-03-28 00:31:05 +02:00
err := m . backendMigrateState ( & backendMigrateOpts {
2017-12-18 17:11:09 +01:00
OneType : s . Backend . Type ,
TwoType : c . Type ,
One : oldB ,
Two : b ,
} )
if err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( err )
return nil , diags
2017-01-19 05:50:04 +01:00
}
2017-04-01 21:42:13 +02:00
if m . stateLock {
2018-02-23 17:28:47 +01:00
stateLocker := clistate . NewLocker ( context . Background ( ) , m . stateLockTimeout , m . Ui , m . Colorize ( ) )
if err := stateLocker . Lock ( sMgr , "backend from plan" ) ; err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( "Error locking state: %s" , err ) )
return nil , diags
2017-04-01 21:42:13 +02:00
}
2018-02-23 17:28:47 +01:00
defer stateLocker . Unlock ( nil )
2017-02-09 21:35:49 +01:00
}
2018-03-28 00:31:05 +02:00
configJSON , err := ctyjson . Marshal ( configVal , b . ConfigSchema ( ) . ImpliedType ( ) )
if err != nil {
diags = diags . Append ( fmt . Errorf ( "Can't serialize backend configuration as JSON: %s" , err ) )
return nil , diags
}
2017-01-19 05:50:04 +01:00
// Update the backend state
2017-02-09 21:35:49 +01:00
s = sMgr . State ( )
2017-01-19 05:50:04 +01:00
if s == nil {
s = terraform . NewState ( )
}
s . Backend = & terraform . BackendState {
2018-03-28 00:31:05 +02:00
Type : c . Type ,
ConfigRaw : json . RawMessage ( configJSON ) ,
Hash : cHash ,
2017-01-19 05:50:04 +01:00
}
2017-02-09 21:35:49 +01:00
2017-01-19 05:50:04 +01:00
if err := sMgr . WriteState ( s ) ; err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( errBackendWriteSaved , err ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
if err := sMgr . PersistState ( ) ; err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( errBackendWriteSaved , err ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
if output {
m . Ui . Output ( m . Colorize ( ) . Color ( fmt . Sprintf (
2017-06-19 16:24:43 +02:00
"[reset][green]\n" + strings . TrimSpace ( successBackendSet ) , s . Backend . Type ) ) )
2017-01-19 05:50:04 +01:00
}
2018-03-28 00:31:05 +02:00
return b , diags
2017-01-19 05:50:04 +01:00
}
// Initiailizing an unchanged saved backend
2018-03-28 00:31:05 +02:00
func ( m * Meta ) backend_C_r_S_unchanged ( c * configs . Backend , cHash int , sMgr state . State ) ( backend . Backend , tfdiags . Diagnostics ) {
var diags tfdiags . Diagnostics
2017-01-19 05:50:04 +01:00
s := sMgr . State ( )
2017-03-29 21:51:24 +02:00
// it's possible for a backend to be unchanged, and the config itself to
2017-04-26 16:10:04 +02:00
// have changed by moving a parameter from the config to `-backend-config`
2017-03-29 21:51:24 +02:00
// In this case we only need to update the Hash.
2018-03-28 00:31:05 +02:00
if c != nil && s . Backend . Hash != cHash {
s . Backend . Hash = cHash
2017-03-29 21:51:24 +02:00
if err := sMgr . WriteState ( s ) ; err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( err )
return nil , diags
2017-03-29 21:51:24 +02:00
}
}
2017-01-19 05:50:04 +01:00
// Get the backend
2018-07-04 12:07:44 +02:00
f := backendInit . Backend ( s . Backend . Type )
2017-02-22 20:17:27 +01:00
if f == nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( strings . TrimSpace ( errBackendSavedUnknown ) , s . Backend . Type ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
b := f ( )
2018-03-28 00:31:05 +02:00
// The configuration saved in the working directory state file is used
// in this case, since it will contain any additional values that
// were provided via -backend-config arguments on terraform init.
schema := b . ConfigSchema ( )
configVal , err := s . Backend . Config ( schema )
2017-01-19 05:50:04 +01:00
if err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( tfdiags . Sourceless (
tfdiags . Error ,
"Failed to decode current backend config" ,
fmt . Sprintf ( "The backend configuration created by the most recent run of \"terraform init\" could not be decoded: %s. The configuration may have been initialized by an earlier version that used an incompatible configuration structure. Run \"terraform init -reconfigure\" to force re-initialization of the backend." , err ) ,
) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
2018-03-28 00:31:05 +02:00
// Validate the config and then configure the backend
validDiags := b . ValidateConfig ( configVal )
diags = diags . Append ( validDiags )
if validDiags . HasErrors ( ) {
return nil , diags
2017-01-19 05:50:04 +01:00
}
2018-03-28 00:31:05 +02:00
configDiags := b . Configure ( configVal )
diags = diags . Append ( configDiags )
if configDiags . HasErrors ( ) {
return nil , diags
2017-01-19 05:50:04 +01:00
}
2018-03-28 00:31:05 +02:00
return b , diags
}
2017-02-28 19:13:03 +01:00
2018-03-28 00:31:05 +02:00
// Initiailizing a changed saved backend with legacy remote state.
func ( m * Meta ) backend_C_R_S_changed ( c * configs . Backend , sMgr state . State ) ( backend . Backend , tfdiags . Diagnostics ) {
var diags tfdiags . Diagnostics
2017-01-19 05:50:04 +01:00
2018-03-28 00:31:05 +02:00
m . Ui . Error ( strings . TrimSpace ( errBackendLegacy ) + "\n" )
2017-02-09 21:35:49 +01:00
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( "Cannot initialize legacy remote state" ) )
return nil , diags
}
2017-02-09 21:35:49 +01:00
2018-03-28 00:31:05 +02:00
// Initiailizing an unchanged saved backend with legacy remote state.
func ( m * Meta ) backend_C_R_S_unchanged ( c * configs . Backend , sMgr state . State , output bool ) ( backend . Backend , tfdiags . Diagnostics ) {
var diags tfdiags . Diagnostics
2017-01-19 05:50:04 +01:00
2018-03-28 00:31:05 +02:00
m . Ui . Error ( strings . TrimSpace ( errBackendLegacy ) + "\n" )
2017-01-19 05:50:04 +01:00
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( "Cannot initialize legacy remote state" ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
//-------------------------------------------------------------------
// Reusable helper functions for backend management
//-------------------------------------------------------------------
2018-03-28 00:31:05 +02:00
func ( m * Meta ) backendInitFromConfig ( c * configs . Backend ) ( backend . Backend , cty . Value , tfdiags . Diagnostics ) {
var diags tfdiags . Diagnostics
2017-01-19 05:50:04 +01:00
// Get the backend
2018-07-04 12:07:44 +02:00
f := backendInit . Backend ( c . Type )
2017-02-22 20:17:27 +01:00
if f == nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( strings . TrimSpace ( errBackendNewUnknown ) , c . Type ) )
return nil , cty . NilVal , diags
2017-01-19 05:50:04 +01:00
}
b := f ( )
2018-03-28 00:31:05 +02:00
schema := b . ConfigSchema ( )
decSpec := schema . DecoderSpec ( )
configVal , hclDiags := hcldec . Decode ( c . Config , decSpec , nil )
diags = diags . Append ( hclDiags )
if hclDiags . HasErrors ( ) {
return nil , cty . NilVal , diags
}
2017-01-19 05:50:04 +01:00
// TODO: test
if m . Input ( ) {
var err error
2018-03-28 00:31:05 +02:00
configVal , err = m . inputForSchema ( configVal , schema )
2017-01-19 05:50:04 +01:00
if err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( "Error asking for input to configure backend %q: %s" , c . Type , err ) )
2017-01-19 05:50:04 +01:00
}
}
2018-03-28 00:31:05 +02:00
validateDiags := b . ValidateConfig ( configVal )
diags = diags . Append ( validateDiags . InConfigBody ( c . Config ) )
if validateDiags . HasErrors ( ) {
return nil , cty . NilVal , diags
2017-01-19 05:50:04 +01:00
}
2018-03-28 00:31:05 +02:00
configureDiags := b . Configure ( configVal )
diags = diags . Append ( configureDiags . InConfigBody ( c . Config ) )
2017-01-19 05:50:04 +01:00
2018-03-28 00:31:05 +02:00
return b , configVal , diags
2017-01-19 05:50:04 +01:00
}
2018-03-28 00:31:05 +02:00
func ( m * Meta ) backendInitFromSaved ( s * terraform . BackendState ) ( backend . Backend , tfdiags . Diagnostics ) {
var diags tfdiags . Diagnostics
2017-01-19 05:50:04 +01:00
2017-03-17 18:41:13 +01:00
// Get the backend
2018-07-04 12:07:44 +02:00
f := backendInit . Backend ( s . Type )
2017-03-17 18:41:13 +01:00
if f == nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( fmt . Errorf ( strings . TrimSpace ( errBackendSavedUnknown ) , s . Type ) )
return nil , diags
2017-03-17 18:41:13 +01:00
}
b := f ( )
2017-01-19 05:50:04 +01:00
2018-03-28 00:31:05 +02:00
schema := b . ConfigSchema ( )
configVal , err := s . Config ( schema )
2017-01-19 05:50:04 +01:00
if err != nil {
2018-03-28 00:31:05 +02:00
diags = diags . Append ( errwrap . Wrapf ( "saved backend configuration is invalid: {{err}}" , err ) )
return nil , diags
2017-01-19 05:50:04 +01:00
}
2018-03-28 00:31:05 +02:00
validateDiags := b . ValidateConfig ( configVal )
diags = diags . Append ( validateDiags )
if validateDiags . HasErrors ( ) {
return nil , diags
2017-01-19 05:50:04 +01:00
}
2018-03-28 00:31:05 +02:00
configureDiags := b . Configure ( configVal )
diags = diags . Append ( configureDiags )
2017-01-19 05:50:04 +01:00
2018-03-28 00:31:05 +02:00
return b , diags
2017-01-19 05:50:04 +01:00
}
func ( m * Meta ) backendInitRequired ( reason string ) {
m . Ui . Output ( m . Colorize ( ) . Color ( fmt . Sprintf (
"[reset]" + strings . TrimSpace ( errBackendInit ) + "\n" , reason ) ) )
}
//-------------------------------------------------------------------
// Output constants and initialization code
//-------------------------------------------------------------------
// errBackendInitRequired is the final error message shown when reinit
// is required for some reason. The error message includes the reason.
var errBackendInitRequired = errors . New (
"Initialization required. Please see the error message above." )
const errBackendLegacyConfig = `
One or more errors occurred while configuring the legacy remote state .
If fixing these errors requires changing your remote state configuration ,
you must switch your configuration to the new remote backend configuration .
You can learn more about remote backends at the URL below :
2017-03-17 18:41:13 +01:00
https : //www.terraform.io/docs/backends/index.html
2017-01-19 05:50:04 +01:00
The error ( s ) configuring the legacy remote state :
% s
`
2017-03-01 04:29:19 +01:00
const errBackendLegacyUnknown = `
The legacy remote state type % q could not be found .
2017-03-17 18:52:22 +01:00
Terraform 0.9 .0 shipped with backwards compatibility for all built - in
2017-03-01 04:29:19 +01:00
legacy remote state types . This error may mean that you were using a
custom Terraform build that perhaps supported a different type of
remote state .
Please check with the creator of the remote state above and try again .
`
2017-01-19 05:50:04 +01:00
const errBackendLocalRead = `
Error reading local state : % s
Terraform is trying to read your local state to determine if there is
state to migrate to your newly configured backend . Terraform can ' t continue
without this check because that would risk losing state . Please resolve the
error above and try again .
`
const errBackendMigrateLocalDelete = `
Error deleting local state after migration : % s
Your local state is deleted after successfully migrating it to the newly
configured backend . As part of the deletion process , a backup is made at
the standard backup path unless explicitly asked not to . To cleanly operate
with a backend , we must delete the local state file . Please resolve the
issue above and retry the command .
`
const errBackendMigrateNew = `
Error migrating local state to backend : % s
Your local state remains intact and unmodified . Please resolve the error
above and try again .
`
const errBackendNewConfig = `
Error configuring the backend % q : % s
Please update the configuration in your Terraform files to fix this error
then run this command again .
`
const errBackendNewRead = `
Error reading newly configured backend state : % s
Terraform is trying to read the state from your newly configured backend
to determine the copy process for your existing state . Backends are expected
to not error even if there is no state yet written . Please resolve the
error above and try again .
`
const errBackendNewUnknown = `
The backend % q could not be found .
This is the backend specified in your Terraform configuration file .
This error could be a simple typo in your configuration , but it can also
be caused by using a Terraform version that doesn ' t support the specified
backend type . Please check your configuration and your Terraform version .
If you ' d like to run Terraform and store state locally , you can fix this
error by removing the backend configuration from your configuration .
`
const errBackendRemoteRead = `
Error reading backend state : % s
Terraform is trying to read the state from your configured backend to
2017-02-12 20:46:32 +01:00
determine if there is any migration steps necessary . Terraform can ' t continue
2017-01-19 05:50:04 +01:00
without this check because that would risk losing state . Please resolve the
error above and try again .
`
const errBackendSavedConfig = `
Error configuring the backend % q : % s
Please update the configuration in your Terraform files to fix this error .
If you ' d like to update the configuration interactively without storing
the values in your configuration , run "terraform init" .
`
const errBackendSavedUnsetConfig = `
Error configuring the existing backend % q : % s
Terraform must configure the existing backend in order to copy the state
from the existing backend , as requested . Please resolve the error and try
again . If you choose to not copy the existing state , Terraform will not
configure the backend . If the configuration is invalid , please update your
Terraform configuration with proper configuration for this backend first
before unsetting the backend .
`
const errBackendSavedUnknown = `
The backend % q could not be found .
This is the backend that this Terraform environment is configured to use
both in your configuration and saved locally as your last - used backend .
If it isn ' t found , it could mean an alternate version of Terraform was
used with this configuration . Please use the proper version of Terraform that
contains support for this backend .
If you ' d like to force remove this backend , you must update your configuration
to not use the backend and run "terraform init" ( or any other command ) again .
`
const errBackendClearLegacy = `
Error clearing the legacy remote state configuration : % s
Terraform completed configuring your backend . It is now safe to remove
the legacy remote state configuration , but an error occurred while trying
to do so . Please look at the error above , resolve it , and try again .
`
const errBackendClearSaved = `
Error clearing the backend configuration : % s
Terraform removes the saved backend configuration when you ' re removing a
configured backend . This must be done so future Terraform runs know to not
use the backend configuration . Please look at the error above , resolve it ,
and try again .
`
const errBackendInit = `
[ reset ] [ bold ] [ yellow ] Backend reinitialization required . Please run "terraform init" . [ reset ]
[ yellow ] Reason : % s
The "backend" is the interface that Terraform uses to store state ,
perform operations , etc . If this message is showing up , it means that the
Terraform configuration you ' re using is using a custom configuration for
the Terraform backend .
Changes to backend configurations require reinitialization . This allows
Terraform to setup the new configuration , copy existing state , etc . This is
only done during "terraform init" . Please run that command now then try again .
If the change reason above is incorrect , please verify your configuration
hasn ' t changed and try again . At this point , no changes to your existing
configuration or state have been made .
`
const errBackendWriteSaved = `
Error saving the backend configuration : % s
Terraform saves the complete backend configuration in a local file for
configuring the backend on future operations . This cannot be disabled . Errors
are usually due to simple file permission errors . Please look at the error
above , resolve it , and try again .
`
const errBackendPlanBoth = `
The plan file contained both a legacy remote state and backend configuration .
This is not allowed . Please recreate the plan file with the latest version of
Terraform .
`
const errBackendPlanLineageDiff = `
The plan file contains a state with a differing lineage than the current
state . By continuing , your current state would be overwritten by the state
in the plan . Please either update the plan with the latest state or delete
your current state and try again .
"Lineage" is a unique identifier generated only once on the creation of
a new , empty state . If these values differ , it means they were created new
at different times . Therefore , Terraform must assume that they ' re completely
different states .
The most common cause of seeing this error is using a plan that was
created against a different state . Perhaps the plan is very old and the
2018-06-13 15:57:32 +02:00
state has since been recreated , or perhaps the plan was against a completely
2017-01-19 05:50:04 +01:00
different infrastructure .
`
const errBackendPlanStateFlag = `
The - state and - state - out flags cannot be set with a plan that has a remote
state . The plan itself contains the configuration for the remote backend to
store state . The state will be written there for consistency .
If you wish to change this behavior , please create a plan from local state .
You may use the state flags with plans from local state to affect where
the final state is written .
`
const errBackendPlanOlder = `
This plan was created against an older state than is current . Please create
a new plan file against the latest state and try again .
Terraform doesn ' t allow you to run plans that were created from older
states since it doesn ' t properly represent the latest changes Terraform
may have made , and can result in unsafe behavior .
Plan Serial : % [ 1 ] d
Current Serial : % [ 2 ] d
`
2017-12-18 17:11:09 +01:00
const outputBackendMigrateChange = `
2017-12-20 23:50:37 +01:00
Terraform detected that the backend type changed from % q to % q .
2017-01-19 05:50:04 +01:00
`
2017-12-18 17:11:09 +01:00
const outputBackendMigrateLegacy = `
Terraform detected legacy remote state .
2017-01-19 05:50:04 +01:00
`
2017-12-18 17:11:09 +01:00
const outputBackendMigrateLocal = `
Terraform has detected you ' re unconfiguring your previously set % q backend .
2017-01-19 05:50:04 +01:00
`
const outputBackendConfigureWithLegacy = `
[ reset ] [ bold ] New backend configuration detected with legacy remote state ! [ reset ]
Terraform has detected that you ' re attempting to configure a new backend .
At the same time , legacy remote state configuration was found . Terraform will
first configure the new backend , and then ask if you ' d like to migrate
your remote state to the new backend .
`
const outputBackendReconfigure = `
[ reset ] [ bold ] Backend configuration changed ! [ reset ]
Terraform has detected that the configuration specified for the backend
2017-12-20 23:50:37 +01:00
has changed . Terraform will now check for existing state in the backends .
2017-01-19 05:50:04 +01:00
`
const outputBackendSavedWithLegacy = `
[ reset ] [ bold ] Legacy remote state was detected ! [ reset ]
Terraform has detected you still have legacy remote state enabled while
also having a backend configured . Terraform will now ask if you want to
migrate your legacy remote state data to the configured backend .
`
const outputBackendSavedWithLegacyChanged = `
[ reset ] [ bold ] Legacy remote state was detected while also changing your current backend ! reset ]
Terraform has detected that you have legacy remote state , a configured
current backend , and you ' re attempting to reconfigure your backend . To handle
all of these changes , Terraform will first reconfigure your backend . After
this , Terraform will handle optionally copying your legacy remote state
into the newly configured backend .
`
const outputBackendUnsetWithLegacy = `
[ reset ] [ bold ] Detected a request to unset the backend with legacy remote state present ! [ reset ]
Terraform has detected that you ' re attempting to unset a previously configured
backend ( by not having the "backend" configuration set in your Terraform files ) .
At the same time , legacy remote state was detected . To handle this complex
scenario , Terraform will first unset your configured backend , and then
ask you how to handle the legacy remote state . This will be multi - step
process .
`
const successBackendLegacyUnset = `
Terraform has successfully migrated from legacy remote state to your
2017-05-19 16:01:44 +02:00
configured backend ( % q ) .
2017-01-19 05:50:04 +01:00
`
const successBackendReconfigureWithLegacy = `
Terraform has successfully reconfigured your backend and migrate
from legacy remote state to the new backend .
`
const successBackendUnset = `
Successfully unset the backend % q . Terraform will now operate locally .
`
const successBackendSet = `
Successfully configured the backend % q ! Terraform will automatically
use this backend unless the backend configuration changes .
`
2018-03-28 00:31:05 +02:00
const errBackendLegacy = `
This working directory is configured to use the legacy remote state features
from Terraform 0.8 or earlier . Remote state changed significantly in Terraform
0.9 and the automatic upgrade mechanism has now been removed .
2017-03-01 04:29:19 +01:00
2018-03-28 00:31:05 +02:00
To upgrade , please first use Terraform v0 .11 to complete the upgrade steps :
https : //www.terraform.io/docs/backends/legacy-0-8.html
2017-01-19 05:50:04 +01:00
`