github.com/opentofu/opentofu@v1.7.1/internal/command/meta_backend.go (about)

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