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