github.com/kevinklinger/open_terraform@v1.3.6/noninternal/command/meta_backend.go (about)

     1  package command
     2  
     3  // This file contains all the Backend-related function calls on Meta,
     4  // exported and private.
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"log"
    13  	"path/filepath"
    14  	"strconv"
    15  	"strings"
    16  
    17  	"github.com/hashicorp/hcl/v2"
    18  	"github.com/hashicorp/hcl/v2/hcldec"
    19  	"github.com/kevinklinger/open_terraform/noninternal/backend"
    20  	"github.com/kevinklinger/open_terraform/noninternal/cloud"
    21  	"github.com/kevinklinger/open_terraform/noninternal/command/arguments"
    22  	"github.com/kevinklinger/open_terraform/noninternal/command/clistate"
    23  	"github.com/kevinklinger/open_terraform/noninternal/command/views"
    24  	"github.com/kevinklinger/open_terraform/noninternal/configs"
    25  	"github.com/kevinklinger/open_terraform/noninternal/plans"
    26  	"github.com/kevinklinger/open_terraform/noninternal/states/statemgr"
    27  	"github.com/kevinklinger/open_terraform/noninternal/terraform"
    28  	"github.com/kevinklinger/open_terraform/noninternal/tfdiags"
    29  	"github.com/zclconf/go-cty/cty"
    30  	ctyjson "github.com/zclconf/go-cty/cty/json"
    31  
    32  	backendInit "github.com/kevinklinger/open_terraform/noninternal/backend/init"
    33  	backendLocal "github.com/kevinklinger/open_terraform/noninternal/backend/local"
    34  	legacy "github.com/kevinklinger/open_terraform/noninternal/legacy/terraform"
    35  )
    36  
    37  // BackendOpts are the options used to initialize a backend.Backend.
    38  type BackendOpts struct {
    39  	// Config is a representation of the backend configuration block given in
    40  	// the root module, or nil if no such block is present.
    41  	Config *configs.Backend
    42  
    43  	// ConfigOverride is an hcl.Body that, if non-nil, will be used with
    44  	// configs.MergeBodies to override the type-specific backend configuration
    45  	// arguments in Config.
    46  	ConfigOverride hcl.Body
    47  
    48  	// Init should be set to true if initialization is allowed. If this is
    49  	// false, then any configuration that requires configuration will show
    50  	// an error asking the user to reinitialize.
    51  	Init bool
    52  
    53  	// ForceLocal will force a purely local backend, including state.
    54  	// You probably don't want to set this.
    55  	ForceLocal bool
    56  }
    57  
    58  // BackendWithRemoteTerraformVersion is a shared interface between the 'remote' and 'cloud' backends
    59  // for simplified type checking when calling functions common to those particular backends.
    60  type BackendWithRemoteTerraformVersion interface {
    61  	IgnoreVersionConflict()
    62  	VerifyWorkspaceTerraformVersion(workspace string) tfdiags.Diagnostics
    63  	IsLocalOperations() bool
    64  }
    65  
    66  // Backend initializes and returns the backend for this CLI session.
    67  //
    68  // The backend is used to perform the actual Terraform operations. This
    69  // abstraction enables easily sliding in new Terraform behavior such as
    70  // remote state storage, remote operations, etc. while allowing the CLI
    71  // to remain mostly identical.
    72  //
    73  // This will initialize a new backend for each call, which can carry some
    74  // overhead with it. Please reuse the returned value for optimal behavior.
    75  //
    76  // Only one backend should be used per Meta. This function is stateful
    77  // and is unsafe to create multiple backends used at once. This function
    78  // can be called multiple times with each backend being "live" (usable)
    79  // one at a time.
    80  //
    81  // A side-effect of this method is the population of m.backendState, recording
    82  // the final resolved backend configuration after dealing with overrides from
    83  // the "terraform init" command line, etc.
    84  func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, tfdiags.Diagnostics) {
    85  	var diags tfdiags.Diagnostics
    86  
    87  	// If no opts are set, then initialize
    88  	if opts == nil {
    89  		opts = &BackendOpts{}
    90  	}
    91  
    92  	// Initialize a backend from the config unless we're forcing a purely
    93  	// local operation.
    94  	var b backend.Backend
    95  	if !opts.ForceLocal {
    96  		var backendDiags tfdiags.Diagnostics
    97  		b, backendDiags = m.backendFromConfig(opts)
    98  		diags = diags.Append(backendDiags)
    99  
   100  		if diags.HasErrors() {
   101  			return nil, diags
   102  		}
   103  
   104  		log.Printf("[TRACE] Meta.Backend: instantiated backend of type %T", b)
   105  	}
   106  
   107  	// Set up the CLI opts we pass into backends that support it.
   108  	cliOpts, err := m.backendCLIOpts()
   109  	if err != nil {
   110  		if errs := providerPluginErrors(nil); errors.As(err, &errs) {
   111  			// This is a special type returned by m.providerFactories, which
   112  			// indicates one or more inconsistencies between the dependency
   113  			// lock file and the provider plugins actually available in the
   114  			// local cache directory.
   115  			//
   116  			// If initialization is allowed, we ignore this error, as it may
   117  			// be resolved by the later step where providers are fetched.
   118  			if !opts.Init {
   119  				var buf bytes.Buffer
   120  				for addr, err := range errs {
   121  					fmt.Fprintf(&buf, "\n  - %s: %s", addr, err)
   122  				}
   123  				suggestion := "To download the plugins required for this configuration, run:\n  terraform init"
   124  				if m.RunningInAutomation {
   125  					// Don't mention "terraform init" specifically if we're running in an automation wrapper
   126  					suggestion = "You must install the required plugins before running Terraform operations."
   127  				}
   128  				diags = diags.Append(tfdiags.Sourceless(
   129  					tfdiags.Error,
   130  					"Required plugins are not installed",
   131  					fmt.Sprintf(
   132  						"The installed provider plugins are not consistent with the packages selected in the dependency lock file:%s\n\nTerraform uses external plugins to integrate with a variety of different infrastructure services. %s",
   133  						buf.String(), suggestion,
   134  					),
   135  				))
   136  				return nil, diags
   137  			}
   138  		} else {
   139  			// All other errors just get generic handling.
   140  			diags = diags.Append(err)
   141  			return nil, diags
   142  		}
   143  	}
   144  	cliOpts.Validation = true
   145  
   146  	// If the backend supports CLI initialization, do it.
   147  	if cli, ok := b.(backend.CLI); ok {
   148  		if err := cli.CLIInit(cliOpts); err != nil {
   149  			diags = diags.Append(fmt.Errorf(
   150  				"Error initializing backend %T: %s\n\n"+
   151  					"This is a bug; please report it to the backend developer",
   152  				b, err,
   153  			))
   154  			return nil, diags
   155  		}
   156  	}
   157  
   158  	// If the result of loading the backend is an enhanced backend,
   159  	// then return that as-is. This works even if b == nil (it will be !ok).
   160  	if enhanced, ok := b.(backend.Enhanced); ok {
   161  		log.Printf("[TRACE] Meta.Backend: backend %T supports operations", b)
   162  		return enhanced, nil
   163  	}
   164  
   165  	// We either have a non-enhanced backend or no backend configured at
   166  	// all. In either case, we use local as our enhanced backend and the
   167  	// non-enhanced (if any) as the state backend.
   168  
   169  	if !opts.ForceLocal {
   170  		log.Printf("[TRACE] Meta.Backend: backend %T does not support operations, so wrapping it in a local backend", b)
   171  	}
   172  
   173  	// Build the local backend
   174  	local := backendLocal.NewWithBackend(b)
   175  	if err := local.CLIInit(cliOpts); err != nil {
   176  		// Local backend isn't allowed to fail. It would be a bug.
   177  		panic(err)
   178  	}
   179  
   180  	// If we got here from backendFromConfig returning nil then m.backendState
   181  	// won't be set, since that codepath considers that to be no backend at all,
   182  	// but our caller considers that to be the local backend with no config
   183  	// and so we'll synthesize a backend state so other code doesn't need to
   184  	// care about this special case.
   185  	//
   186  	// FIXME: We should refactor this so that we more directly and explicitly
   187  	// treat the local backend as the default, including in the UI shown to
   188  	// the user, since the local backend should only be used when learning or
   189  	// in exceptional cases and so it's better to help the user learn that
   190  	// by introducing it as a concept.
   191  	if m.backendState == nil {
   192  		// NOTE: This synthetic object is intentionally _not_ retained in the
   193  		// on-disk record of the backend configuration, which was already dealt
   194  		// with inside backendFromConfig, because we still need that codepath
   195  		// to be able to recognize the lack of a config as distinct from
   196  		// explicitly setting local until we do some more refactoring here.
   197  		m.backendState = &legacy.BackendState{
   198  			Type:      "local",
   199  			ConfigRaw: json.RawMessage("{}"),
   200  		}
   201  	}
   202  
   203  	return local, nil
   204  }
   205  
   206  // selectWorkspace gets a list of existing workspaces and then checks
   207  // if the currently selected workspace is valid. If not, it will ask
   208  // the user to select a workspace from the list.
   209  func (m *Meta) selectWorkspace(b backend.Backend) error {
   210  	workspaces, err := b.Workspaces()
   211  	if err == backend.ErrWorkspacesNotSupported {
   212  		return nil
   213  	}
   214  	if err != nil {
   215  		return fmt.Errorf("Failed to get existing workspaces: %s", err)
   216  	}
   217  	if len(workspaces) == 0 {
   218  		if c, ok := b.(*cloud.Cloud); ok && m.input {
   219  			// len is always 1 if using Name; 0 means we're using Tags and there
   220  			// aren't any matching workspaces. Which might be normal and fine, so
   221  			// let's just ask:
   222  			name, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{
   223  				Id:          "create-workspace",
   224  				Query:       "\n[reset][bold][yellow]No workspaces found.[reset]",
   225  				Description: fmt.Sprintf(inputCloudInitCreateWorkspace, strings.Join(c.WorkspaceMapping.Tags, ", ")),
   226  			})
   227  			if err != nil {
   228  				return fmt.Errorf("Couldn't create initial workspace: %w", err)
   229  			}
   230  			name = strings.TrimSpace(name)
   231  			if name == "" {
   232  				return fmt.Errorf("Couldn't create initial workspace: no name provided")
   233  			}
   234  			log.Printf("[TRACE] Meta.selectWorkspace: selecting the new TFC workspace requested by the user (%s)", name)
   235  			return m.SetWorkspace(name)
   236  		} else {
   237  			return fmt.Errorf(strings.TrimSpace(errBackendNoExistingWorkspaces))
   238  		}
   239  	}
   240  
   241  	// Get the currently selected workspace.
   242  	workspace, err := m.Workspace()
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	// Check if any of the existing workspaces matches the selected
   248  	// workspace and create a numbered list of existing workspaces.
   249  	var list strings.Builder
   250  	for i, w := range workspaces {
   251  		if w == workspace {
   252  			log.Printf("[TRACE] Meta.selectWorkspace: the currently selected workspace is present in the configured backend (%s)", workspace)
   253  			return nil
   254  		}
   255  		fmt.Fprintf(&list, "%d. %s\n", i+1, w)
   256  	}
   257  
   258  	// If the backend only has a single workspace, select that as the current workspace
   259  	if len(workspaces) == 1 {
   260  		log.Printf("[TRACE] Meta.selectWorkspace: automatically selecting the single workspace provided by the backend (%s)", workspaces[0])
   261  		return m.SetWorkspace(workspaces[0])
   262  	}
   263  
   264  	if !m.input {
   265  		return fmt.Errorf("Currently selected workspace %q does not exist", workspace)
   266  	}
   267  
   268  	// Otherwise, ask the user to select a workspace from the list of existing workspaces.
   269  	v, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{
   270  		Id: "select-workspace",
   271  		Query: fmt.Sprintf(
   272  			"\n[reset][bold][yellow]The currently selected workspace (%s) does not exist.[reset]",
   273  			workspace),
   274  		Description: fmt.Sprintf(
   275  			strings.TrimSpace(inputBackendSelectWorkspace), list.String()),
   276  	})
   277  	if err != nil {
   278  		return fmt.Errorf("Failed to select workspace: %s", err)
   279  	}
   280  
   281  	idx, err := strconv.Atoi(v)
   282  	if err != nil || (idx < 1 || idx > len(workspaces)) {
   283  		return fmt.Errorf("Failed to select workspace: input not a valid number")
   284  	}
   285  
   286  	workspace = workspaces[idx-1]
   287  	log.Printf("[TRACE] Meta.selectWorkspace: setting the current workpace according to user selection (%s)", workspace)
   288  	return m.SetWorkspace(workspace)
   289  }
   290  
   291  // BackendForPlan is similar to Backend, but uses backend settings that were
   292  // stored in a plan.
   293  //
   294  // The current workspace name is also stored as part of the plan, and so this
   295  // method will check that it matches the currently-selected workspace name
   296  // and produce error diagnostics if not.
   297  func (m *Meta) BackendForPlan(settings plans.Backend) (backend.Enhanced, tfdiags.Diagnostics) {
   298  	var diags tfdiags.Diagnostics
   299  
   300  	f := backendInit.Backend(settings.Type)
   301  	if f == nil {
   302  		diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), settings.Type))
   303  		return nil, diags
   304  	}
   305  	b := f()
   306  	log.Printf("[TRACE] Meta.BackendForPlan: instantiated backend of type %T", b)
   307  
   308  	schema := b.ConfigSchema()
   309  	configVal, err := settings.Config.Decode(schema.ImpliedType())
   310  	if err != nil {
   311  		diags = diags.Append(fmt.Errorf("saved backend configuration is invalid: %w", err))
   312  		return nil, diags
   313  	}
   314  
   315  	newVal, validateDiags := b.PrepareConfig(configVal)
   316  	diags = diags.Append(validateDiags)
   317  	if validateDiags.HasErrors() {
   318  		return nil, diags
   319  	}
   320  
   321  	configureDiags := b.Configure(newVal)
   322  	diags = diags.Append(configureDiags)
   323  	if configureDiags.HasErrors() {
   324  		return nil, diags
   325  	}
   326  
   327  	// If the backend supports CLI initialization, do it.
   328  	if cli, ok := b.(backend.CLI); ok {
   329  		cliOpts, err := m.backendCLIOpts()
   330  		if err != nil {
   331  			diags = diags.Append(err)
   332  			return nil, diags
   333  		}
   334  		if err := cli.CLIInit(cliOpts); err != nil {
   335  			diags = diags.Append(fmt.Errorf(
   336  				"Error initializing backend %T: %s\n\n"+
   337  					"This is a bug; please report it to the backend developer",
   338  				b, err,
   339  			))
   340  			return nil, diags
   341  		}
   342  	}
   343  
   344  	// If the result of loading the backend is an enhanced backend,
   345  	// then return that as-is. This works even if b == nil (it will be !ok).
   346  	if enhanced, ok := b.(backend.Enhanced); ok {
   347  		log.Printf("[TRACE] Meta.BackendForPlan: backend %T supports operations", b)
   348  		return enhanced, nil
   349  	}
   350  
   351  	// Otherwise, we'll wrap our state-only remote backend in the local backend
   352  	// to cause any operations to be run locally.
   353  	log.Printf("[TRACE] Meta.Backend: backend %T does not support operations, so wrapping it in a local backend", b)
   354  	cliOpts, err := m.backendCLIOpts()
   355  	if err != nil {
   356  		diags = diags.Append(err)
   357  		return nil, diags
   358  	}
   359  	cliOpts.Validation = false // don't validate here in case config contains file(...) calls where the file doesn't exist
   360  	local := backendLocal.NewWithBackend(b)
   361  	if err := local.CLIInit(cliOpts); err != nil {
   362  		// Local backend should never fail, so this is always a bug.
   363  		panic(err)
   364  	}
   365  
   366  	return local, diags
   367  }
   368  
   369  // backendCLIOpts returns a backend.CLIOpts object that should be passed to
   370  // a backend that supports local CLI operations.
   371  func (m *Meta) backendCLIOpts() (*backend.CLIOpts, error) {
   372  	contextOpts, err := m.contextOpts()
   373  	if contextOpts == nil && err != nil {
   374  		return nil, err
   375  	}
   376  	return &backend.CLIOpts{
   377  		CLI:                 m.Ui,
   378  		CLIColor:            m.Colorize(),
   379  		Streams:             m.Streams,
   380  		StatePath:           m.statePath,
   381  		StateOutPath:        m.stateOutPath,
   382  		StateBackupPath:     m.backupPath,
   383  		ContextOpts:         contextOpts,
   384  		Input:               m.Input(),
   385  		RunningInAutomation: m.RunningInAutomation,
   386  	}, err
   387  }
   388  
   389  // Operation initializes a new backend.Operation struct.
   390  //
   391  // This prepares the operation. After calling this, the caller is expected
   392  // to modify fields of the operation such as Sequence to specify what will
   393  // be called.
   394  func (m *Meta) Operation(b backend.Backend) *backend.Operation {
   395  	schema := b.ConfigSchema()
   396  	workspace, err := m.Workspace()
   397  	if err != nil {
   398  		// An invalid workspace error would have been raised when creating the
   399  		// backend, and the caller should have already exited. Seeing the error
   400  		// here first is a bug, so panic.
   401  		panic(fmt.Sprintf("invalid workspace: %s", err))
   402  	}
   403  	planOutBackend, err := m.backendState.ForPlan(schema, workspace)
   404  	if err != nil {
   405  		// Always indicates an implementation error in practice, because
   406  		// errors here indicate invalid encoding of the backend configuration
   407  		// in memory, and we should always have validated that by the time
   408  		// we get here.
   409  		panic(fmt.Sprintf("failed to encode backend configuration for plan: %s", err))
   410  	}
   411  
   412  	stateLocker := clistate.NewNoopLocker()
   413  	if m.stateLock {
   414  		view := views.NewStateLocker(arguments.ViewHuman, m.View)
   415  		stateLocker = clistate.NewLocker(m.stateLockTimeout, view)
   416  	}
   417  
   418  	depLocks, diags := m.lockedDependencies()
   419  	if diags.HasErrors() {
   420  		// We can't actually report errors from here, but m.lockedDependencies
   421  		// should always have been called earlier to prepare the "ContextOpts"
   422  		// for the backend anyway, so we should never actually get here in
   423  		// a real situation. If we do get here then the backend will inevitably
   424  		// fail downstream somwhere if it tries to use the empty depLocks.
   425  		log.Printf("[WARN] Failed to load dependency locks while preparing backend operation (ignored): %s", diags.Err().Error())
   426  	}
   427  
   428  	return &backend.Operation{
   429  		PlanOutBackend:  planOutBackend,
   430  		Targets:         m.targets,
   431  		UIIn:            m.UIInput(),
   432  		UIOut:           m.Ui,
   433  		Workspace:       workspace,
   434  		StateLocker:     stateLocker,
   435  		DependencyLocks: depLocks,
   436  	}
   437  }
   438  
   439  // backendConfig returns the local configuration for the backend
   440  func (m *Meta) backendConfig(opts *BackendOpts) (*configs.Backend, int, tfdiags.Diagnostics) {
   441  	var diags tfdiags.Diagnostics
   442  
   443  	if opts.Config == nil {
   444  		// check if the config was missing, or just not required
   445  		conf, moreDiags := m.loadBackendConfig(".")
   446  		diags = diags.Append(moreDiags)
   447  		if moreDiags.HasErrors() {
   448  			return nil, 0, diags
   449  		}
   450  
   451  		if conf == nil {
   452  			log.Println("[TRACE] Meta.Backend: no config given or present on disk, so returning nil config")
   453  			return nil, 0, nil
   454  		}
   455  
   456  		log.Printf("[TRACE] Meta.Backend: BackendOpts.Config not set, so using settings loaded from %s", conf.DeclRange)
   457  		opts.Config = conf
   458  	}
   459  
   460  	c := opts.Config
   461  
   462  	if c == nil {
   463  		log.Println("[TRACE] Meta.Backend: no explicit backend config, so returning nil config")
   464  		return nil, 0, nil
   465  	}
   466  
   467  	bf := backendInit.Backend(c.Type)
   468  	if bf == nil {
   469  		detail := fmt.Sprintf("There is no backend type named %q.", c.Type)
   470  		if msg, removed := backendInit.RemovedBackends[c.Type]; removed {
   471  			detail = msg
   472  		}
   473  
   474  		diags = diags.Append(&hcl.Diagnostic{
   475  			Severity: hcl.DiagError,
   476  			Summary:  "Invalid backend type",
   477  			Detail:   detail,
   478  			Subject:  &c.TypeRange,
   479  		})
   480  		return nil, 0, diags
   481  	}
   482  	b := bf()
   483  
   484  	configSchema := b.ConfigSchema()
   485  	configBody := c.Config
   486  	configHash := c.Hash(configSchema)
   487  
   488  	// If we have an override configuration body then we must apply it now.
   489  	if opts.ConfigOverride != nil {
   490  		log.Println("[TRACE] Meta.Backend: merging -backend-config=... CLI overrides into backend configuration")
   491  		configBody = configs.MergeBodies(configBody, opts.ConfigOverride)
   492  	}
   493  
   494  	log.Printf("[TRACE] Meta.Backend: built configuration for %q backend with hash value %d", c.Type, configHash)
   495  
   496  	// We'll shallow-copy configs.Backend here so that we can replace the
   497  	// body without affecting others that hold this reference.
   498  	configCopy := *c
   499  	configCopy.Config = configBody
   500  	return &configCopy, configHash, diags
   501  }
   502  
   503  // backendFromConfig returns the initialized (not configured) backend
   504  // directly from the config/state..
   505  //
   506  // This function handles various edge cases around backend config loading. For
   507  // example: new config changes, backend type changes, etc.
   508  //
   509  // As of the 0.12 release it can no longer migrate from legacy remote state
   510  // to backends, and will instead instruct users to use 0.11 or earlier as
   511  // a stepping-stone to do that migration.
   512  //
   513  // This function may query the user for input unless input is disabled, in
   514  // which case this function will error.
   515  func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) {
   516  	// Get the local backend configuration.
   517  	c, cHash, diags := m.backendConfig(opts)
   518  	if diags.HasErrors() {
   519  		return nil, diags
   520  	}
   521  
   522  	// ------------------------------------------------------------------------
   523  	// For historical reasons, current backend configuration for a working
   524  	// directory is kept in a *state-like* file, using the legacy state
   525  	// structures in the Terraform package. It is not actually a Terraform
   526  	// state, and so only the "backend" portion of it is actually used.
   527  	//
   528  	// The remainder of this code often confusingly refers to this as a "state",
   529  	// so it's unfortunately important to remember that this is not actually
   530  	// what we _usually_ think of as "state", and is instead a local working
   531  	// directory "backend configuration state" that is never persisted anywhere.
   532  	//
   533  	// Since the "real" state has since moved on to be represented by
   534  	// states.State, we can recognize the special meaning of state that applies
   535  	// to this function and its callees by their continued use of the
   536  	// otherwise-obsolete terraform.State.
   537  	// ------------------------------------------------------------------------
   538  
   539  	// Get the path to where we store a local cache of backend configuration
   540  	// if we're using a remote backend. This may not yet exist which means
   541  	// we haven't used a non-local backend before. That is okay.
   542  	statePath := filepath.Join(m.DataDir(), DefaultStateFilename)
   543  	sMgr := &clistate.LocalState{Path: statePath}
   544  	if err := sMgr.RefreshState(); err != nil {
   545  		diags = diags.Append(fmt.Errorf("Failed to load state: %s", err))
   546  		return nil, diags
   547  	}
   548  
   549  	// Load the state, it must be non-nil for the tests below but can be empty
   550  	s := sMgr.State()
   551  	if s == nil {
   552  		log.Printf("[TRACE] Meta.Backend: backend has not previously been initialized in this working directory")
   553  		s = legacy.NewState()
   554  	} else if s.Backend != nil {
   555  		log.Printf("[TRACE] Meta.Backend: working directory was previously initialized for %q backend", s.Backend.Type)
   556  	} else {
   557  		log.Printf("[TRACE] Meta.Backend: working directory was previously initialized but has no backend (is using legacy remote state?)")
   558  	}
   559  
   560  	// if we want to force reconfiguration of the backend, we set the backend
   561  	// state to nil on this copy. This will direct us through the correct
   562  	// configuration path in the switch statement below.
   563  	if m.reconfigure {
   564  		s.Backend = nil
   565  	}
   566  
   567  	// Upon return, we want to set the state we're using in-memory so that
   568  	// we can access it for commands.
   569  	m.backendState = nil
   570  	defer func() {
   571  		if s := sMgr.State(); s != nil && !s.Backend.Empty() {
   572  			m.backendState = s.Backend
   573  		}
   574  	}()
   575  
   576  	if !s.Remote.Empty() {
   577  		// Legacy remote state is no longer supported. User must first
   578  		// migrate with Terraform 0.11 or earlier.
   579  		diags = diags.Append(tfdiags.Sourceless(
   580  			tfdiags.Error,
   581  			"Legacy remote state not supported",
   582  			"This working directory is configured for legacy remote state, which is no longer supported from Terraform v0.12 onwards. To migrate this environment, first run \"terraform init\" under a Terraform 0.11 release, and then upgrade Terraform again.",
   583  		))
   584  		return nil, diags
   585  	}
   586  
   587  	// This switch statement covers all the different combinations of
   588  	// configuring new backends, updating previously-configured backends, etc.
   589  	switch {
   590  	// No configuration set at all. Pure local state.
   591  	case c == nil && s.Backend.Empty():
   592  		log.Printf("[TRACE] Meta.Backend: using default local state only (no backend configuration, and no existing initialized backend)")
   593  		return nil, nil
   594  
   595  	// We're unsetting a backend (moving from backend => local)
   596  	case c == nil && !s.Backend.Empty():
   597  		log.Printf("[TRACE] Meta.Backend: previously-initialized %q backend is no longer present in config", s.Backend.Type)
   598  
   599  		initReason := fmt.Sprintf("Unsetting the previously set backend %q", s.Backend.Type)
   600  		if !opts.Init {
   601  			diags = diags.Append(tfdiags.Sourceless(
   602  				tfdiags.Error,
   603  				"Backend initialization required, please run \"terraform init\"",
   604  				fmt.Sprintf(strings.TrimSpace(errBackendInit), initReason),
   605  			))
   606  			return nil, diags
   607  		}
   608  
   609  		if s.Backend.Type != "cloud" && !m.migrateState {
   610  			diags = diags.Append(migrateOrReconfigDiag)
   611  			return nil, diags
   612  		}
   613  
   614  		return m.backend_c_r_S(c, cHash, sMgr, true)
   615  
   616  	// Configuring a backend for the first time or -reconfigure flag was used
   617  	case c != nil && s.Backend.Empty():
   618  		log.Printf("[TRACE] Meta.Backend: moving from default local state only to %q backend", c.Type)
   619  		if !opts.Init {
   620  			if c.Type == "cloud" {
   621  				initReason := "Initial configuration of Terraform Cloud"
   622  				diags = diags.Append(tfdiags.Sourceless(
   623  					tfdiags.Error,
   624  					"Terraform Cloud initialization required: please run \"terraform init\"",
   625  					fmt.Sprintf(strings.TrimSpace(errBackendInitCloud), initReason),
   626  				))
   627  			} else {
   628  				initReason := fmt.Sprintf("Initial configuration of the requested backend %q", c.Type)
   629  				diags = diags.Append(tfdiags.Sourceless(
   630  					tfdiags.Error,
   631  					"Backend initialization required, please run \"terraform init\"",
   632  					fmt.Sprintf(strings.TrimSpace(errBackendInit), initReason),
   633  				))
   634  			}
   635  			return nil, diags
   636  		}
   637  		return m.backend_C_r_s(c, cHash, sMgr, opts)
   638  	// Potentially changing a backend configuration
   639  	case c != nil && !s.Backend.Empty():
   640  		// We are not going to migrate if...
   641  		//
   642  		// We're not initializing
   643  		// AND the backend cache hash values match, indicating that the stored config is valid and completely unchanged.
   644  		// AND we're not providing any overrides. An override can mean a change overriding an unchanged backend block (indicated by the hash value).
   645  		if (uint64(cHash) == s.Backend.Hash) && (!opts.Init || opts.ConfigOverride == nil) {
   646  			log.Printf("[TRACE] Meta.Backend: using already-initialized, unchanged %q backend configuration", c.Type)
   647  			savedBackend, diags := m.savedBackend(sMgr)
   648  			// Verify that selected workspace exist. Otherwise prompt user to create one
   649  			if opts.Init && savedBackend != nil {
   650  				if err := m.selectWorkspace(savedBackend); err != nil {
   651  					diags = diags.Append(err)
   652  					return nil, diags
   653  				}
   654  			}
   655  			return savedBackend, diags
   656  		}
   657  
   658  		// If our configuration (the result of both the literal configuration and given
   659  		// -backend-config options) is the same, then we're just initializing a previously
   660  		// configured backend. The literal configuration may differ, however, so while we
   661  		// don't need to migrate, we update the backend cache hash value.
   662  		if !m.backendConfigNeedsMigration(c, s.Backend) {
   663  			log.Printf("[TRACE] Meta.Backend: using already-initialized %q backend configuration", c.Type)
   664  			savedBackend, moreDiags := m.savedBackend(sMgr)
   665  			diags = diags.Append(moreDiags)
   666  			if moreDiags.HasErrors() {
   667  				return nil, diags
   668  			}
   669  
   670  			// It's possible for a backend to be unchanged, and the config itself to
   671  			// have changed by moving a parameter from the config to `-backend-config`
   672  			// In this case, we update the Hash.
   673  			moreDiags = m.updateSavedBackendHash(cHash, sMgr)
   674  			if moreDiags.HasErrors() {
   675  				return nil, diags
   676  			}
   677  			// Verify that selected workspace exist. Otherwise prompt user to create one
   678  			if opts.Init && savedBackend != nil {
   679  				if err := m.selectWorkspace(savedBackend); err != nil {
   680  					diags = diags.Append(err)
   681  					return nil, diags
   682  				}
   683  			}
   684  
   685  			return savedBackend, diags
   686  		}
   687  		log.Printf("[TRACE] Meta.Backend: backend configuration has changed (from type %q to type %q)", s.Backend.Type, c.Type)
   688  
   689  		cloudMode := cloud.DetectConfigChangeType(s.Backend, c, false)
   690  
   691  		if !opts.Init {
   692  			//user ran another cmd that is not init but they are required to initialize because of a potential relevant change to their backend configuration
   693  			initDiag := m.determineInitReason(s.Backend.Type, c.Type, cloudMode)
   694  			diags = diags.Append(initDiag)
   695  			return nil, diags
   696  		}
   697  
   698  		if !cloudMode.InvolvesCloud() && !m.migrateState {
   699  			diags = diags.Append(migrateOrReconfigDiag)
   700  			return nil, diags
   701  		}
   702  
   703  		log.Printf("[WARN] backend config has changed since last init")
   704  		return m.backend_C_r_S_changed(c, cHash, sMgr, true, opts)
   705  
   706  	default:
   707  		diags = diags.Append(fmt.Errorf(
   708  			"Unhandled backend configuration state. This is a bug. Please\n"+
   709  				"report this error with the following information.\n\n"+
   710  				"Config Nil: %v\n"+
   711  				"Saved Backend Empty: %v\n",
   712  			c == nil, s.Backend.Empty(),
   713  		))
   714  		return nil, diags
   715  	}
   716  }
   717  
   718  func (m *Meta) determineInitReason(previousBackendType string, currentBackendType string, cloudMode cloud.ConfigChangeMode) tfdiags.Diagnostics {
   719  	initReason := ""
   720  	switch cloudMode {
   721  	case cloud.ConfigMigrationIn:
   722  		initReason = fmt.Sprintf("Changed from backend %q to Terraform Cloud", previousBackendType)
   723  	case cloud.ConfigMigrationOut:
   724  		initReason = fmt.Sprintf("Changed from Terraform Cloud to backend %q", previousBackendType)
   725  	case cloud.ConfigChangeInPlace:
   726  		initReason = "Terraform Cloud configuration block has changed"
   727  	default:
   728  		switch {
   729  		case previousBackendType != currentBackendType:
   730  			initReason = fmt.Sprintf("Backend type changed from %q to %q", previousBackendType, currentBackendType)
   731  		default:
   732  			initReason = "Backend configuration block has changed"
   733  		}
   734  	}
   735  
   736  	var diags tfdiags.Diagnostics
   737  	switch cloudMode {
   738  	case cloud.ConfigChangeInPlace:
   739  		diags = diags.Append(tfdiags.Sourceless(
   740  			tfdiags.Error,
   741  			"Terraform Cloud initialization required: please run \"terraform init\"",
   742  			fmt.Sprintf(strings.TrimSpace(errBackendInitCloud), initReason),
   743  		))
   744  	case cloud.ConfigMigrationIn:
   745  		diags = diags.Append(tfdiags.Sourceless(
   746  			tfdiags.Error,
   747  			"Terraform Cloud initialization required: please run \"terraform init\"",
   748  			fmt.Sprintf(strings.TrimSpace(errBackendInitCloud), initReason),
   749  		))
   750  	default:
   751  		diags = diags.Append(tfdiags.Sourceless(
   752  			tfdiags.Error,
   753  			"Backend initialization required: please run \"terraform init\"",
   754  			fmt.Sprintf(strings.TrimSpace(errBackendInit), initReason),
   755  		))
   756  	}
   757  
   758  	return diags
   759  }
   760  
   761  // backendFromState returns the initialized (not configured) backend directly
   762  // from the state. This should be used only when a user runs `terraform init
   763  // -backend=false`. This function returns a local backend if there is no state
   764  // or no backend configured.
   765  func (m *Meta) backendFromState() (backend.Backend, tfdiags.Diagnostics) {
   766  	var diags tfdiags.Diagnostics
   767  	// Get the path to where we store a local cache of backend configuration
   768  	// if we're using a remote backend. This may not yet exist which means
   769  	// we haven't used a non-local backend before. That is okay.
   770  	statePath := filepath.Join(m.DataDir(), DefaultStateFilename)
   771  	sMgr := &clistate.LocalState{Path: statePath}
   772  	if err := sMgr.RefreshState(); err != nil {
   773  		diags = diags.Append(fmt.Errorf("Failed to load state: %s", err))
   774  		return nil, diags
   775  	}
   776  	s := sMgr.State()
   777  	if s == nil {
   778  		// no state, so return a local backend
   779  		log.Printf("[TRACE] Meta.Backend: backend has not previously been initialized in this working directory")
   780  		return backendLocal.New(), diags
   781  	}
   782  	if s.Backend == nil {
   783  		// s.Backend is nil, so return a local backend
   784  		log.Printf("[TRACE] Meta.Backend: working directory was previously initialized but has no backend (is using legacy remote state?)")
   785  		return backendLocal.New(), diags
   786  	}
   787  	log.Printf("[TRACE] Meta.Backend: working directory was previously initialized for %q backend", s.Backend.Type)
   788  
   789  	//backend init function
   790  	if s.Backend.Type == "" {
   791  		return backendLocal.New(), diags
   792  	}
   793  	f := backendInit.Backend(s.Backend.Type)
   794  	if f == nil {
   795  		diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), s.Backend.Type))
   796  		return nil, diags
   797  	}
   798  	b := f()
   799  
   800  	// The configuration saved in the working directory state file is used
   801  	// in this case, since it will contain any additional values that
   802  	// were provided via -backend-config arguments on terraform init.
   803  	schema := b.ConfigSchema()
   804  	configVal, err := s.Backend.Config(schema)
   805  	if err != nil {
   806  		diags = diags.Append(tfdiags.Sourceless(
   807  			tfdiags.Error,
   808  			"Failed to decode current backend config",
   809  			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),
   810  		))
   811  		return nil, diags
   812  	}
   813  
   814  	// Validate the config and then configure the backend
   815  	newVal, validDiags := b.PrepareConfig(configVal)
   816  	diags = diags.Append(validDiags)
   817  	if validDiags.HasErrors() {
   818  		return nil, diags
   819  	}
   820  
   821  	configDiags := b.Configure(newVal)
   822  	diags = diags.Append(configDiags)
   823  	if configDiags.HasErrors() {
   824  		return nil, diags
   825  	}
   826  
   827  	return b, diags
   828  }
   829  
   830  //-------------------------------------------------------------------
   831  // Backend Config Scenarios
   832  //
   833  // The functions below cover handling all the various scenarios that
   834  // can exist when loading a backend. They are named in the format of
   835  // "backend_C_R_S" where C, R, S may be upper or lowercase. Lowercase
   836  // means it is false, uppercase means it is true. The full set of eight
   837  // possible cases is handled.
   838  //
   839  // The fields are:
   840  //
   841  //   * C - Backend configuration is set and changed in TF files
   842  //   * R - Legacy remote state is set
   843  //   * S - Backend configuration is set in the state
   844  //
   845  //-------------------------------------------------------------------
   846  
   847  // Unconfiguring a backend (moving from backend => local).
   848  func (m *Meta) backend_c_r_S(c *configs.Backend, cHash int, sMgr *clistate.LocalState, output bool) (backend.Backend, tfdiags.Diagnostics) {
   849  	var diags tfdiags.Diagnostics
   850  
   851  	s := sMgr.State()
   852  
   853  	cloudMode := cloud.DetectConfigChangeType(s.Backend, c, false)
   854  	diags = diags.Append(m.assertSupportedCloudInitOptions(cloudMode))
   855  	if diags.HasErrors() {
   856  		return nil, diags
   857  	}
   858  
   859  	// Get the backend type for output
   860  	backendType := s.Backend.Type
   861  
   862  	if cloudMode == cloud.ConfigMigrationOut {
   863  		m.Ui.Output("Migrating from Terraform Cloud to local state.")
   864  	} else {
   865  		m.Ui.Output(fmt.Sprintf(strings.TrimSpace(outputBackendMigrateLocal), s.Backend.Type))
   866  	}
   867  
   868  	// Grab a purely local backend to get the local state if it exists
   869  	localB, moreDiags := m.Backend(&BackendOpts{ForceLocal: true, Init: true})
   870  	diags = diags.Append(moreDiags)
   871  	if moreDiags.HasErrors() {
   872  		return nil, diags
   873  	}
   874  
   875  	// Initialize the configured backend
   876  	b, moreDiags := m.savedBackend(sMgr)
   877  	diags = diags.Append(moreDiags)
   878  	if moreDiags.HasErrors() {
   879  		return nil, diags
   880  	}
   881  
   882  	// Perform the migration
   883  	err := m.backendMigrateState(&backendMigrateOpts{
   884  		SourceType:      s.Backend.Type,
   885  		DestinationType: "local",
   886  		Source:          b,
   887  		Destination:     localB,
   888  	})
   889  	if err != nil {
   890  		diags = diags.Append(err)
   891  		return nil, diags
   892  	}
   893  
   894  	// Remove the stored metadata
   895  	s.Backend = nil
   896  	if err := sMgr.WriteState(s); err != nil {
   897  		diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendClearSaved), err))
   898  		return nil, diags
   899  	}
   900  	if err := sMgr.PersistState(); err != nil {
   901  		diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendClearSaved), err))
   902  		return nil, diags
   903  	}
   904  
   905  	if output {
   906  		m.Ui.Output(m.Colorize().Color(fmt.Sprintf(
   907  			"[reset][green]\n\n"+
   908  				strings.TrimSpace(successBackendUnset), backendType)))
   909  	}
   910  
   911  	// Return no backend
   912  	return nil, diags
   913  }
   914  
   915  // Configuring a backend for the first time.
   916  func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.LocalState, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) {
   917  	var diags tfdiags.Diagnostics
   918  
   919  	// Grab a purely local backend to get the local state if it exists
   920  	localB, localBDiags := m.Backend(&BackendOpts{ForceLocal: true, Init: true})
   921  	if localBDiags.HasErrors() {
   922  		diags = diags.Append(localBDiags)
   923  		return nil, diags
   924  	}
   925  
   926  	workspaces, err := localB.Workspaces()
   927  	if err != nil {
   928  		diags = diags.Append(fmt.Errorf(errBackendLocalRead, err))
   929  		return nil, diags
   930  	}
   931  
   932  	var localStates []statemgr.Full
   933  	for _, workspace := range workspaces {
   934  		localState, err := localB.StateMgr(workspace)
   935  		if err != nil {
   936  			diags = diags.Append(fmt.Errorf(errBackendLocalRead, err))
   937  			return nil, diags
   938  		}
   939  		if err := localState.RefreshState(); err != nil {
   940  			diags = diags.Append(fmt.Errorf(errBackendLocalRead, err))
   941  			return nil, diags
   942  		}
   943  
   944  		// We only care about non-empty states.
   945  		if localS := localState.State(); !localS.Empty() {
   946  			log.Printf("[TRACE] Meta.Backend: will need to migrate workspace states because of existing %q workspace", workspace)
   947  			localStates = append(localStates, localState)
   948  		} else {
   949  			log.Printf("[TRACE] Meta.Backend: ignoring local %q workspace because its state is empty", workspace)
   950  		}
   951  	}
   952  
   953  	cloudMode := cloud.DetectConfigChangeType(nil, c, len(localStates) > 0)
   954  	diags = diags.Append(m.assertSupportedCloudInitOptions(cloudMode))
   955  	if diags.HasErrors() {
   956  		return nil, diags
   957  	}
   958  
   959  	// Get the backend
   960  	b, configVal, moreDiags := m.backendInitFromConfig(c)
   961  	diags = diags.Append(moreDiags)
   962  	if diags.HasErrors() {
   963  		return nil, diags
   964  	}
   965  
   966  	if len(localStates) > 0 {
   967  		// Perform the migration
   968  		err = m.backendMigrateState(&backendMigrateOpts{
   969  			SourceType:      "local",
   970  			DestinationType: c.Type,
   971  			Source:          localB,
   972  			Destination:     b,
   973  		})
   974  		if err != nil {
   975  			diags = diags.Append(err)
   976  			return nil, diags
   977  		}
   978  
   979  		// we usually remove the local state after migration to prevent
   980  		// confusion, but adding a default local backend block to the config
   981  		// can get us here too. Don't delete our state if the old and new paths
   982  		// are the same.
   983  		erase := true
   984  		if newLocalB, ok := b.(*backendLocal.Local); ok {
   985  			if localB, ok := localB.(*backendLocal.Local); ok {
   986  				if newLocalB.PathsConflictWith(localB) {
   987  					erase = false
   988  					log.Printf("[TRACE] Meta.Backend: both old and new backends share the same local state paths, so not erasing old state")
   989  				}
   990  			}
   991  		}
   992  
   993  		if erase {
   994  			log.Printf("[TRACE] Meta.Backend: removing old state snapshots from old backend")
   995  			for _, localState := range localStates {
   996  				// We always delete the local state, unless that was our new state too.
   997  				if err := localState.WriteState(nil); err != nil {
   998  					diags = diags.Append(fmt.Errorf(errBackendMigrateLocalDelete, err))
   999  					return nil, diags
  1000  				}
  1001  				if err := localState.PersistState(nil); err != nil {
  1002  					diags = diags.Append(fmt.Errorf(errBackendMigrateLocalDelete, err))
  1003  					return nil, diags
  1004  				}
  1005  			}
  1006  		}
  1007  	}
  1008  
  1009  	if m.stateLock {
  1010  		view := views.NewStateLocker(arguments.ViewHuman, m.View)
  1011  		stateLocker := clistate.NewLocker(m.stateLockTimeout, view)
  1012  		if err := stateLocker.Lock(sMgr, "backend from plan"); err != nil {
  1013  			diags = diags.Append(fmt.Errorf("Error locking state: %s", err))
  1014  			return nil, diags
  1015  		}
  1016  		defer stateLocker.Unlock()
  1017  	}
  1018  
  1019  	configJSON, err := ctyjson.Marshal(configVal, b.ConfigSchema().ImpliedType())
  1020  	if err != nil {
  1021  		diags = diags.Append(fmt.Errorf("Can't serialize backend configuration as JSON: %s", err))
  1022  		return nil, diags
  1023  	}
  1024  
  1025  	// Store the metadata in our saved state location
  1026  	s := sMgr.State()
  1027  	if s == nil {
  1028  		s = legacy.NewState()
  1029  	}
  1030  	s.Backend = &legacy.BackendState{
  1031  		Type:      c.Type,
  1032  		ConfigRaw: json.RawMessage(configJSON),
  1033  		Hash:      uint64(cHash),
  1034  	}
  1035  
  1036  	// Verify that selected workspace exists in the backend.
  1037  	if opts.Init && b != nil {
  1038  		err := m.selectWorkspace(b)
  1039  		if err != nil {
  1040  			diags = diags.Append(err)
  1041  
  1042  			// FIXME: A compatibility oddity with the 'remote' backend.
  1043  			// As an awkward legacy UX, when the remote backend is configured and there
  1044  			// are no workspaces, the output to the user saying that there are none and
  1045  			// the user should create one with 'workspace new' takes the form of an
  1046  			// error message - even though it's happy path, expected behavior.
  1047  			//
  1048  			// Therefore, only return nil with errored diags for everything else, and
  1049  			// allow the remote backend to continue and write its configuration to state
  1050  			// even though no workspace is selected.
  1051  			if c.Type != "remote" {
  1052  				return nil, diags
  1053  			}
  1054  		}
  1055  	}
  1056  
  1057  	if err := sMgr.WriteState(s); err != nil {
  1058  		diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err))
  1059  		return nil, diags
  1060  	}
  1061  	if err := sMgr.PersistState(); err != nil {
  1062  		diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err))
  1063  		return nil, diags
  1064  	}
  1065  
  1066  	// By now the backend is successfully configured.  If using Terraform Cloud, the success
  1067  	// message is handled as part of the final init message
  1068  	if _, ok := b.(*cloud.Cloud); !ok {
  1069  		m.Ui.Output(m.Colorize().Color(fmt.Sprintf(
  1070  			"[reset][green]\n"+strings.TrimSpace(successBackendSet), s.Backend.Type)))
  1071  	}
  1072  
  1073  	return b, diags
  1074  }
  1075  
  1076  // Changing a previously saved backend.
  1077  func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clistate.LocalState, output bool, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) {
  1078  	var diags tfdiags.Diagnostics
  1079  
  1080  	// Get the old state
  1081  	s := sMgr.State()
  1082  
  1083  	cloudMode := cloud.DetectConfigChangeType(s.Backend, c, false)
  1084  	diags = diags.Append(m.assertSupportedCloudInitOptions(cloudMode))
  1085  	if diags.HasErrors() {
  1086  		return nil, diags
  1087  	}
  1088  
  1089  	if output {
  1090  		// Notify the user
  1091  		switch cloudMode {
  1092  		case cloud.ConfigChangeInPlace:
  1093  			m.Ui.Output("Terraform Cloud configuration has changed.")
  1094  		case cloud.ConfigMigrationIn:
  1095  			m.Ui.Output(fmt.Sprintf("Migrating from backend %q to Terraform Cloud.", s.Backend.Type))
  1096  		case cloud.ConfigMigrationOut:
  1097  			m.Ui.Output(fmt.Sprintf("Migrating from Terraform Cloud to backend %q.", c.Type))
  1098  		default:
  1099  			if s.Backend.Type != c.Type {
  1100  				output := fmt.Sprintf(outputBackendMigrateChange, s.Backend.Type, c.Type)
  1101  				m.Ui.Output(m.Colorize().Color(fmt.Sprintf(
  1102  					"[reset]%s\n",
  1103  					strings.TrimSpace(output))))
  1104  			} else {
  1105  				m.Ui.Output(m.Colorize().Color(fmt.Sprintf(
  1106  					"[reset]%s\n",
  1107  					strings.TrimSpace(outputBackendReconfigure))))
  1108  			}
  1109  		}
  1110  	}
  1111  
  1112  	// Get the backend
  1113  	b, configVal, moreDiags := m.backendInitFromConfig(c)
  1114  	diags = diags.Append(moreDiags)
  1115  	if moreDiags.HasErrors() {
  1116  		return nil, diags
  1117  	}
  1118  
  1119  	// If this is a migration into, out of, or irrelevant to Terraform Cloud
  1120  	// mode then we will do state migration here. Otherwise, we just update
  1121  	// the working directory initialization directly, because Terraform Cloud
  1122  	// doesn't have configurable state storage anyway -- we're only changing
  1123  	// which workspaces are relevant to this configuration, not where their
  1124  	// state lives.
  1125  	if cloudMode != cloud.ConfigChangeInPlace {
  1126  		// Grab the existing backend
  1127  		oldB, oldBDiags := m.savedBackend(sMgr)
  1128  		diags = diags.Append(oldBDiags)
  1129  		if oldBDiags.HasErrors() {
  1130  			return nil, diags
  1131  		}
  1132  
  1133  		// Perform the migration
  1134  		err := m.backendMigrateState(&backendMigrateOpts{
  1135  			SourceType:      s.Backend.Type,
  1136  			DestinationType: c.Type,
  1137  			Source:          oldB,
  1138  			Destination:     b,
  1139  		})
  1140  		if err != nil {
  1141  			diags = diags.Append(err)
  1142  			return nil, diags
  1143  		}
  1144  
  1145  		if m.stateLock {
  1146  			view := views.NewStateLocker(arguments.ViewHuman, m.View)
  1147  			stateLocker := clistate.NewLocker(m.stateLockTimeout, view)
  1148  			if err := stateLocker.Lock(sMgr, "backend from plan"); err != nil {
  1149  				diags = diags.Append(fmt.Errorf("Error locking state: %s", err))
  1150  				return nil, diags
  1151  			}
  1152  			defer stateLocker.Unlock()
  1153  		}
  1154  	}
  1155  
  1156  	configJSON, err := ctyjson.Marshal(configVal, b.ConfigSchema().ImpliedType())
  1157  	if err != nil {
  1158  		diags = diags.Append(fmt.Errorf("Can't serialize backend configuration as JSON: %s", err))
  1159  		return nil, diags
  1160  	}
  1161  
  1162  	// Update the backend state
  1163  	s = sMgr.State()
  1164  	if s == nil {
  1165  		s = legacy.NewState()
  1166  	}
  1167  	s.Backend = &legacy.BackendState{
  1168  		Type:      c.Type,
  1169  		ConfigRaw: json.RawMessage(configJSON),
  1170  		Hash:      uint64(cHash),
  1171  	}
  1172  
  1173  	// Verify that selected workspace exist. Otherwise prompt user to create one
  1174  	if opts.Init && b != nil {
  1175  		if err := m.selectWorkspace(b); err != nil {
  1176  			diags = diags.Append(err)
  1177  			return b, diags
  1178  		}
  1179  	}
  1180  
  1181  	if err := sMgr.WriteState(s); err != nil {
  1182  		diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err))
  1183  		return nil, diags
  1184  	}
  1185  	if err := sMgr.PersistState(); err != nil {
  1186  		diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err))
  1187  		return nil, diags
  1188  	}
  1189  
  1190  	if output {
  1191  		// By now the backend is successfully configured.  If using Terraform Cloud, the success
  1192  		// message is handled as part of the final init message
  1193  		if _, ok := b.(*cloud.Cloud); !ok {
  1194  			m.Ui.Output(m.Colorize().Color(fmt.Sprintf(
  1195  				"[reset][green]\n"+strings.TrimSpace(successBackendSet), s.Backend.Type)))
  1196  		}
  1197  	}
  1198  
  1199  	return b, diags
  1200  }
  1201  
  1202  // Initializing a saved backend from the cache file (legacy state file)
  1203  //
  1204  // TODO: This is extremely similar to Meta.backendFromState() but for legacy reasons this is the
  1205  // function used by the migration APIs within this file. The other handles 'init -backend=false',
  1206  // specifically.
  1207  func (m *Meta) savedBackend(sMgr *clistate.LocalState) (backend.Backend, tfdiags.Diagnostics) {
  1208  	var diags tfdiags.Diagnostics
  1209  
  1210  	s := sMgr.State()
  1211  
  1212  	// Get the backend
  1213  	f := backendInit.Backend(s.Backend.Type)
  1214  	if f == nil {
  1215  		diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), s.Backend.Type))
  1216  		return nil, diags
  1217  	}
  1218  	b := f()
  1219  
  1220  	// The configuration saved in the working directory state file is used
  1221  	// in this case, since it will contain any additional values that
  1222  	// were provided via -backend-config arguments on terraform init.
  1223  	schema := b.ConfigSchema()
  1224  	configVal, err := s.Backend.Config(schema)
  1225  	if err != nil {
  1226  		diags = diags.Append(tfdiags.Sourceless(
  1227  			tfdiags.Error,
  1228  			"Failed to decode current backend config",
  1229  			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),
  1230  		))
  1231  		return nil, diags
  1232  	}
  1233  
  1234  	// Validate the config and then configure the backend
  1235  	newVal, validDiags := b.PrepareConfig(configVal)
  1236  	diags = diags.Append(validDiags)
  1237  	if validDiags.HasErrors() {
  1238  		return nil, diags
  1239  	}
  1240  
  1241  	configDiags := b.Configure(newVal)
  1242  	diags = diags.Append(configDiags)
  1243  	if configDiags.HasErrors() {
  1244  		return nil, diags
  1245  	}
  1246  
  1247  	return b, diags
  1248  }
  1249  
  1250  func (m *Meta) updateSavedBackendHash(cHash int, sMgr *clistate.LocalState) tfdiags.Diagnostics {
  1251  	var diags tfdiags.Diagnostics
  1252  
  1253  	s := sMgr.State()
  1254  
  1255  	if s.Backend.Hash != uint64(cHash) {
  1256  		s.Backend.Hash = uint64(cHash)
  1257  		if err := sMgr.WriteState(s); err != nil {
  1258  			diags = diags.Append(err)
  1259  		}
  1260  	}
  1261  
  1262  	return diags
  1263  }
  1264  
  1265  //-------------------------------------------------------------------
  1266  // Reusable helper functions for backend management
  1267  //-------------------------------------------------------------------
  1268  
  1269  // backendConfigNeedsMigration returns true if migration might be required to
  1270  // move from the configured backend to the given cached backend config.
  1271  //
  1272  // This must be called with the synthetic *configs.Backend that results from
  1273  // merging in any command-line options for correct behavior.
  1274  //
  1275  // If either the given configuration or cached configuration are invalid then
  1276  // this function will conservatively assume that migration is required,
  1277  // expecting that the migration code will subsequently deal with the same
  1278  // errors.
  1279  func (m *Meta) backendConfigNeedsMigration(c *configs.Backend, s *legacy.BackendState) bool {
  1280  	if s == nil || s.Empty() {
  1281  		log.Print("[TRACE] backendConfigNeedsMigration: no cached config, so migration is required")
  1282  		return true
  1283  	}
  1284  	if c.Type != s.Type {
  1285  		log.Printf("[TRACE] backendConfigNeedsMigration: type changed from %q to %q, so migration is required", s.Type, c.Type)
  1286  		return true
  1287  	}
  1288  
  1289  	// We need the backend's schema to do our comparison here.
  1290  	f := backendInit.Backend(c.Type)
  1291  	if f == nil {
  1292  		log.Printf("[TRACE] backendConfigNeedsMigration: no backend of type %q, which migration codepath must handle", c.Type)
  1293  		return true // let the migration codepath deal with the missing backend
  1294  	}
  1295  	b := f()
  1296  
  1297  	schema := b.ConfigSchema()
  1298  	decSpec := schema.NoneRequired().DecoderSpec()
  1299  	givenVal, diags := hcldec.Decode(c.Config, decSpec, nil)
  1300  	if diags.HasErrors() {
  1301  		log.Printf("[TRACE] backendConfigNeedsMigration: failed to decode given config; migration codepath must handle problem: %s", diags.Error())
  1302  		return true // let the migration codepath deal with these errors
  1303  	}
  1304  
  1305  	cachedVal, err := s.Config(schema)
  1306  	if err != nil {
  1307  		log.Printf("[TRACE] backendConfigNeedsMigration: failed to decode cached config; migration codepath must handle problem: %s", err)
  1308  		return true // let the migration codepath deal with the error
  1309  	}
  1310  
  1311  	// If we get all the way down here then it's the exact equality of the
  1312  	// two decoded values that decides our outcome. It's safe to use RawEquals
  1313  	// here (rather than Equals) because we know that unknown values can
  1314  	// never appear in backend configurations.
  1315  	if cachedVal.RawEquals(givenVal) {
  1316  		log.Print("[TRACE] backendConfigNeedsMigration: given configuration matches cached configuration, so no migration is required")
  1317  		return false
  1318  	}
  1319  	log.Print("[TRACE] backendConfigNeedsMigration: configuration values have changed, so migration is required")
  1320  	return true
  1321  }
  1322  
  1323  func (m *Meta) backendInitFromConfig(c *configs.Backend) (backend.Backend, cty.Value, tfdiags.Diagnostics) {
  1324  	var diags tfdiags.Diagnostics
  1325  
  1326  	// Get the backend
  1327  	f := backendInit.Backend(c.Type)
  1328  	if f == nil {
  1329  		diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendNewUnknown), c.Type))
  1330  		return nil, cty.NilVal, diags
  1331  	}
  1332  	b := f()
  1333  
  1334  	schema := b.ConfigSchema()
  1335  	decSpec := schema.NoneRequired().DecoderSpec()
  1336  	configVal, hclDiags := hcldec.Decode(c.Config, decSpec, nil)
  1337  	diags = diags.Append(hclDiags)
  1338  	if hclDiags.HasErrors() {
  1339  		return nil, cty.NilVal, diags
  1340  	}
  1341  
  1342  	// TODO: test
  1343  	if m.Input() {
  1344  		var err error
  1345  		configVal, err = m.inputForSchema(configVal, schema)
  1346  		if err != nil {
  1347  			diags = diags.Append(fmt.Errorf("Error asking for input to configure backend %q: %s", c.Type, err))
  1348  		}
  1349  
  1350  		// We get an unknown here if the if the user aborted input, but we can't
  1351  		// turn that into a config value, so set it to null and let the provider
  1352  		// handle it in PrepareConfig.
  1353  		if !configVal.IsKnown() {
  1354  			configVal = cty.NullVal(configVal.Type())
  1355  		}
  1356  	}
  1357  
  1358  	newVal, validateDiags := b.PrepareConfig(configVal)
  1359  	diags = diags.Append(validateDiags.InConfigBody(c.Config, ""))
  1360  	if validateDiags.HasErrors() {
  1361  		return nil, cty.NilVal, diags
  1362  	}
  1363  
  1364  	configureDiags := b.Configure(newVal)
  1365  	diags = diags.Append(configureDiags.InConfigBody(c.Config, ""))
  1366  
  1367  	return b, configVal, diags
  1368  }
  1369  
  1370  // Helper method to ignore remote/cloud backend version conflicts. Only call this
  1371  // for commands which cannot accidentally upgrade remote state files.
  1372  func (m *Meta) ignoreRemoteVersionConflict(b backend.Backend) {
  1373  	if back, ok := b.(BackendWithRemoteTerraformVersion); ok {
  1374  		back.IgnoreVersionConflict()
  1375  	}
  1376  }
  1377  
  1378  // Helper method to check the local Terraform version against the configured
  1379  // version in the remote workspace, returning diagnostics if they conflict.
  1380  func (m *Meta) remoteVersionCheck(b backend.Backend, workspace string) tfdiags.Diagnostics {
  1381  	var diags tfdiags.Diagnostics
  1382  
  1383  	if back, ok := b.(BackendWithRemoteTerraformVersion); ok {
  1384  		// Allow user override based on command-line flag
  1385  		if m.ignoreRemoteVersion {
  1386  			back.IgnoreVersionConflict()
  1387  		}
  1388  		// If the override is set, this check will return a warning instead of
  1389  		// an error
  1390  		versionDiags := back.VerifyWorkspaceTerraformVersion(workspace)
  1391  		diags = diags.Append(versionDiags)
  1392  		// If there are no errors resulting from this check, we do not need to
  1393  		// check again
  1394  		if !diags.HasErrors() {
  1395  			back.IgnoreVersionConflict()
  1396  		}
  1397  	}
  1398  
  1399  	return diags
  1400  }
  1401  
  1402  // assertSupportedCloudInitOptions returns diagnostics with errors if the
  1403  // init-related command line options (implied inside the Meta receiver)
  1404  // are incompatible with the given cloud configuration change mode.
  1405  func (m *Meta) assertSupportedCloudInitOptions(mode cloud.ConfigChangeMode) tfdiags.Diagnostics {
  1406  	var diags tfdiags.Diagnostics
  1407  	if mode.InvolvesCloud() {
  1408  		log.Printf("[TRACE] Meta.Backend: Terraform Cloud mode initialization type: %s", mode)
  1409  		if m.reconfigure {
  1410  			if mode.IsCloudMigration() {
  1411  				diags = diags.Append(tfdiags.Sourceless(
  1412  					tfdiags.Error,
  1413  					"Invalid command-line option",
  1414  					"The -reconfigure option is unsupported when migrating to Terraform Cloud, because activating Terraform Cloud involves some additional steps.",
  1415  				))
  1416  			} else {
  1417  				diags = diags.Append(tfdiags.Sourceless(
  1418  					tfdiags.Error,
  1419  					"Invalid command-line option",
  1420  					"The -reconfigure option is for in-place reconfiguration of state backends only, and is not needed when changing Terraform Cloud settings.\n\nWhen using Terraform Cloud, initialization automatically activates any new Cloud configuration settings.",
  1421  				))
  1422  			}
  1423  		}
  1424  		if m.migrateState {
  1425  			name := "-migrate-state"
  1426  			if m.forceInitCopy {
  1427  				// -force copy implies -migrate-state in "terraform init",
  1428  				// so m.migrateState is forced to true in this case even if
  1429  				// the user didn't actually specify it. We'll use the other
  1430  				// name here to avoid being confusing, then.
  1431  				name = "-force-copy"
  1432  			}
  1433  			if mode.IsCloudMigration() {
  1434  				diags = diags.Append(tfdiags.Sourceless(
  1435  					tfdiags.Error,
  1436  					"Invalid command-line option",
  1437  					fmt.Sprintf("The %s option is for migration between state backends only, and is not applicable when using Terraform Cloud.\n\nTerraform Cloud migration has additional steps, configured by interactive prompts.", name),
  1438  				))
  1439  			} else {
  1440  				diags = diags.Append(tfdiags.Sourceless(
  1441  					tfdiags.Error,
  1442  					"Invalid command-line option",
  1443  					fmt.Sprintf("The %s option is for migration between state backends only, and is not applicable when using Terraform Cloud.\n\nState storage is handled automatically by Terraform Cloud and so the state storage location is not configurable.", name),
  1444  				))
  1445  			}
  1446  		}
  1447  	}
  1448  	return diags
  1449  }
  1450  
  1451  //-------------------------------------------------------------------
  1452  // Output constants and initialization code
  1453  //-------------------------------------------------------------------
  1454  
  1455  const errBackendLocalRead = `
  1456  Error reading local state: %s
  1457  
  1458  Terraform is trying to read your local state to determine if there is
  1459  state to migrate to your newly configured backend. Terraform can't continue
  1460  without this check because that would risk losing state. Please resolve the
  1461  error above and try again.
  1462  `
  1463  
  1464  const errBackendMigrateLocalDelete = `
  1465  Error deleting local state after migration: %s
  1466  
  1467  Your local state is deleted after successfully migrating it to the newly
  1468  configured backend. As part of the deletion process, a backup is made at
  1469  the standard backup path unless explicitly asked not to. To cleanly operate
  1470  with a backend, we must delete the local state file. Please resolve the
  1471  issue above and retry the command.
  1472  `
  1473  
  1474  const errBackendNewUnknown = `
  1475  The backend %q could not be found.
  1476  
  1477  This is the backend specified in your Terraform configuration file.
  1478  This error could be a simple typo in your configuration, but it can also
  1479  be caused by using a Terraform version that doesn't support the specified
  1480  backend type. Please check your configuration and your Terraform version.
  1481  
  1482  If you'd like to run Terraform and store state locally, you can fix this
  1483  error by removing the backend configuration from your configuration.
  1484  `
  1485  
  1486  const errBackendNoExistingWorkspaces = `
  1487  No existing workspaces.
  1488  
  1489  Use the "terraform workspace" command to create and select a new workspace.
  1490  If the backend already contains existing workspaces, you may need to update
  1491  the backend configuration.
  1492  `
  1493  
  1494  const errBackendSavedUnknown = `
  1495  The backend %q could not be found.
  1496  
  1497  This is the backend that this Terraform environment is configured to use
  1498  both in your configuration and saved locally as your last-used backend.
  1499  If it isn't found, it could mean an alternate version of Terraform was
  1500  used with this configuration. Please use the proper version of Terraform that
  1501  contains support for this backend.
  1502  
  1503  If you'd like to force remove this backend, you must update your configuration
  1504  to not use the backend and run "terraform init" (or any other command) again.
  1505  `
  1506  
  1507  const errBackendClearSaved = `
  1508  Error clearing the backend configuration: %s
  1509  
  1510  Terraform removes the saved backend configuration when you're removing a
  1511  configured backend. This must be done so future Terraform runs know to not
  1512  use the backend configuration. Please look at the error above, resolve it,
  1513  and try again.
  1514  `
  1515  
  1516  const errBackendInit = `
  1517  Reason: %s
  1518  
  1519  The "backend" is the interface that Terraform uses to store state,
  1520  perform operations, etc. If this message is showing up, it means that the
  1521  Terraform configuration you're using is using a custom configuration for
  1522  the Terraform backend.
  1523  
  1524  Changes to backend configurations require reinitialization. This allows
  1525  Terraform to set up the new configuration, copy existing state, etc. Please run
  1526  "terraform init" with either the "-reconfigure" or "-migrate-state" flags to
  1527  use the current configuration.
  1528  
  1529  If the change reason above is incorrect, please verify your configuration
  1530  hasn't changed and try again. At this point, no changes to your existing
  1531  configuration or state have been made.
  1532  `
  1533  
  1534  const errBackendInitCloud = `
  1535  Reason: %s.
  1536  
  1537  Changes to the Terraform Cloud configuration block require reinitialization, to discover any changes to the available workspaces.
  1538  
  1539  To re-initialize, run:
  1540    terraform init
  1541  
  1542  Terraform has not yet made changes to your existing configuration or state.
  1543  `
  1544  
  1545  const errBackendWriteSaved = `
  1546  Error saving the backend configuration: %s
  1547  
  1548  Terraform saves the complete backend configuration in a local file for
  1549  configuring the backend on future operations. This cannot be disabled. Errors
  1550  are usually due to simple file permission errors. Please look at the error
  1551  above, resolve it, and try again.
  1552  `
  1553  
  1554  const outputBackendMigrateChange = `
  1555  Terraform detected that the backend type changed from %q to %q.
  1556  `
  1557  
  1558  const outputBackendMigrateLocal = `
  1559  Terraform has detected you're unconfiguring your previously set %q backend.
  1560  `
  1561  
  1562  const outputBackendReconfigure = `
  1563  [reset][bold]Backend configuration changed![reset]
  1564  
  1565  Terraform has detected that the configuration specified for the backend
  1566  has changed. Terraform will now check for existing state in the backends.
  1567  `
  1568  
  1569  const inputCloudInitCreateWorkspace = `
  1570  There are no workspaces with the configured tags (%s)
  1571  in your Terraform Cloud organization. To finish initializing, Terraform needs at
  1572  least one workspace available.
  1573  
  1574  Terraform can create a properly tagged workspace for you now. Please enter a
  1575  name to create a new Terraform Cloud workspace.
  1576  `
  1577  
  1578  const successBackendUnset = `
  1579  Successfully unset the backend %q. Terraform will now operate locally.
  1580  `
  1581  
  1582  const successBackendSet = `
  1583  Successfully configured the backend %q! Terraform will automatically
  1584  use this backend unless the backend configuration changes.
  1585  `
  1586  
  1587  var migrateOrReconfigDiag = tfdiags.Sourceless(
  1588  	tfdiags.Error,
  1589  	"Backend configuration changed",
  1590  	"A change in the backend configuration has been detected, which may require migrating existing state.\n\n"+
  1591  		"If you wish to attempt automatic migration of the state, use \"terraform init -migrate-state\".\n"+
  1592  		`If you wish to store the current configuration with no changes to the state, use "terraform init -reconfigure".`)