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