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".`)