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