kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/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 "kubeform.dev/terraform-backend-sdk/backend" 18 remoteBackend "kubeform.dev/terraform-backend-sdk/backend/remote" 19 "kubeform.dev/terraform-backend-sdk/command/arguments" 20 "kubeform.dev/terraform-backend-sdk/command/clistate" 21 "kubeform.dev/terraform-backend-sdk/command/views" 22 "kubeform.dev/terraform-backend-sdk/configs" 23 "kubeform.dev/terraform-backend-sdk/plans" 24 "kubeform.dev/terraform-backend-sdk/states/statemgr" 25 "kubeform.dev/terraform-backend-sdk/terraform" 26 "kubeform.dev/terraform-backend-sdk/tfdiags" 27 "github.com/zclconf/go-cty/cty" 28 ctyjson "github.com/zclconf/go-cty/cty/json" 29 30 backendInit "kubeform.dev/terraform-backend-sdk/backend/init" 31 backendLocal "kubeform.dev/terraform-backend-sdk/backend/local" 32 legacy "kubeform.dev/terraform-backend-sdk/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 Targets: m.targets, 353 UIIn: m.UIInput(), 354 UIOut: m.Ui, 355 Workspace: workspace, 356 StateLocker: stateLocker, 357 } 358 } 359 360 // backendConfig returns the local configuration for the backend 361 func (m *Meta) backendConfig(opts *BackendOpts) (*configs.Backend, int, tfdiags.Diagnostics) { 362 var diags tfdiags.Diagnostics 363 364 if opts.Config == nil { 365 // check if the config was missing, or just not required 366 conf, moreDiags := m.loadBackendConfig(".") 367 diags = diags.Append(moreDiags) 368 if moreDiags.HasErrors() { 369 return nil, 0, diags 370 } 371 372 if conf == nil { 373 log.Println("[TRACE] Meta.Backend: no config given or present on disk, so returning nil config") 374 return nil, 0, nil 375 } 376 377 log.Printf("[TRACE] Meta.Backend: BackendOpts.Config not set, so using settings loaded from %s", conf.DeclRange) 378 opts.Config = conf 379 } 380 381 c := opts.Config 382 383 if c == nil { 384 log.Println("[TRACE] Meta.Backend: no explicit backend config, so returning nil config") 385 return nil, 0, nil 386 } 387 388 bf := backendInit.Backend(c.Type) 389 if bf == nil { 390 diags = diags.Append(&hcl.Diagnostic{ 391 Severity: hcl.DiagError, 392 Summary: "Invalid backend type", 393 Detail: fmt.Sprintf("There is no backend type named %q.", c.Type), 394 Subject: &c.TypeRange, 395 }) 396 return nil, 0, diags 397 } 398 b := bf() 399 400 configSchema := b.ConfigSchema() 401 configBody := c.Config 402 configHash := c.Hash(configSchema) 403 404 // If we have an override configuration body then we must apply it now. 405 if opts.ConfigOverride != nil { 406 log.Println("[TRACE] Meta.Backend: merging -backend-config=... CLI overrides into backend configuration") 407 configBody = configs.MergeBodies(configBody, opts.ConfigOverride) 408 } 409 410 log.Printf("[TRACE] Meta.Backend: built configuration for %q backend with hash value %d", c.Type, configHash) 411 412 // We'll shallow-copy configs.Backend here so that we can replace the 413 // body without affecting others that hold this reference. 414 configCopy := *c 415 configCopy.Config = configBody 416 return &configCopy, configHash, diags 417 } 418 419 // backendFromConfig returns the initialized (not configured) backend 420 // directly from the config/state.. 421 // 422 // This function handles various edge cases around backend config loading. For 423 // example: new config changes, backend type changes, etc. 424 // 425 // As of the 0.12 release it can no longer migrate from legacy remote state 426 // to backends, and will instead instruct users to use 0.11 or earlier as 427 // a stepping-stone to do that migration. 428 // 429 // This function may query the user for input unless input is disabled, in 430 // which case this function will error. 431 func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) { 432 // Get the local backend configuration. 433 c, cHash, diags := m.backendConfig(opts) 434 if diags.HasErrors() { 435 return nil, diags 436 } 437 438 // ------------------------------------------------------------------------ 439 // For historical reasons, current backend configuration for a working 440 // directory is kept in a *state-like* file, using the legacy state 441 // structures in the Terraform package. It is not actually a Terraform 442 // state, and so only the "backend" portion of it is actually used. 443 // 444 // The remainder of this code often confusingly refers to this as a "state", 445 // so it's unfortunately important to remember that this is not actually 446 // what we _usually_ think of as "state", and is instead a local working 447 // directory "backend configuration state" that is never persisted anywhere. 448 // 449 // Since the "real" state has since moved on to be represented by 450 // states.State, we can recognize the special meaning of state that applies 451 // to this function and its callees by their continued use of the 452 // otherwise-obsolete terraform.State. 453 // ------------------------------------------------------------------------ 454 455 // Get the path to where we store a local cache of backend configuration 456 // if we're using a remote backend. This may not yet exist which means 457 // we haven't used a non-local backend before. That is okay. 458 statePath := filepath.Join(m.DataDir(), DefaultStateFilename) 459 sMgr := &clistate.LocalState{Path: statePath} 460 if err := sMgr.RefreshState(); err != nil { 461 diags = diags.Append(fmt.Errorf("Failed to load state: %s", err)) 462 return nil, diags 463 } 464 465 // Load the state, it must be non-nil for the tests below but can be empty 466 s := sMgr.State() 467 if s == nil { 468 log.Printf("[TRACE] Meta.Backend: backend has not previously been initialized in this working directory") 469 s = legacy.NewState() 470 } else if s.Backend != nil { 471 log.Printf("[TRACE] Meta.Backend: working directory was previously initialized for %q backend", s.Backend.Type) 472 } else { 473 log.Printf("[TRACE] Meta.Backend: working directory was previously initialized but has no backend (is using legacy remote state?)") 474 } 475 476 // if we want to force reconfiguration of the backend, we set the backend 477 // state to nil on this copy. This will direct us through the correct 478 // configuration path in the switch statement below. 479 if m.reconfigure { 480 s.Backend = nil 481 } 482 483 // Upon return, we want to set the state we're using in-memory so that 484 // we can access it for commands. 485 m.backendState = nil 486 defer func() { 487 if s := sMgr.State(); s != nil && !s.Backend.Empty() { 488 m.backendState = s.Backend 489 } 490 }() 491 492 if !s.Remote.Empty() { 493 // Legacy remote state is no longer supported. User must first 494 // migrate with Terraform 0.11 or earlier. 495 diags = diags.Append(tfdiags.Sourceless( 496 tfdiags.Error, 497 "Legacy remote state not supported", 498 "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.", 499 )) 500 return nil, diags 501 } 502 503 // This switch statement covers all the different combinations of 504 // configuring new backends, updating previously-configured backends, etc. 505 switch { 506 // No configuration set at all. Pure local state. 507 case c == nil && s.Backend.Empty(): 508 log.Printf("[TRACE] Meta.Backend: using default local state only (no backend configuration, and no existing initialized backend)") 509 return nil, nil 510 511 // We're unsetting a backend (moving from backend => local) 512 case c == nil && !s.Backend.Empty(): 513 log.Printf("[TRACE] Meta.Backend: previously-initialized %q backend is no longer present in config", s.Backend.Type) 514 515 initReason := fmt.Sprintf("Unsetting the previously set backend %q", s.Backend.Type) 516 if !opts.Init { 517 diags = diags.Append(tfdiags.Sourceless( 518 tfdiags.Error, 519 "Backend initialization required, please run \"terraform init\"", 520 fmt.Sprintf(strings.TrimSpace(errBackendInit), initReason), 521 )) 522 return nil, diags 523 } 524 525 if !m.migrateState { 526 diags = diags.Append(migrateOrReconfigDiag) 527 return nil, diags 528 } 529 530 return m.backend_c_r_S(c, cHash, sMgr, true) 531 532 // Configuring a backend for the first time. 533 case c != nil && s.Backend.Empty(): 534 log.Printf("[TRACE] Meta.Backend: moving from default local state only to %q backend", c.Type) 535 if !opts.Init { 536 initReason := fmt.Sprintf("Initial configuration of the requested backend %q", c.Type) 537 diags = diags.Append(tfdiags.Sourceless( 538 tfdiags.Error, 539 "Backend initialization required, please run \"terraform init\"", 540 fmt.Sprintf(strings.TrimSpace(errBackendInit), initReason), 541 )) 542 return nil, diags 543 } 544 545 return m.backend_C_r_s(c, cHash, sMgr) 546 547 // Potentially changing a backend configuration 548 case c != nil && !s.Backend.Empty(): 549 // We are not going to migrate if were not initializing and the hashes 550 // match indicating that the stored config is valid. If we are 551 // initializing, then we also assume the the backend config is OK if 552 // the hashes match, as long as we're not providing any new overrides. 553 if (uint64(cHash) == s.Backend.Hash) && (!opts.Init || opts.ConfigOverride == nil) { 554 log.Printf("[TRACE] Meta.Backend: using already-initialized, unchanged %q backend configuration", c.Type) 555 return m.backend_C_r_S_unchanged(c, cHash, sMgr) 556 } 557 558 // If our configuration is the same, then we're just initializing 559 // a previously configured remote backend. 560 if !m.backendConfigNeedsMigration(c, s.Backend) { 561 log.Printf("[TRACE] Meta.Backend: using already-initialized %q backend configuration", c.Type) 562 return m.backend_C_r_S_unchanged(c, cHash, sMgr) 563 } 564 log.Printf("[TRACE] Meta.Backend: backend configuration has changed (from type %q to type %q)", s.Backend.Type, c.Type) 565 566 initReason := fmt.Sprintf("Backend configuration changed for %q", c.Type) 567 if s.Backend.Type != c.Type { 568 initReason = fmt.Sprintf("Backend configuration changed from %q to %q", s.Backend.Type, c.Type) 569 } 570 571 if !opts.Init { 572 diags = diags.Append(tfdiags.Sourceless( 573 tfdiags.Error, 574 "Backend initialization required, please run \"terraform init\"", 575 fmt.Sprintf(strings.TrimSpace(errBackendInit), initReason), 576 )) 577 return nil, diags 578 } 579 580 if !m.migrateState { 581 diags = diags.Append(migrateOrReconfigDiag) 582 return nil, diags 583 } 584 585 log.Printf("[WARN] backend config has changed since last init") 586 return m.backend_C_r_S_changed(c, cHash, sMgr, true) 587 588 default: 589 diags = diags.Append(fmt.Errorf( 590 "Unhandled backend configuration state. This is a bug. Please\n"+ 591 "report this error with the following information.\n\n"+ 592 "Config Nil: %v\n"+ 593 "Saved Backend Empty: %v\n", 594 c == nil, s.Backend.Empty(), 595 )) 596 return nil, diags 597 } 598 } 599 600 // backendFromState returns the initialized (not configured) backend directly 601 // from the state. This should be used only when a user runs `terraform init 602 // -backend=false`. This function returns a local backend if there is no state 603 // or no backend configured. 604 func (m *Meta) backendFromState() (backend.Backend, tfdiags.Diagnostics) { 605 var diags tfdiags.Diagnostics 606 // Get the path to where we store a local cache of backend configuration 607 // if we're using a remote backend. This may not yet exist which means 608 // we haven't used a non-local backend before. That is okay. 609 statePath := filepath.Join(m.DataDir(), DefaultStateFilename) 610 sMgr := &clistate.LocalState{Path: statePath} 611 if err := sMgr.RefreshState(); err != nil { 612 diags = diags.Append(fmt.Errorf("Failed to load state: %s", err)) 613 return nil, diags 614 } 615 s := sMgr.State() 616 if s == nil { 617 // no state, so return a local backend 618 log.Printf("[TRACE] Meta.Backend: backend has not previously been initialized in this working directory") 619 return backendLocal.New(), diags 620 } 621 if s.Backend == nil { 622 // s.Backend is nil, so return a local backend 623 log.Printf("[TRACE] Meta.Backend: working directory was previously initialized but has no backend (is using legacy remote state?)") 624 return backendLocal.New(), diags 625 } 626 log.Printf("[TRACE] Meta.Backend: working directory was previously initialized for %q backend", s.Backend.Type) 627 628 //backend init function 629 if s.Backend.Type == "" { 630 return backendLocal.New(), diags 631 } 632 f := backendInit.Backend(s.Backend.Type) 633 if f == nil { 634 diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), s.Backend.Type)) 635 return nil, diags 636 } 637 b := f() 638 639 // The configuration saved in the working directory state file is used 640 // in this case, since it will contain any additional values that 641 // were provided via -backend-config arguments on terraform init. 642 schema := b.ConfigSchema() 643 configVal, err := s.Backend.Config(schema) 644 if err != nil { 645 diags = diags.Append(tfdiags.Sourceless( 646 tfdiags.Error, 647 "Failed to decode current backend config", 648 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), 649 )) 650 return nil, diags 651 } 652 653 // Validate the config and then configure the backend 654 newVal, validDiags := b.PrepareConfig(configVal) 655 diags = diags.Append(validDiags) 656 if validDiags.HasErrors() { 657 return nil, diags 658 } 659 660 configDiags := b.Configure(newVal) 661 diags = diags.Append(configDiags) 662 if configDiags.HasErrors() { 663 return nil, diags 664 } 665 666 return b, diags 667 } 668 669 //------------------------------------------------------------------- 670 // Backend Config Scenarios 671 // 672 // The functions below cover handling all the various scenarios that 673 // can exist when loading a backend. They are named in the format of 674 // "backend_C_R_S" where C, R, S may be upper or lowercase. Lowercase 675 // means it is false, uppercase means it is true. The full set of eight 676 // possible cases is handled. 677 // 678 // The fields are: 679 // 680 // * C - Backend configuration is set and changed in TF files 681 // * R - Legacy remote state is set 682 // * S - Backend configuration is set in the state 683 // 684 //------------------------------------------------------------------- 685 686 // Unconfiguring a backend (moving from backend => local). 687 func (m *Meta) backend_c_r_S(c *configs.Backend, cHash int, sMgr *clistate.LocalState, output bool) (backend.Backend, tfdiags.Diagnostics) { 688 s := sMgr.State() 689 690 // Get the backend type for output 691 backendType := s.Backend.Type 692 693 m.Ui.Output(fmt.Sprintf(strings.TrimSpace(outputBackendMigrateLocal), s.Backend.Type)) 694 695 // Grab a purely local backend to get the local state if it exists 696 localB, diags := m.Backend(&BackendOpts{ForceLocal: true}) 697 if diags.HasErrors() { 698 return nil, diags 699 } 700 701 // Initialize the configured backend 702 b, moreDiags := m.backend_C_r_S_unchanged(c, cHash, sMgr) 703 diags = diags.Append(moreDiags) 704 if moreDiags.HasErrors() { 705 return nil, diags 706 } 707 708 // Perform the migration 709 err := m.backendMigrateState(&backendMigrateOpts{ 710 OneType: s.Backend.Type, 711 TwoType: "local", 712 One: b, 713 Two: localB, 714 }) 715 if err != nil { 716 diags = diags.Append(err) 717 return nil, diags 718 } 719 720 // Remove the stored metadata 721 s.Backend = nil 722 if err := sMgr.WriteState(s); err != nil { 723 diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendClearSaved), err)) 724 return nil, diags 725 } 726 if err := sMgr.PersistState(); err != nil { 727 diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendClearSaved), err)) 728 return nil, diags 729 } 730 731 if output { 732 m.Ui.Output(m.Colorize().Color(fmt.Sprintf( 733 "[reset][green]\n\n"+ 734 strings.TrimSpace(successBackendUnset), backendType))) 735 } 736 737 // Return no backend 738 return nil, diags 739 } 740 741 // Configuring a backend for the first time. 742 func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.LocalState) (backend.Backend, tfdiags.Diagnostics) { 743 // Get the backend 744 b, configVal, diags := m.backendInitFromConfig(c) 745 if diags.HasErrors() { 746 return nil, diags 747 } 748 749 // Grab a purely local backend to get the local state if it exists 750 localB, localBDiags := m.Backend(&BackendOpts{ForceLocal: true}) 751 if localBDiags.HasErrors() { 752 diags = diags.Append(localBDiags) 753 return nil, diags 754 } 755 756 workspaces, err := localB.Workspaces() 757 if err != nil { 758 diags = diags.Append(fmt.Errorf(errBackendLocalRead, err)) 759 return nil, diags 760 } 761 762 var localStates []statemgr.Full 763 for _, workspace := range workspaces { 764 localState, err := localB.StateMgr(workspace) 765 if err != nil { 766 diags = diags.Append(fmt.Errorf(errBackendLocalRead, err)) 767 return nil, diags 768 } 769 if err := localState.RefreshState(); err != nil { 770 diags = diags.Append(fmt.Errorf(errBackendLocalRead, err)) 771 return nil, diags 772 } 773 774 // We only care about non-empty states. 775 if localS := localState.State(); !localS.Empty() { 776 log.Printf("[TRACE] Meta.Backend: will need to migrate workspace states because of existing %q workspace", workspace) 777 localStates = append(localStates, localState) 778 } else { 779 log.Printf("[TRACE] Meta.Backend: ignoring local %q workspace because its state is empty", workspace) 780 } 781 } 782 783 if len(localStates) > 0 { 784 // Perform the migration 785 err = m.backendMigrateState(&backendMigrateOpts{ 786 OneType: "local", 787 TwoType: c.Type, 788 One: localB, 789 Two: b, 790 }) 791 if err != nil { 792 diags = diags.Append(err) 793 return nil, diags 794 } 795 796 // we usually remove the local state after migration to prevent 797 // confusion, but adding a default local backend block to the config 798 // can get us here too. Don't delete our state if the old and new paths 799 // are the same. 800 erase := true 801 if newLocalB, ok := b.(*backendLocal.Local); ok { 802 if localB, ok := localB.(*backendLocal.Local); ok { 803 if newLocalB.PathsConflictWith(localB) { 804 erase = false 805 log.Printf("[TRACE] Meta.Backend: both old and new backends share the same local state paths, so not erasing old state") 806 } 807 } 808 } 809 810 if erase { 811 log.Printf("[TRACE] Meta.Backend: removing old state snapshots from old backend") 812 for _, localState := range localStates { 813 // We always delete the local state, unless that was our new state too. 814 if err := localState.WriteState(nil); err != nil { 815 diags = diags.Append(fmt.Errorf(errBackendMigrateLocalDelete, err)) 816 return nil, diags 817 } 818 if err := localState.PersistState(); err != nil { 819 diags = diags.Append(fmt.Errorf(errBackendMigrateLocalDelete, err)) 820 return nil, diags 821 } 822 } 823 } 824 } 825 826 if m.stateLock { 827 view := views.NewStateLocker(arguments.ViewHuman, m.View) 828 stateLocker := clistate.NewLocker(m.stateLockTimeout, view) 829 if err := stateLocker.Lock(sMgr, "backend from plan"); err != nil { 830 diags = diags.Append(fmt.Errorf("Error locking state: %s", err)) 831 return nil, diags 832 } 833 defer stateLocker.Unlock() 834 } 835 836 configJSON, err := ctyjson.Marshal(configVal, b.ConfigSchema().ImpliedType()) 837 if err != nil { 838 diags = diags.Append(fmt.Errorf("Can't serialize backend configuration as JSON: %s", err)) 839 return nil, diags 840 } 841 842 // Store the metadata in our saved state location 843 s := sMgr.State() 844 if s == nil { 845 s = legacy.NewState() 846 } 847 s.Backend = &legacy.BackendState{ 848 Type: c.Type, 849 ConfigRaw: json.RawMessage(configJSON), 850 Hash: uint64(cHash), 851 } 852 853 if err := sMgr.WriteState(s); err != nil { 854 diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err)) 855 return nil, diags 856 } 857 if err := sMgr.PersistState(); err != nil { 858 diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err)) 859 return nil, diags 860 } 861 862 // By now the backend is successfully configured. 863 m.Ui.Output(m.Colorize().Color(fmt.Sprintf( 864 "[reset][green]\n"+strings.TrimSpace(successBackendSet), s.Backend.Type))) 865 866 return b, diags 867 } 868 869 // Changing a previously saved backend. 870 func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clistate.LocalState, output bool) (backend.Backend, tfdiags.Diagnostics) { 871 if output { 872 // Notify the user 873 m.Ui.Output(m.Colorize().Color(fmt.Sprintf( 874 "[reset]%s\n\n", 875 strings.TrimSpace(outputBackendReconfigure)))) 876 } 877 878 // Get the old state 879 s := sMgr.State() 880 881 // Get the backend 882 b, configVal, diags := m.backendInitFromConfig(c) 883 if diags.HasErrors() { 884 return nil, diags 885 } 886 887 // no need to confuse the user if the backend types are the same 888 if s.Backend.Type != c.Type { 889 m.Ui.Output(strings.TrimSpace(fmt.Sprintf(outputBackendMigrateChange, s.Backend.Type, c.Type))) 890 } 891 892 // Grab the existing backend 893 oldB, oldBDiags := m.backend_C_r_S_unchanged(c, cHash, sMgr) 894 diags = diags.Append(oldBDiags) 895 if oldBDiags.HasErrors() { 896 return nil, diags 897 } 898 899 // Perform the migration 900 err := m.backendMigrateState(&backendMigrateOpts{ 901 OneType: s.Backend.Type, 902 TwoType: c.Type, 903 One: oldB, 904 Two: b, 905 }) 906 if err != nil { 907 diags = diags.Append(err) 908 return nil, diags 909 } 910 911 if m.stateLock { 912 view := views.NewStateLocker(arguments.ViewHuman, m.View) 913 stateLocker := clistate.NewLocker(m.stateLockTimeout, view) 914 if err := stateLocker.Lock(sMgr, "backend from plan"); err != nil { 915 diags = diags.Append(fmt.Errorf("Error locking state: %s", err)) 916 return nil, diags 917 } 918 defer stateLocker.Unlock() 919 } 920 921 configJSON, err := ctyjson.Marshal(configVal, b.ConfigSchema().ImpliedType()) 922 if err != nil { 923 diags = diags.Append(fmt.Errorf("Can't serialize backend configuration as JSON: %s", err)) 924 return nil, diags 925 } 926 927 // Update the backend state 928 s = sMgr.State() 929 if s == nil { 930 s = legacy.NewState() 931 } 932 s.Backend = &legacy.BackendState{ 933 Type: c.Type, 934 ConfigRaw: json.RawMessage(configJSON), 935 Hash: uint64(cHash), 936 } 937 938 if err := sMgr.WriteState(s); err != nil { 939 diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err)) 940 return nil, diags 941 } 942 if err := sMgr.PersistState(); err != nil { 943 diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err)) 944 return nil, diags 945 } 946 947 if output { 948 m.Ui.Output(m.Colorize().Color(fmt.Sprintf( 949 "[reset][green]\n"+strings.TrimSpace(successBackendSet), s.Backend.Type))) 950 } 951 952 return b, diags 953 } 954 955 // Initiailizing an unchanged saved backend 956 func (m *Meta) backend_C_r_S_unchanged(c *configs.Backend, cHash int, sMgr *clistate.LocalState) (backend.Backend, tfdiags.Diagnostics) { 957 var diags tfdiags.Diagnostics 958 959 s := sMgr.State() 960 961 // it's possible for a backend to be unchanged, and the config itself to 962 // have changed by moving a parameter from the config to `-backend-config` 963 // In this case we only need to update the Hash. 964 if c != nil && s.Backend.Hash != uint64(cHash) { 965 s.Backend.Hash = uint64(cHash) 966 if err := sMgr.WriteState(s); err != nil { 967 diags = diags.Append(err) 968 return nil, diags 969 } 970 } 971 972 // Get the backend 973 f := backendInit.Backend(s.Backend.Type) 974 if f == nil { 975 diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), s.Backend.Type)) 976 return nil, diags 977 } 978 b := f() 979 980 // The configuration saved in the working directory state file is used 981 // in this case, since it will contain any additional values that 982 // were provided via -backend-config arguments on terraform init. 983 schema := b.ConfigSchema() 984 configVal, err := s.Backend.Config(schema) 985 if err != nil { 986 diags = diags.Append(tfdiags.Sourceless( 987 tfdiags.Error, 988 "Failed to decode current backend config", 989 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), 990 )) 991 return nil, diags 992 } 993 994 // Validate the config and then configure the backend 995 newVal, validDiags := b.PrepareConfig(configVal) 996 diags = diags.Append(validDiags) 997 if validDiags.HasErrors() { 998 return nil, diags 999 } 1000 1001 configDiags := b.Configure(newVal) 1002 diags = diags.Append(configDiags) 1003 if configDiags.HasErrors() { 1004 return nil, diags 1005 } 1006 1007 return b, diags 1008 } 1009 1010 //------------------------------------------------------------------- 1011 // Reusable helper functions for backend management 1012 //------------------------------------------------------------------- 1013 1014 // backendConfigNeedsMigration returns true if migration might be required to 1015 // move from the configured backend to the given cached backend config. 1016 // 1017 // This must be called with the synthetic *configs.Backend that results from 1018 // merging in any command-line options for correct behavior. 1019 // 1020 // If either the given configuration or cached configuration are invalid then 1021 // this function will conservatively assume that migration is required, 1022 // expecting that the migration code will subsequently deal with the same 1023 // errors. 1024 func (m *Meta) backendConfigNeedsMigration(c *configs.Backend, s *legacy.BackendState) bool { 1025 if s == nil || s.Empty() { 1026 log.Print("[TRACE] backendConfigNeedsMigration: no cached config, so migration is required") 1027 return true 1028 } 1029 if c.Type != s.Type { 1030 log.Printf("[TRACE] backendConfigNeedsMigration: type changed from %q to %q, so migration is required", s.Type, c.Type) 1031 return true 1032 } 1033 1034 // We need the backend's schema to do our comparison here. 1035 f := backendInit.Backend(c.Type) 1036 if f == nil { 1037 log.Printf("[TRACE] backendConfigNeedsMigration: no backend of type %q, which migration codepath must handle", c.Type) 1038 return true // let the migration codepath deal with the missing backend 1039 } 1040 b := f() 1041 1042 schema := b.ConfigSchema() 1043 decSpec := schema.NoneRequired().DecoderSpec() 1044 givenVal, diags := hcldec.Decode(c.Config, decSpec, nil) 1045 if diags.HasErrors() { 1046 log.Printf("[TRACE] backendConfigNeedsMigration: failed to decode given config; migration codepath must handle problem: %s", diags.Error()) 1047 return true // let the migration codepath deal with these errors 1048 } 1049 1050 cachedVal, err := s.Config(schema) 1051 if err != nil { 1052 log.Printf("[TRACE] backendConfigNeedsMigration: failed to decode cached config; migration codepath must handle problem: %s", err) 1053 return true // let the migration codepath deal with the error 1054 } 1055 1056 // If we get all the way down here then it's the exact equality of the 1057 // two decoded values that decides our outcome. It's safe to use RawEquals 1058 // here (rather than Equals) because we know that unknown values can 1059 // never appear in backend configurations. 1060 if cachedVal.RawEquals(givenVal) { 1061 log.Print("[TRACE] backendConfigNeedsMigration: given configuration matches cached configuration, so no migration is required") 1062 return false 1063 } 1064 log.Print("[TRACE] backendConfigNeedsMigration: configuration values have changed, so migration is required") 1065 return true 1066 } 1067 1068 func (m *Meta) backendInitFromConfig(c *configs.Backend) (backend.Backend, cty.Value, tfdiags.Diagnostics) { 1069 var diags tfdiags.Diagnostics 1070 1071 // Get the backend 1072 f := backendInit.Backend(c.Type) 1073 if f == nil { 1074 diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendNewUnknown), c.Type)) 1075 return nil, cty.NilVal, diags 1076 } 1077 b := f() 1078 1079 schema := b.ConfigSchema() 1080 decSpec := schema.NoneRequired().DecoderSpec() 1081 configVal, hclDiags := hcldec.Decode(c.Config, decSpec, nil) 1082 diags = diags.Append(hclDiags) 1083 if hclDiags.HasErrors() { 1084 return nil, cty.NilVal, diags 1085 } 1086 1087 // TODO: test 1088 if m.Input() { 1089 var err error 1090 configVal, err = m.inputForSchema(configVal, schema) 1091 if err != nil { 1092 diags = diags.Append(fmt.Errorf("Error asking for input to configure backend %q: %s", c.Type, err)) 1093 } 1094 1095 // We get an unknown here if the if the user aborted input, but we can't 1096 // turn that into a config value, so set it to null and let the provider 1097 // handle it in PrepareConfig. 1098 if !configVal.IsKnown() { 1099 configVal = cty.NullVal(configVal.Type()) 1100 } 1101 } 1102 1103 newVal, validateDiags := b.PrepareConfig(configVal) 1104 diags = diags.Append(validateDiags.InConfigBody(c.Config, "")) 1105 if validateDiags.HasErrors() { 1106 return nil, cty.NilVal, diags 1107 } 1108 1109 configureDiags := b.Configure(newVal) 1110 diags = diags.Append(configureDiags.InConfigBody(c.Config, "")) 1111 1112 return b, configVal, diags 1113 } 1114 1115 // Helper method to ignore remote backend version conflicts. Only call this 1116 // for commands which cannot accidentally upgrade remote state files. 1117 func (m *Meta) ignoreRemoteBackendVersionConflict(b backend.Backend) { 1118 if rb, ok := b.(*remoteBackend.Remote); ok { 1119 rb.IgnoreVersionConflict() 1120 } 1121 } 1122 1123 // Helper method to check the local Terraform version against the configured 1124 // version in the remote workspace, returning diagnostics if they conflict. 1125 func (m *Meta) remoteBackendVersionCheck(b backend.Backend, workspace string) tfdiags.Diagnostics { 1126 var diags tfdiags.Diagnostics 1127 1128 if rb, ok := b.(*remoteBackend.Remote); ok { 1129 // Allow user override based on command-line flag 1130 if m.ignoreRemoteVersion { 1131 rb.IgnoreVersionConflict() 1132 } 1133 // If the override is set, this check will return a warning instead of 1134 // an error 1135 versionDiags := rb.VerifyWorkspaceTerraformVersion(workspace) 1136 diags = diags.Append(versionDiags) 1137 // If there are no errors resulting from this check, we do not need to 1138 // check again 1139 if !diags.HasErrors() { 1140 rb.IgnoreVersionConflict() 1141 } 1142 } 1143 1144 return diags 1145 } 1146 1147 //------------------------------------------------------------------- 1148 // Output constants and initialization code 1149 //------------------------------------------------------------------- 1150 1151 const errBackendLocalRead = ` 1152 Error reading local state: %s 1153 1154 Terraform is trying to read your local state to determine if there is 1155 state to migrate to your newly configured backend. Terraform can't continue 1156 without this check because that would risk losing state. Please resolve the 1157 error above and try again. 1158 ` 1159 1160 const errBackendMigrateLocalDelete = ` 1161 Error deleting local state after migration: %s 1162 1163 Your local state is deleted after successfully migrating it to the newly 1164 configured backend. As part of the deletion process, a backup is made at 1165 the standard backup path unless explicitly asked not to. To cleanly operate 1166 with a backend, we must delete the local state file. Please resolve the 1167 issue above and retry the command. 1168 ` 1169 1170 const errBackendNewUnknown = ` 1171 The backend %q could not be found. 1172 1173 This is the backend specified in your Terraform configuration file. 1174 This error could be a simple typo in your configuration, but it can also 1175 be caused by using a Terraform version that doesn't support the specified 1176 backend type. Please check your configuration and your Terraform version. 1177 1178 If you'd like to run Terraform and store state locally, you can fix this 1179 error by removing the backend configuration from your configuration. 1180 ` 1181 1182 const errBackendNoExistingWorkspaces = ` 1183 No existing workspaces. 1184 1185 Use the "terraform workspace" command to create and select a new workspace. 1186 If the backend already contains existing workspaces, you may need to update 1187 the backend configuration. 1188 ` 1189 1190 const errBackendSavedUnknown = ` 1191 The backend %q could not be found. 1192 1193 This is the backend that this Terraform environment is configured to use 1194 both in your configuration and saved locally as your last-used backend. 1195 If it isn't found, it could mean an alternate version of Terraform was 1196 used with this configuration. Please use the proper version of Terraform that 1197 contains support for this backend. 1198 1199 If you'd like to force remove this backend, you must update your configuration 1200 to not use the backend and run "terraform init" (or any other command) again. 1201 ` 1202 1203 const errBackendClearSaved = ` 1204 Error clearing the backend configuration: %s 1205 1206 Terraform removes the saved backend configuration when you're removing a 1207 configured backend. This must be done so future Terraform runs know to not 1208 use the backend configuration. Please look at the error above, resolve it, 1209 and try again. 1210 ` 1211 1212 const errBackendInit = ` 1213 Reason: %s 1214 1215 The "backend" is the interface that Terraform uses to store state, 1216 perform operations, etc. If this message is showing up, it means that the 1217 Terraform configuration you're using is using a custom configuration for 1218 the Terraform backend. 1219 1220 Changes to backend configurations require reinitialization. This allows 1221 Terraform to set up the new configuration, copy existing state, etc. Please run 1222 "terraform init" with either the "-reconfigure" or "-migrate-state" flags to 1223 use the current configuration. 1224 1225 If the change reason above is incorrect, please verify your configuration 1226 hasn't changed and try again. At this point, no changes to your existing 1227 configuration or state have been made. 1228 ` 1229 1230 const errBackendWriteSaved = ` 1231 Error saving the backend configuration: %s 1232 1233 Terraform saves the complete backend configuration in a local file for 1234 configuring the backend on future operations. This cannot be disabled. Errors 1235 are usually due to simple file permission errors. Please look at the error 1236 above, resolve it, and try again. 1237 ` 1238 1239 const outputBackendMigrateChange = ` 1240 Terraform detected that the backend type changed from %q to %q. 1241 ` 1242 1243 const outputBackendMigrateLocal = ` 1244 Terraform has detected you're unconfiguring your previously set %q backend. 1245 ` 1246 1247 const outputBackendReconfigure = ` 1248 [reset][bold]Backend configuration changed![reset] 1249 1250 Terraform has detected that the configuration specified for the backend 1251 has changed. Terraform will now check for existing state in the backends. 1252 ` 1253 1254 const successBackendUnset = ` 1255 Successfully unset the backend %q. Terraform will now operate locally. 1256 ` 1257 1258 const successBackendSet = ` 1259 Successfully configured the backend %q! Terraform will automatically 1260 use this backend unless the backend configuration changes. 1261 ` 1262 1263 var migrateOrReconfigDiag = tfdiags.Sourceless( 1264 tfdiags.Error, 1265 "Backend configuration changed", 1266 "A change in the backend configuration has been detected, which may require migrating existing state.\n\n"+ 1267 "If you wish to attempt automatic migration of the state, use \"terraform init -migrate-state\".\n"+ 1268 `If you wish to store the current configuration with no changes to the state, use "terraform init -reconfigure".`)