github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/meta_backend.go (about)

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