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