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