github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/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/cycloidio/terraform/backend" 20 "github.com/cycloidio/terraform/cloud" 21 "github.com/cycloidio/terraform/command/arguments" 22 "github.com/cycloidio/terraform/command/clistate" 23 "github.com/cycloidio/terraform/command/views" 24 "github.com/cycloidio/terraform/configs" 25 "github.com/cycloidio/terraform/plans" 26 "github.com/cycloidio/terraform/states/statemgr" 27 "github.com/cycloidio/terraform/terraform" 28 "github.com/cycloidio/terraform/tfdiags" 29 "github.com/zclconf/go-cty/cty" 30 ctyjson "github.com/zclconf/go-cty/cty/json" 31 32 backendInit "github.com/cycloidio/terraform/backend/init" 33 backendLocal "github.com/cycloidio/terraform/backend/local" 34 legacy "github.com/cycloidio/terraform/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 diags = diags.Append(&hcl.Diagnostic{ 470 Severity: hcl.DiagError, 471 Summary: "Invalid backend type", 472 Detail: fmt.Sprintf("There is no backend type named %q.", c.Type), 473 Subject: &c.TypeRange, 474 }) 475 return nil, 0, diags 476 } 477 b := bf() 478 479 configSchema := b.ConfigSchema() 480 configBody := c.Config 481 configHash := c.Hash(configSchema) 482 483 // If we have an override configuration body then we must apply it now. 484 if opts.ConfigOverride != nil { 485 log.Println("[TRACE] Meta.Backend: merging -backend-config=... CLI overrides into backend configuration") 486 configBody = configs.MergeBodies(configBody, opts.ConfigOverride) 487 } 488 489 log.Printf("[TRACE] Meta.Backend: built configuration for %q backend with hash value %d", c.Type, configHash) 490 491 // We'll shallow-copy configs.Backend here so that we can replace the 492 // body without affecting others that hold this reference. 493 configCopy := *c 494 configCopy.Config = configBody 495 return &configCopy, configHash, diags 496 } 497 498 // backendFromConfig returns the initialized (not configured) backend 499 // directly from the config/state.. 500 // 501 // This function handles various edge cases around backend config loading. For 502 // example: new config changes, backend type changes, etc. 503 // 504 // As of the 0.12 release it can no longer migrate from legacy remote state 505 // to backends, and will instead instruct users to use 0.11 or earlier as 506 // a stepping-stone to do that migration. 507 // 508 // This function may query the user for input unless input is disabled, in 509 // which case this function will error. 510 func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) { 511 // Get the local backend configuration. 512 c, cHash, diags := m.backendConfig(opts) 513 if diags.HasErrors() { 514 return nil, diags 515 } 516 517 // ------------------------------------------------------------------------ 518 // For historical reasons, current backend configuration for a working 519 // directory is kept in a *state-like* file, using the legacy state 520 // structures in the Terraform package. It is not actually a Terraform 521 // state, and so only the "backend" portion of it is actually used. 522 // 523 // The remainder of this code often confusingly refers to this as a "state", 524 // so it's unfortunately important to remember that this is not actually 525 // what we _usually_ think of as "state", and is instead a local working 526 // directory "backend configuration state" that is never persisted anywhere. 527 // 528 // Since the "real" state has since moved on to be represented by 529 // states.State, we can recognize the special meaning of state that applies 530 // to this function and its callees by their continued use of the 531 // otherwise-obsolete terraform.State. 532 // ------------------------------------------------------------------------ 533 534 // Get the path to where we store a local cache of backend configuration 535 // if we're using a remote backend. This may not yet exist which means 536 // we haven't used a non-local backend before. That is okay. 537 statePath := filepath.Join(m.DataDir(), DefaultStateFilename) 538 sMgr := &clistate.LocalState{Path: statePath} 539 if err := sMgr.RefreshState(); err != nil { 540 diags = diags.Append(fmt.Errorf("Failed to load state: %s", err)) 541 return nil, diags 542 } 543 544 // Load the state, it must be non-nil for the tests below but can be empty 545 s := sMgr.State() 546 if s == nil { 547 log.Printf("[TRACE] Meta.Backend: backend has not previously been initialized in this working directory") 548 s = legacy.NewState() 549 } else if s.Backend != nil { 550 log.Printf("[TRACE] Meta.Backend: working directory was previously initialized for %q backend", s.Backend.Type) 551 } else { 552 log.Printf("[TRACE] Meta.Backend: working directory was previously initialized but has no backend (is using legacy remote state?)") 553 } 554 555 // if we want to force reconfiguration of the backend, we set the backend 556 // state to nil on this copy. This will direct us through the correct 557 // configuration path in the switch statement below. 558 if m.reconfigure { 559 s.Backend = nil 560 } 561 562 // Upon return, we want to set the state we're using in-memory so that 563 // we can access it for commands. 564 m.backendState = nil 565 defer func() { 566 if s := sMgr.State(); s != nil && !s.Backend.Empty() { 567 m.backendState = s.Backend 568 } 569 }() 570 571 if !s.Remote.Empty() { 572 // Legacy remote state is no longer supported. User must first 573 // migrate with Terraform 0.11 or earlier. 574 diags = diags.Append(tfdiags.Sourceless( 575 tfdiags.Error, 576 "Legacy remote state not supported", 577 "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.", 578 )) 579 return nil, diags 580 } 581 582 // This switch statement covers all the different combinations of 583 // configuring new backends, updating previously-configured backends, etc. 584 switch { 585 // No configuration set at all. Pure local state. 586 case c == nil && s.Backend.Empty(): 587 log.Printf("[TRACE] Meta.Backend: using default local state only (no backend configuration, and no existing initialized backend)") 588 return nil, nil 589 590 // We're unsetting a backend (moving from backend => local) 591 case c == nil && !s.Backend.Empty(): 592 log.Printf("[TRACE] Meta.Backend: previously-initialized %q backend is no longer present in config", s.Backend.Type) 593 594 initReason := fmt.Sprintf("Unsetting the previously set backend %q", s.Backend.Type) 595 if !opts.Init { 596 diags = diags.Append(tfdiags.Sourceless( 597 tfdiags.Error, 598 "Backend initialization required, please run \"terraform init\"", 599 fmt.Sprintf(strings.TrimSpace(errBackendInit), initReason), 600 )) 601 return nil, diags 602 } 603 604 if s.Backend.Type != "cloud" && !m.migrateState { 605 diags = diags.Append(migrateOrReconfigDiag) 606 return nil, diags 607 } 608 609 return m.backend_c_r_S(c, cHash, sMgr, true) 610 611 // Configuring a backend for the first time or -reconfigure flag was used 612 case c != nil && s.Backend.Empty(): 613 log.Printf("[TRACE] Meta.Backend: moving from default local state only to %q backend", c.Type) 614 if !opts.Init { 615 if c.Type == "cloud" { 616 initReason := "Initial configuration of Terraform Cloud" 617 diags = diags.Append(tfdiags.Sourceless( 618 tfdiags.Error, 619 "Terraform Cloud initialization required: please run \"terraform init\"", 620 fmt.Sprintf(strings.TrimSpace(errBackendInitCloud), initReason), 621 )) 622 } else { 623 initReason := fmt.Sprintf("Initial configuration of the requested backend %q", c.Type) 624 diags = diags.Append(tfdiags.Sourceless( 625 tfdiags.Error, 626 "Backend initialization required, please run \"terraform init\"", 627 fmt.Sprintf(strings.TrimSpace(errBackendInit), initReason), 628 )) 629 } 630 return nil, diags 631 } 632 return m.backend_C_r_s(c, cHash, sMgr, opts) 633 // Potentially changing a backend configuration 634 case c != nil && !s.Backend.Empty(): 635 // We are not going to migrate if... 636 // 637 // We're not initializing 638 // AND the backend cache hash values match, indicating that the stored config is valid and completely unchanged. 639 // AND we're not providing any overrides. An override can mean a change overriding an unchanged backend block (indicated by the hash value). 640 if (uint64(cHash) == s.Backend.Hash) && (!opts.Init || opts.ConfigOverride == nil) { 641 log.Printf("[TRACE] Meta.Backend: using already-initialized, unchanged %q backend configuration", c.Type) 642 savedBackend, diags := m.savedBackend(sMgr) 643 // Verify that selected workspace exist. Otherwise prompt user to create one 644 if opts.Init && savedBackend != nil { 645 if err := m.selectWorkspace(savedBackend); err != nil { 646 diags = diags.Append(err) 647 return nil, diags 648 } 649 } 650 return savedBackend, diags 651 } 652 653 // If our configuration (the result of both the literal configuration and given 654 // -backend-config options) is the same, then we're just initializing a previously 655 // configured backend. The literal configuration may differ, however, so while we 656 // don't need to migrate, we update the backend cache hash value. 657 if !m.backendConfigNeedsMigration(c, s.Backend) { 658 log.Printf("[TRACE] Meta.Backend: using already-initialized %q backend configuration", c.Type) 659 savedBackend, moreDiags := m.savedBackend(sMgr) 660 diags = diags.Append(moreDiags) 661 if moreDiags.HasErrors() { 662 return nil, diags 663 } 664 665 // It's possible for a backend to be unchanged, and the config itself to 666 // have changed by moving a parameter from the config to `-backend-config` 667 // In this case, we update the Hash. 668 moreDiags = m.updateSavedBackendHash(cHash, sMgr) 669 if moreDiags.HasErrors() { 670 return nil, diags 671 } 672 // Verify that selected workspace exist. Otherwise prompt user to create one 673 if opts.Init && savedBackend != nil { 674 if err := m.selectWorkspace(savedBackend); err != nil { 675 diags = diags.Append(err) 676 return nil, diags 677 } 678 } 679 680 return savedBackend, diags 681 } 682 log.Printf("[TRACE] Meta.Backend: backend configuration has changed (from type %q to type %q)", s.Backend.Type, c.Type) 683 684 cloudMode := cloud.DetectConfigChangeType(s.Backend, c, false) 685 686 if !opts.Init { 687 //user ran another cmd that is not init but they are required to initialize because of a potential relevant change to their backend configuration 688 initDiag := m.determineInitReason(s.Backend.Type, c.Type, cloudMode) 689 diags = diags.Append(initDiag) 690 return nil, diags 691 } 692 693 if !cloudMode.InvolvesCloud() && !m.migrateState { 694 diags = diags.Append(migrateOrReconfigDiag) 695 return nil, diags 696 } 697 698 log.Printf("[WARN] backend config has changed since last init") 699 return m.backend_C_r_S_changed(c, cHash, sMgr, true, opts) 700 701 default: 702 diags = diags.Append(fmt.Errorf( 703 "Unhandled backend configuration state. This is a bug. Please\n"+ 704 "report this error with the following information.\n\n"+ 705 "Config Nil: %v\n"+ 706 "Saved Backend Empty: %v\n", 707 c == nil, s.Backend.Empty(), 708 )) 709 return nil, diags 710 } 711 } 712 713 func (m *Meta) determineInitReason(previousBackendType string, currentBackendType string, cloudMode cloud.ConfigChangeMode) tfdiags.Diagnostics { 714 initReason := "" 715 switch cloudMode { 716 case cloud.ConfigMigrationIn: 717 initReason = fmt.Sprintf("Changed from backend %q to Terraform Cloud", previousBackendType) 718 case cloud.ConfigMigrationOut: 719 initReason = fmt.Sprintf("Changed from Terraform Cloud to backend %q", previousBackendType) 720 case cloud.ConfigChangeInPlace: 721 initReason = "Terraform Cloud configuration block has changed" 722 default: 723 switch { 724 case previousBackendType != currentBackendType: 725 initReason = fmt.Sprintf("Backend type changed from %q to %q", previousBackendType, currentBackendType) 726 default: 727 initReason = "Backend configuration block has changed" 728 } 729 } 730 731 var diags tfdiags.Diagnostics 732 switch cloudMode { 733 case cloud.ConfigChangeInPlace: 734 diags = diags.Append(tfdiags.Sourceless( 735 tfdiags.Error, 736 "Terraform Cloud initialization required: please run \"terraform init\"", 737 fmt.Sprintf(strings.TrimSpace(errBackendInitCloud), initReason), 738 )) 739 case cloud.ConfigMigrationIn: 740 diags = diags.Append(tfdiags.Sourceless( 741 tfdiags.Error, 742 "Terraform Cloud initialization required: please run \"terraform init\"", 743 fmt.Sprintf(strings.TrimSpace(errBackendInitCloud), initReason), 744 )) 745 default: 746 diags = diags.Append(tfdiags.Sourceless( 747 tfdiags.Error, 748 "Backend initialization required: please run \"terraform init\"", 749 fmt.Sprintf(strings.TrimSpace(errBackendInit), initReason), 750 )) 751 } 752 753 return diags 754 } 755 756 // backendFromState returns the initialized (not configured) backend directly 757 // from the state. This should be used only when a user runs `terraform init 758 // -backend=false`. This function returns a local backend if there is no state 759 // or no backend configured. 760 func (m *Meta) backendFromState() (backend.Backend, tfdiags.Diagnostics) { 761 var diags tfdiags.Diagnostics 762 // Get the path to where we store a local cache of backend configuration 763 // if we're using a remote backend. This may not yet exist which means 764 // we haven't used a non-local backend before. That is okay. 765 statePath := filepath.Join(m.DataDir(), DefaultStateFilename) 766 sMgr := &clistate.LocalState{Path: statePath} 767 if err := sMgr.RefreshState(); err != nil { 768 diags = diags.Append(fmt.Errorf("Failed to load state: %s", err)) 769 return nil, diags 770 } 771 s := sMgr.State() 772 if s == nil { 773 // no state, so return a local backend 774 log.Printf("[TRACE] Meta.Backend: backend has not previously been initialized in this working directory") 775 return backendLocal.New(), diags 776 } 777 if s.Backend == nil { 778 // s.Backend is nil, so return a local backend 779 log.Printf("[TRACE] Meta.Backend: working directory was previously initialized but has no backend (is using legacy remote state?)") 780 return backendLocal.New(), diags 781 } 782 log.Printf("[TRACE] Meta.Backend: working directory was previously initialized for %q backend", s.Backend.Type) 783 784 //backend init function 785 if s.Backend.Type == "" { 786 return backendLocal.New(), diags 787 } 788 f := backendInit.Backend(s.Backend.Type) 789 if f == nil { 790 diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), s.Backend.Type)) 791 return nil, diags 792 } 793 b := f() 794 795 // The configuration saved in the working directory state file is used 796 // in this case, since it will contain any additional values that 797 // were provided via -backend-config arguments on terraform init. 798 schema := b.ConfigSchema() 799 configVal, err := s.Backend.Config(schema) 800 if err != nil { 801 diags = diags.Append(tfdiags.Sourceless( 802 tfdiags.Error, 803 "Failed to decode current backend config", 804 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), 805 )) 806 return nil, diags 807 } 808 809 // Validate the config and then configure the backend 810 newVal, validDiags := b.PrepareConfig(configVal) 811 diags = diags.Append(validDiags) 812 if validDiags.HasErrors() { 813 return nil, diags 814 } 815 816 configDiags := b.Configure(newVal) 817 diags = diags.Append(configDiags) 818 if configDiags.HasErrors() { 819 return nil, diags 820 } 821 822 return b, diags 823 } 824 825 //------------------------------------------------------------------- 826 // Backend Config Scenarios 827 // 828 // The functions below cover handling all the various scenarios that 829 // can exist when loading a backend. They are named in the format of 830 // "backend_C_R_S" where C, R, S may be upper or lowercase. Lowercase 831 // means it is false, uppercase means it is true. The full set of eight 832 // possible cases is handled. 833 // 834 // The fields are: 835 // 836 // * C - Backend configuration is set and changed in TF files 837 // * R - Legacy remote state is set 838 // * S - Backend configuration is set in the state 839 // 840 //------------------------------------------------------------------- 841 842 // Unconfiguring a backend (moving from backend => local). 843 func (m *Meta) backend_c_r_S(c *configs.Backend, cHash int, sMgr *clistate.LocalState, output bool) (backend.Backend, tfdiags.Diagnostics) { 844 var diags tfdiags.Diagnostics 845 846 s := sMgr.State() 847 848 cloudMode := cloud.DetectConfigChangeType(s.Backend, c, false) 849 diags = diags.Append(m.assertSupportedCloudInitOptions(cloudMode)) 850 if diags.HasErrors() { 851 return nil, diags 852 } 853 854 // Get the backend type for output 855 backendType := s.Backend.Type 856 857 if cloudMode == cloud.ConfigMigrationOut { 858 m.Ui.Output("Migrating from Terraform Cloud to local state.") 859 } else { 860 m.Ui.Output(fmt.Sprintf(strings.TrimSpace(outputBackendMigrateLocal), s.Backend.Type)) 861 } 862 863 // Grab a purely local backend to get the local state if it exists 864 localB, moreDiags := m.Backend(&BackendOpts{ForceLocal: true, Init: true}) 865 diags = diags.Append(moreDiags) 866 if moreDiags.HasErrors() { 867 return nil, diags 868 } 869 870 // Initialize the configured backend 871 b, moreDiags := m.savedBackend(sMgr) 872 diags = diags.Append(moreDiags) 873 if moreDiags.HasErrors() { 874 return nil, diags 875 } 876 877 // Perform the migration 878 err := m.backendMigrateState(&backendMigrateOpts{ 879 SourceType: s.Backend.Type, 880 DestinationType: "local", 881 Source: b, 882 Destination: localB, 883 }) 884 if err != nil { 885 diags = diags.Append(err) 886 return nil, diags 887 } 888 889 // Remove the stored metadata 890 s.Backend = nil 891 if err := sMgr.WriteState(s); err != nil { 892 diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendClearSaved), err)) 893 return nil, diags 894 } 895 if err := sMgr.PersistState(); err != nil { 896 diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendClearSaved), err)) 897 return nil, diags 898 } 899 900 if output { 901 m.Ui.Output(m.Colorize().Color(fmt.Sprintf( 902 "[reset][green]\n\n"+ 903 strings.TrimSpace(successBackendUnset), backendType))) 904 } 905 906 // Return no backend 907 return nil, diags 908 } 909 910 // Configuring a backend for the first time. 911 func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.LocalState, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) { 912 var diags tfdiags.Diagnostics 913 914 // Grab a purely local backend to get the local state if it exists 915 localB, localBDiags := m.Backend(&BackendOpts{ForceLocal: true, Init: true}) 916 if localBDiags.HasErrors() { 917 diags = diags.Append(localBDiags) 918 return nil, diags 919 } 920 921 workspaces, err := localB.Workspaces() 922 if err != nil { 923 diags = diags.Append(fmt.Errorf(errBackendLocalRead, err)) 924 return nil, diags 925 } 926 927 var localStates []statemgr.Full 928 for _, workspace := range workspaces { 929 localState, err := localB.StateMgr(workspace) 930 if err != nil { 931 diags = diags.Append(fmt.Errorf(errBackendLocalRead, err)) 932 return nil, diags 933 } 934 if err := localState.RefreshState(); err != nil { 935 diags = diags.Append(fmt.Errorf(errBackendLocalRead, err)) 936 return nil, diags 937 } 938 939 // We only care about non-empty states. 940 if localS := localState.State(); !localS.Empty() { 941 log.Printf("[TRACE] Meta.Backend: will need to migrate workspace states because of existing %q workspace", workspace) 942 localStates = append(localStates, localState) 943 } else { 944 log.Printf("[TRACE] Meta.Backend: ignoring local %q workspace because its state is empty", workspace) 945 } 946 } 947 948 cloudMode := cloud.DetectConfigChangeType(nil, c, len(localStates) > 0) 949 diags = diags.Append(m.assertSupportedCloudInitOptions(cloudMode)) 950 if diags.HasErrors() { 951 return nil, diags 952 } 953 954 // Get the backend 955 b, configVal, moreDiags := m.backendInitFromConfig(c) 956 diags = diags.Append(moreDiags) 957 if diags.HasErrors() { 958 return nil, diags 959 } 960 961 if len(localStates) > 0 { 962 // Perform the migration 963 err = m.backendMigrateState(&backendMigrateOpts{ 964 SourceType: "local", 965 DestinationType: c.Type, 966 Source: localB, 967 Destination: b, 968 }) 969 if err != nil { 970 diags = diags.Append(err) 971 return nil, diags 972 } 973 974 // we usually remove the local state after migration to prevent 975 // confusion, but adding a default local backend block to the config 976 // can get us here too. Don't delete our state if the old and new paths 977 // are the same. 978 erase := true 979 if newLocalB, ok := b.(*backendLocal.Local); ok { 980 if localB, ok := localB.(*backendLocal.Local); ok { 981 if newLocalB.PathsConflictWith(localB) { 982 erase = false 983 log.Printf("[TRACE] Meta.Backend: both old and new backends share the same local state paths, so not erasing old state") 984 } 985 } 986 } 987 988 if erase { 989 log.Printf("[TRACE] Meta.Backend: removing old state snapshots from old backend") 990 for _, localState := range localStates { 991 // We always delete the local state, unless that was our new state too. 992 if err := localState.WriteState(nil); err != nil { 993 diags = diags.Append(fmt.Errorf(errBackendMigrateLocalDelete, err)) 994 return nil, diags 995 } 996 if err := localState.PersistState(); err != nil { 997 diags = diags.Append(fmt.Errorf(errBackendMigrateLocalDelete, err)) 998 return nil, diags 999 } 1000 } 1001 } 1002 } 1003 1004 if m.stateLock { 1005 view := views.NewStateLocker(arguments.ViewHuman, m.View) 1006 stateLocker := clistate.NewLocker(m.stateLockTimeout, view) 1007 if err := stateLocker.Lock(sMgr, "backend from plan"); err != nil { 1008 diags = diags.Append(fmt.Errorf("Error locking state: %s", err)) 1009 return nil, diags 1010 } 1011 defer stateLocker.Unlock() 1012 } 1013 1014 configJSON, err := ctyjson.Marshal(configVal, b.ConfigSchema().ImpliedType()) 1015 if err != nil { 1016 diags = diags.Append(fmt.Errorf("Can't serialize backend configuration as JSON: %s", err)) 1017 return nil, diags 1018 } 1019 1020 // Store the metadata in our saved state location 1021 s := sMgr.State() 1022 if s == nil { 1023 s = legacy.NewState() 1024 } 1025 s.Backend = &legacy.BackendState{ 1026 Type: c.Type, 1027 ConfigRaw: json.RawMessage(configJSON), 1028 Hash: uint64(cHash), 1029 } 1030 1031 // Verify that selected workspace exists in the backend. 1032 if opts.Init && b != nil { 1033 err := m.selectWorkspace(b) 1034 if err != nil { 1035 diags = diags.Append(err) 1036 1037 // FIXME: A compatibility oddity with the 'remote' backend. 1038 // As an awkward legacy UX, when the remote backend is configured and there 1039 // are no workspaces, the output to the user saying that there are none and 1040 // the user should create one with 'workspace new' takes the form of an 1041 // error message - even though it's happy path, expected behavior. 1042 // 1043 // Therefore, only return nil with errored diags for everything else, and 1044 // allow the remote backend to continue and write its configuration to state 1045 // even though no workspace is selected. 1046 if c.Type != "remote" { 1047 return nil, diags 1048 } 1049 } 1050 } 1051 1052 if err := sMgr.WriteState(s); err != nil { 1053 diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err)) 1054 return nil, diags 1055 } 1056 if err := sMgr.PersistState(); err != nil { 1057 diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err)) 1058 return nil, diags 1059 } 1060 1061 // By now the backend is successfully configured. If using Terraform Cloud, the success 1062 // message is handled as part of the final init message 1063 if _, ok := b.(*cloud.Cloud); !ok { 1064 m.Ui.Output(m.Colorize().Color(fmt.Sprintf( 1065 "[reset][green]\n"+strings.TrimSpace(successBackendSet), s.Backend.Type))) 1066 } 1067 1068 return b, diags 1069 } 1070 1071 // Changing a previously saved backend. 1072 func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clistate.LocalState, output bool, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) { 1073 var diags tfdiags.Diagnostics 1074 1075 // Get the old state 1076 s := sMgr.State() 1077 1078 cloudMode := cloud.DetectConfigChangeType(s.Backend, c, false) 1079 diags = diags.Append(m.assertSupportedCloudInitOptions(cloudMode)) 1080 if diags.HasErrors() { 1081 return nil, diags 1082 } 1083 1084 if output { 1085 // Notify the user 1086 switch cloudMode { 1087 case cloud.ConfigChangeInPlace: 1088 m.Ui.Output("Terraform Cloud configuration has changed.") 1089 case cloud.ConfigMigrationIn: 1090 m.Ui.Output(fmt.Sprintf("Migrating from backend %q to Terraform Cloud.", s.Backend.Type)) 1091 case cloud.ConfigMigrationOut: 1092 m.Ui.Output(fmt.Sprintf("Migrating from Terraform Cloud to backend %q.", c.Type)) 1093 default: 1094 if s.Backend.Type != c.Type { 1095 output := fmt.Sprintf(outputBackendMigrateChange, s.Backend.Type, c.Type) 1096 m.Ui.Output(m.Colorize().Color(fmt.Sprintf( 1097 "[reset]%s\n", 1098 strings.TrimSpace(output)))) 1099 } else { 1100 m.Ui.Output(m.Colorize().Color(fmt.Sprintf( 1101 "[reset]%s\n", 1102 strings.TrimSpace(outputBackendReconfigure)))) 1103 } 1104 } 1105 } 1106 1107 // Get the backend 1108 b, configVal, moreDiags := m.backendInitFromConfig(c) 1109 diags = diags.Append(moreDiags) 1110 if moreDiags.HasErrors() { 1111 return nil, diags 1112 } 1113 1114 // If this is a migration into, out of, or irrelevant to Terraform Cloud 1115 // mode then we will do state migration here. Otherwise, we just update 1116 // the working directory initialization directly, because Terraform Cloud 1117 // doesn't have configurable state storage anyway -- we're only changing 1118 // which workspaces are relevant to this configuration, not where their 1119 // state lives. 1120 if cloudMode != cloud.ConfigChangeInPlace { 1121 // Grab the existing backend 1122 oldB, oldBDiags := m.savedBackend(sMgr) 1123 diags = diags.Append(oldBDiags) 1124 if oldBDiags.HasErrors() { 1125 return nil, diags 1126 } 1127 1128 // Perform the migration 1129 err := m.backendMigrateState(&backendMigrateOpts{ 1130 SourceType: s.Backend.Type, 1131 DestinationType: c.Type, 1132 Source: oldB, 1133 Destination: b, 1134 }) 1135 if err != nil { 1136 diags = diags.Append(err) 1137 return nil, diags 1138 } 1139 1140 if m.stateLock { 1141 view := views.NewStateLocker(arguments.ViewHuman, m.View) 1142 stateLocker := clistate.NewLocker(m.stateLockTimeout, view) 1143 if err := stateLocker.Lock(sMgr, "backend from plan"); err != nil { 1144 diags = diags.Append(fmt.Errorf("Error locking state: %s", err)) 1145 return nil, diags 1146 } 1147 defer stateLocker.Unlock() 1148 } 1149 } 1150 1151 configJSON, err := ctyjson.Marshal(configVal, b.ConfigSchema().ImpliedType()) 1152 if err != nil { 1153 diags = diags.Append(fmt.Errorf("Can't serialize backend configuration as JSON: %s", err)) 1154 return nil, diags 1155 } 1156 1157 // Update the backend state 1158 s = sMgr.State() 1159 if s == nil { 1160 s = legacy.NewState() 1161 } 1162 s.Backend = &legacy.BackendState{ 1163 Type: c.Type, 1164 ConfigRaw: json.RawMessage(configJSON), 1165 Hash: uint64(cHash), 1166 } 1167 1168 // Verify that selected workspace exist. Otherwise prompt user to create one 1169 if opts.Init && b != nil { 1170 if err := m.selectWorkspace(b); err != nil { 1171 diags = diags.Append(err) 1172 return b, diags 1173 } 1174 } 1175 1176 if err := sMgr.WriteState(s); err != nil { 1177 diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err)) 1178 return nil, diags 1179 } 1180 if err := sMgr.PersistState(); err != nil { 1181 diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err)) 1182 return nil, diags 1183 } 1184 1185 if output { 1186 // By now the backend is successfully configured. If using Terraform Cloud, the success 1187 // message is handled as part of the final init message 1188 if _, ok := b.(*cloud.Cloud); !ok { 1189 m.Ui.Output(m.Colorize().Color(fmt.Sprintf( 1190 "[reset][green]\n"+strings.TrimSpace(successBackendSet), s.Backend.Type))) 1191 } 1192 } 1193 1194 return b, diags 1195 } 1196 1197 // Initializing a saved backend from the cache file (legacy state file) 1198 // 1199 // TODO: This is extremely similar to Meta.backendFromState() but for legacy reasons this is the 1200 // function used by the migration APIs within this file. The other handles 'init -backend=false', 1201 // specifically. 1202 func (m *Meta) savedBackend(sMgr *clistate.LocalState) (backend.Backend, tfdiags.Diagnostics) { 1203 var diags tfdiags.Diagnostics 1204 1205 s := sMgr.State() 1206 1207 // Get the backend 1208 f := backendInit.Backend(s.Backend.Type) 1209 if f == nil { 1210 diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), s.Backend.Type)) 1211 return nil, diags 1212 } 1213 b := f() 1214 1215 // The configuration saved in the working directory state file is used 1216 // in this case, since it will contain any additional values that 1217 // were provided via -backend-config arguments on terraform init. 1218 schema := b.ConfigSchema() 1219 configVal, err := s.Backend.Config(schema) 1220 if err != nil { 1221 diags = diags.Append(tfdiags.Sourceless( 1222 tfdiags.Error, 1223 "Failed to decode current backend config", 1224 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), 1225 )) 1226 return nil, diags 1227 } 1228 1229 // Validate the config and then configure the backend 1230 newVal, validDiags := b.PrepareConfig(configVal) 1231 diags = diags.Append(validDiags) 1232 if validDiags.HasErrors() { 1233 return nil, diags 1234 } 1235 1236 configDiags := b.Configure(newVal) 1237 diags = diags.Append(configDiags) 1238 if configDiags.HasErrors() { 1239 return nil, diags 1240 } 1241 1242 return b, diags 1243 } 1244 1245 func (m *Meta) updateSavedBackendHash(cHash int, sMgr *clistate.LocalState) tfdiags.Diagnostics { 1246 var diags tfdiags.Diagnostics 1247 1248 s := sMgr.State() 1249 1250 if s.Backend.Hash != uint64(cHash) { 1251 s.Backend.Hash = uint64(cHash) 1252 if err := sMgr.WriteState(s); err != nil { 1253 diags = diags.Append(err) 1254 } 1255 } 1256 1257 return diags 1258 } 1259 1260 //------------------------------------------------------------------- 1261 // Reusable helper functions for backend management 1262 //------------------------------------------------------------------- 1263 1264 // backendConfigNeedsMigration returns true if migration might be required to 1265 // move from the configured backend to the given cached backend config. 1266 // 1267 // This must be called with the synthetic *configs.Backend that results from 1268 // merging in any command-line options for correct behavior. 1269 // 1270 // If either the given configuration or cached configuration are invalid then 1271 // this function will conservatively assume that migration is required, 1272 // expecting that the migration code will subsequently deal with the same 1273 // errors. 1274 func (m *Meta) backendConfigNeedsMigration(c *configs.Backend, s *legacy.BackendState) bool { 1275 if s == nil || s.Empty() { 1276 log.Print("[TRACE] backendConfigNeedsMigration: no cached config, so migration is required") 1277 return true 1278 } 1279 if c.Type != s.Type { 1280 log.Printf("[TRACE] backendConfigNeedsMigration: type changed from %q to %q, so migration is required", s.Type, c.Type) 1281 return true 1282 } 1283 1284 // We need the backend's schema to do our comparison here. 1285 f := backendInit.Backend(c.Type) 1286 if f == nil { 1287 log.Printf("[TRACE] backendConfigNeedsMigration: no backend of type %q, which migration codepath must handle", c.Type) 1288 return true // let the migration codepath deal with the missing backend 1289 } 1290 b := f() 1291 1292 schema := b.ConfigSchema() 1293 decSpec := schema.NoneRequired().DecoderSpec() 1294 givenVal, diags := hcldec.Decode(c.Config, decSpec, nil) 1295 if diags.HasErrors() { 1296 log.Printf("[TRACE] backendConfigNeedsMigration: failed to decode given config; migration codepath must handle problem: %s", diags.Error()) 1297 return true // let the migration codepath deal with these errors 1298 } 1299 1300 cachedVal, err := s.Config(schema) 1301 if err != nil { 1302 log.Printf("[TRACE] backendConfigNeedsMigration: failed to decode cached config; migration codepath must handle problem: %s", err) 1303 return true // let the migration codepath deal with the error 1304 } 1305 1306 // If we get all the way down here then it's the exact equality of the 1307 // two decoded values that decides our outcome. It's safe to use RawEquals 1308 // here (rather than Equals) because we know that unknown values can 1309 // never appear in backend configurations. 1310 if cachedVal.RawEquals(givenVal) { 1311 log.Print("[TRACE] backendConfigNeedsMigration: given configuration matches cached configuration, so no migration is required") 1312 return false 1313 } 1314 log.Print("[TRACE] backendConfigNeedsMigration: configuration values have changed, so migration is required") 1315 return true 1316 } 1317 1318 func (m *Meta) backendInitFromConfig(c *configs.Backend) (backend.Backend, cty.Value, tfdiags.Diagnostics) { 1319 var diags tfdiags.Diagnostics 1320 1321 // Get the backend 1322 f := backendInit.Backend(c.Type) 1323 if f == nil { 1324 diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendNewUnknown), c.Type)) 1325 return nil, cty.NilVal, diags 1326 } 1327 b := f() 1328 1329 schema := b.ConfigSchema() 1330 decSpec := schema.NoneRequired().DecoderSpec() 1331 configVal, hclDiags := hcldec.Decode(c.Config, decSpec, nil) 1332 diags = diags.Append(hclDiags) 1333 if hclDiags.HasErrors() { 1334 return nil, cty.NilVal, diags 1335 } 1336 1337 // TODO: test 1338 if m.Input() { 1339 var err error 1340 configVal, err = m.inputForSchema(configVal, schema) 1341 if err != nil { 1342 diags = diags.Append(fmt.Errorf("Error asking for input to configure backend %q: %s", c.Type, err)) 1343 } 1344 1345 // We get an unknown here if the if the user aborted input, but we can't 1346 // turn that into a config value, so set it to null and let the provider 1347 // handle it in PrepareConfig. 1348 if !configVal.IsKnown() { 1349 configVal = cty.NullVal(configVal.Type()) 1350 } 1351 } 1352 1353 newVal, validateDiags := b.PrepareConfig(configVal) 1354 diags = diags.Append(validateDiags.InConfigBody(c.Config, "")) 1355 if validateDiags.HasErrors() { 1356 return nil, cty.NilVal, diags 1357 } 1358 1359 configureDiags := b.Configure(newVal) 1360 diags = diags.Append(configureDiags.InConfigBody(c.Config, "")) 1361 1362 return b, configVal, diags 1363 } 1364 1365 // Helper method to ignore remote/cloud backend version conflicts. Only call this 1366 // for commands which cannot accidentally upgrade remote state files. 1367 func (m *Meta) ignoreRemoteVersionConflict(b backend.Backend) { 1368 if back, ok := b.(BackendWithRemoteTerraformVersion); ok { 1369 back.IgnoreVersionConflict() 1370 } 1371 } 1372 1373 // Helper method to check the local Terraform version against the configured 1374 // version in the remote workspace, returning diagnostics if they conflict. 1375 func (m *Meta) remoteVersionCheck(b backend.Backend, workspace string) tfdiags.Diagnostics { 1376 var diags tfdiags.Diagnostics 1377 1378 if back, ok := b.(BackendWithRemoteTerraformVersion); ok { 1379 // Allow user override based on command-line flag 1380 if m.ignoreRemoteVersion { 1381 back.IgnoreVersionConflict() 1382 } 1383 // If the override is set, this check will return a warning instead of 1384 // an error 1385 versionDiags := back.VerifyWorkspaceTerraformVersion(workspace) 1386 diags = diags.Append(versionDiags) 1387 // If there are no errors resulting from this check, we do not need to 1388 // check again 1389 if !diags.HasErrors() { 1390 back.IgnoreVersionConflict() 1391 } 1392 } 1393 1394 return diags 1395 } 1396 1397 // assertSupportedCloudInitOptions returns diagnostics with errors if the 1398 // init-related command line options (implied inside the Meta receiver) 1399 // are incompatible with the given cloud configuration change mode. 1400 func (m *Meta) assertSupportedCloudInitOptions(mode cloud.ConfigChangeMode) tfdiags.Diagnostics { 1401 var diags tfdiags.Diagnostics 1402 if mode.InvolvesCloud() { 1403 log.Printf("[TRACE] Meta.Backend: Terraform Cloud mode initialization type: %s", mode) 1404 if m.reconfigure { 1405 if mode.IsCloudMigration() { 1406 diags = diags.Append(tfdiags.Sourceless( 1407 tfdiags.Error, 1408 "Invalid command-line option", 1409 "The -reconfigure option is unsupported when migrating to Terraform Cloud, because activating Terraform Cloud involves some additional steps.", 1410 )) 1411 } else { 1412 diags = diags.Append(tfdiags.Sourceless( 1413 tfdiags.Error, 1414 "Invalid command-line option", 1415 "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.", 1416 )) 1417 } 1418 } 1419 if m.migrateState { 1420 name := "-migrate-state" 1421 if m.forceInitCopy { 1422 // -force copy implies -migrate-state in "terraform init", 1423 // so m.migrateState is forced to true in this case even if 1424 // the user didn't actually specify it. We'll use the other 1425 // name here to avoid being confusing, then. 1426 name = "-force-copy" 1427 } 1428 if mode.IsCloudMigration() { 1429 diags = diags.Append(tfdiags.Sourceless( 1430 tfdiags.Error, 1431 "Invalid command-line option", 1432 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), 1433 )) 1434 } else { 1435 diags = diags.Append(tfdiags.Sourceless( 1436 tfdiags.Error, 1437 "Invalid command-line option", 1438 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), 1439 )) 1440 } 1441 } 1442 } 1443 return diags 1444 } 1445 1446 //------------------------------------------------------------------- 1447 // Output constants and initialization code 1448 //------------------------------------------------------------------- 1449 1450 const errBackendLocalRead = ` 1451 Error reading local state: %s 1452 1453 Terraform is trying to read your local state to determine if there is 1454 state to migrate to your newly configured backend. Terraform can't continue 1455 without this check because that would risk losing state. Please resolve the 1456 error above and try again. 1457 ` 1458 1459 const errBackendMigrateLocalDelete = ` 1460 Error deleting local state after migration: %s 1461 1462 Your local state is deleted after successfully migrating it to the newly 1463 configured backend. As part of the deletion process, a backup is made at 1464 the standard backup path unless explicitly asked not to. To cleanly operate 1465 with a backend, we must delete the local state file. Please resolve the 1466 issue above and retry the command. 1467 ` 1468 1469 const errBackendNewUnknown = ` 1470 The backend %q could not be found. 1471 1472 This is the backend specified in your Terraform configuration file. 1473 This error could be a simple typo in your configuration, but it can also 1474 be caused by using a Terraform version that doesn't support the specified 1475 backend type. Please check your configuration and your Terraform version. 1476 1477 If you'd like to run Terraform and store state locally, you can fix this 1478 error by removing the backend configuration from your configuration. 1479 ` 1480 1481 const errBackendNoExistingWorkspaces = ` 1482 No existing workspaces. 1483 1484 Use the "terraform workspace" command to create and select a new workspace. 1485 If the backend already contains existing workspaces, you may need to update 1486 the backend configuration. 1487 ` 1488 1489 const errBackendSavedUnknown = ` 1490 The backend %q could not be found. 1491 1492 This is the backend that this Terraform environment is configured to use 1493 both in your configuration and saved locally as your last-used backend. 1494 If it isn't found, it could mean an alternate version of Terraform was 1495 used with this configuration. Please use the proper version of Terraform that 1496 contains support for this backend. 1497 1498 If you'd like to force remove this backend, you must update your configuration 1499 to not use the backend and run "terraform init" (or any other command) again. 1500 ` 1501 1502 const errBackendClearSaved = ` 1503 Error clearing the backend configuration: %s 1504 1505 Terraform removes the saved backend configuration when you're removing a 1506 configured backend. This must be done so future Terraform runs know to not 1507 use the backend configuration. Please look at the error above, resolve it, 1508 and try again. 1509 ` 1510 1511 const errBackendInit = ` 1512 Reason: %s 1513 1514 The "backend" is the interface that Terraform uses to store state, 1515 perform operations, etc. If this message is showing up, it means that the 1516 Terraform configuration you're using is using a custom configuration for 1517 the Terraform backend. 1518 1519 Changes to backend configurations require reinitialization. This allows 1520 Terraform to set up the new configuration, copy existing state, etc. Please run 1521 "terraform init" with either the "-reconfigure" or "-migrate-state" flags to 1522 use the current configuration. 1523 1524 If the change reason above is incorrect, please verify your configuration 1525 hasn't changed and try again. At this point, no changes to your existing 1526 configuration or state have been made. 1527 ` 1528 1529 const errBackendInitCloud = ` 1530 Reason: %s. 1531 1532 Changes to the Terraform Cloud configuration block require reinitialization, to discover any changes to the available workspaces. 1533 1534 To re-initialize, run: 1535 terraform init 1536 1537 Terraform has not yet made changes to your existing configuration or state. 1538 ` 1539 1540 const errBackendWriteSaved = ` 1541 Error saving the backend configuration: %s 1542 1543 Terraform saves the complete backend configuration in a local file for 1544 configuring the backend on future operations. This cannot be disabled. Errors 1545 are usually due to simple file permission errors. Please look at the error 1546 above, resolve it, and try again. 1547 ` 1548 1549 const outputBackendMigrateChange = ` 1550 Terraform detected that the backend type changed from %q to %q. 1551 ` 1552 1553 const outputBackendMigrateLocal = ` 1554 Terraform has detected you're unconfiguring your previously set %q backend. 1555 ` 1556 1557 const outputBackendReconfigure = ` 1558 [reset][bold]Backend configuration changed![reset] 1559 1560 Terraform has detected that the configuration specified for the backend 1561 has changed. Terraform will now check for existing state in the backends. 1562 ` 1563 1564 const inputCloudInitCreateWorkspace = ` 1565 There are no workspaces with the configured tags (%s) 1566 in your Terraform Cloud organization. To finish initializing, Terraform needs at 1567 least one workspace available. 1568 1569 Terraform can create a properly tagged workspace for you now. Please enter a 1570 name to create a new Terraform Cloud workspace. 1571 ` 1572 1573 const successBackendUnset = ` 1574 Successfully unset the backend %q. Terraform will now operate locally. 1575 ` 1576 1577 const successBackendSet = ` 1578 Successfully configured the backend %q! Terraform will automatically 1579 use this backend unless the backend configuration changes. 1580 ` 1581 1582 var migrateOrReconfigDiag = tfdiags.Sourceless( 1583 tfdiags.Error, 1584 "Backend configuration changed", 1585 "A change in the backend configuration has been detected, which may require migrating existing state.\n\n"+ 1586 "If you wish to attempt automatic migration of the state, use \"terraform init -migrate-state\".\n"+ 1587 `If you wish to store the current configuration with no changes to the state, use "terraform init -reconfigure".`)