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