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