| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package command | |
| import ( | |
| "fmt" | |
| "github.com/hashicorp/hcl/v2" | |
| "github.com/hashicorp/terraform/internal/tfdiags" | |
| ) | |
| // errWrongWorkspaceForPlan is a custom error used to alert users that the plan file they are applying | |
| // describes a workspace that doesn't match the currently selected workspace. | |
| // | |
| // This needs to render slightly different errors depending on whether we're using: | |
| // > CE Workspaces (remote-state backends, local backends) | |
| // > HCP Terraform Workspaces (cloud backend) | |
| type errWrongWorkspaceForPlan struct { | |
| plannedWorkspace string | |
| currentWorkspace string | |
| isCloud bool | |
| } | |
| func (e *errWrongWorkspaceForPlan) Error() string { | |
| msg := fmt.Sprintf(`The plan file describes changes to the %q workspace, but the %q workspace is currently in use. | |
| Applying this plan with the incorrect workspace selected could result in state being stored in an unexpected location, or a downstream error when Terraform attempts apply a plan using the other workspace's state.`, | |
| e.plannedWorkspace, | |
| e.currentWorkspace, | |
| ) | |
| // For users to understand what's happened and how to correct it we'll give some guidance, | |
| // but that guidance depends on whether a cloud backend is in use or not. | |
| if e.isCloud { | |
| // When using the cloud backend the solution is to focus on the cloud block and running init | |
| msg = msg + fmt.Sprintf(` If you'd like to continue to use the plan file, make sure the cloud block in your configuration contains the workspace name %q. | |
| In future, make sure your cloud block is correct and unchanged since the last time you performed "terraform init" before creating a plan.`, e.plannedWorkspace) | |
| } else { | |
| // When using the backend block the solution is to not select a different workspace | |
| // between plan and apply operations. | |
| msg = msg + fmt.Sprintf(` If you'd like to continue to use the plan file, you must run "terraform workspace select %s" to select the matching workspace. | |
| In future make sure the selected workspace is not changed between creating and applying a plan file. | |
| `, e.plannedWorkspace) | |
| } | |
| return msg | |
| } | |
| // errBackendLocalRead is a custom error used to alert users that state | |
| // files on their local filesystem were not erased successfully after | |
| // migrating that state to a remote-state backend. | |
| type errBackendLocalRead struct { | |
| innerError error | |
| } | |
| func (e *errBackendLocalRead) Error() string { | |
| return fmt.Sprintf(`Error reading local state: %s | |
| Terraform is trying to read your local state to determine if there is | |
| state to migrate to your newly configured backend. Terraform can't continue | |
| without this check because that would risk losing state. Please resolve the | |
| error above and try again.`, e.innerError) | |
| } | |
| // errBackendMigrateLocalDelete is a custom error used to alert users that state | |
| // files on their local filesystem were not erased successfully after migrating | |
| // that state to a remote-state backend. | |
| type errBackendMigrateLocalDelete struct { | |
| innerError error | |
| } | |
| func (e *errBackendMigrateLocalDelete) Error() string { | |
| return fmt.Sprintf(`Error deleting local state after migration: %s | |
| Your local state is deleted after successfully migrating it to the newly | |
| configured backend. As part of the deletion process, a backup is made at | |
| the standard backup path unless explicitly asked not to. To cleanly operate | |
| with a backend, we must delete the local state file. Please resolve the | |
| issue above and retry the command.`, e.innerError) | |
| } | |
| // errBackendSavedUnknown is a custom error used to alert users that their | |
| // configuration describes a backend that's not implemented in Terraform. | |
| type errBackendNewUnknown struct { | |
| backendName string | |
| } | |
| func (e *errBackendNewUnknown) Error() string { | |
| return fmt.Sprintf(`The backend %q could not be found. | |
| This is the backend specified in your Terraform configuration file. | |
| This error could be a simple typo in your configuration, but it can also | |
| be caused by using a Terraform version that doesn't support the specified | |
| backend type. Please check your configuration and your Terraform version. | |
| If you'd like to run Terraform and store state locally, you can fix this | |
| error by removing the backend configuration from your configuration.`, e.backendName) | |
| } | |
| // errBackendSavedUnknown is a custom error used to alert users that their | |
| // plan file describes a backend that's not implemented in Terraform. | |
| type errBackendSavedUnknown struct { | |
| backendName string | |
| } | |
| func (e *errBackendSavedUnknown) Error() string { | |
| return fmt.Sprintf(`The backend %q could not be found. | |
| This is the backend that this Terraform environment is configured to use | |
| both in your configuration and saved locally as your last-used backend. | |
| If it isn't found, it could mean an alternate version of Terraform was | |
| used with this configuration. Please use the proper version of Terraform that | |
| contains support for this backend. | |
| If you'd like to force remove this backend, you must update your configuration | |
| to not use the backend and run "terraform init" (or any other command) again.`, e.backendName) | |
| } | |
| // errBackendClearSaved is a custom error used to alert users that | |
| // Terraform failed to empty the backend state file's contents. | |
| type errBackendClearSaved struct { | |
| innerError error | |
| } | |
| func (e *errBackendClearSaved) Error() string { | |
| return fmt.Sprintf(`Error clearing the backend configuration: %s | |
| Terraform removes the saved backend configuration when you're removing a | |
| configured backend. This must be done so future Terraform runs know to not | |
| use the backend configuration. Please look at the error above, resolve it, | |
| and try again.`, e.innerError) | |
| } | |
| // errBackendInitDiag creates a diagnostic to present to users when | |
| // users attempt to run a non-init command after making a change to their | |
| // backend configuration. | |
| // | |
| // An init reason should be provided as an argument. | |
| func errBackendInitDiag(initReason string) tfdiags.Diagnostic { | |
| msg := fmt.Sprintf(`Reason: %s | |
| The "backend" is the interface that Terraform uses to store state, | |
| perform operations, etc. If this message is showing up, it means that the | |
| Terraform configuration you're using is using a custom configuration for | |
| the Terraform backend. | |
| Changes to backend configurations require reinitialization. This allows | |
| Terraform to set up the new configuration, copy existing state, etc. Please run | |
| "terraform init" with either the "-reconfigure" or "-migrate-state" flags to | |
| use the current configuration. | |
| If the change reason above is incorrect, please verify your configuration | |
| hasn't changed and try again. At this point, no changes to your existing | |
| configuration or state have been made.`, initReason) | |
| return tfdiags.Sourceless( | |
| tfdiags.Error, | |
| "Backend initialization required, please run \"terraform init\"", | |
| msg, | |
| ) | |
| } | |
| // errBackendToStateStoreInitDiag is a variation of errBackendInitDiag specific to when | |
| // migrating from a backend to a state store. Text needs to resemble the old backend-specific diagnostic, | |
| // but the recommended actions are different (using new terraform state migrate command). | |
| func errBackendToStateStoreInitDiag(initReason string) tfdiags.Diagnostic { | |
| msg := fmt.Sprintf(`Reason: %s | |
| The "backend" is the interface that Terraform uses to store state, | |
| perform operations, etc. If this message is showing up, it means that the | |
| Terraform configuration you're using is using a custom configuration for | |
| the Terraform backend. | |
| Changes to backend configurations require reinitialization. This allows | |
| Terraform to set up the new configuration, copy existing state, etc. Please run | |
| "terraform state migrate" to migrate existing state to the new state store, | |
| or run "terraform init -reconfigure" to use the current configuration without | |
| migrating existing state. | |
| If the change reason above is incorrect, please verify your configuration | |
| hasn't changed and try again. At this point, no changes to your existing | |
| configuration or state have been made.`, initReason) | |
| return tfdiags.Sourceless( | |
| tfdiags.Error, | |
| "Backend initialization required, please run \"terraform state migrate\" or \"terraform init -reconfigure\"", | |
| msg, | |
| ) | |
| } | |
| type ssInitReason struct { | |
| MigrationNeeded bool | |
| Reason string | |
| Subject *hcl.Range | |
| } | |
| // errStateStoreInitDiag creates a diagnostic to present to users when | |
| // users attempt to run a non-init command after making a change to their | |
| // state_store configuration. | |
| func errStateStoreInitDiag(ir *ssInitReason) tfdiags.Diagnostics { | |
| if ir == nil { | |
| panic("errStateStoreInitDiag requires a non-nil reason argument") | |
| } | |
| var msg string | |
| msg += fmt.Sprintf("Reason: %s\n\n", ir.Reason) | |
| msg += `A "state store" is an interface that Terraform uses to store state. | |
| Changes to state store configurations require reinitialization, and a | |
| decision whether to migrate any existing state or not. | |
| If you want to migrate existing state using the current configuration, | |
| please run "terraform state migrate". | |
| If you don't want to migrate existing state and just reconfigure how state is | |
| stored using the current configuration, please run "terraform init -reconfigure". | |
| If the change reason above is incorrect, please verify your configuration | |
| hasn't changed and try again. At this point, no changes to your existing | |
| configuration or state have been made.` | |
| var diags tfdiags.Diagnostics | |
| var summary string | |
| if ir.MigrationNeeded { | |
| summary = "State store initialization required, please run \"terraform state migrate\" or \"terraform init -reconfigure\"" | |
| } else { | |
| // When no migration is needed we're just initializing a state store for the first time. | |
| summary = "State store initialization required, please run \"terraform init\"" | |
| } | |
| if ir.Subject != nil { | |
| diags = diags.Append(&hcl.Diagnostic{ | |
| Subject: ir.Subject, | |
| Severity: hcl.DiagError, | |
| Summary: summary, | |
| Detail: msg, | |
| }) | |
| return diags | |
| } | |
| diags = diags.Append(tfdiags.Sourceless( | |
| tfdiags.Error, | |
| summary, | |
| msg, | |
| )) | |
| return diags | |
| } | |
| // errBackendInitCloudDiag creates a diagnostic to present to users when | |
| // an init command encounters config changes in a `cloud` block. | |
| // | |
| // An init reason should be provided as an argument. | |
| func errBackendInitCloudDiag(initReason string) tfdiags.Diagnostic { | |
| msg := fmt.Sprintf(`Reason: %s. | |
| Changes to the HCP Terraform configuration block require reinitialization, to discover any changes to the available workspaces. | |
| To re-initialize, run: | |
| terraform init | |
| Terraform has not yet made changes to your existing configuration or state.`, initReason) | |
| return tfdiags.Sourceless( | |
| tfdiags.Error, | |
| "HCP Terraform or Terraform Enterprise initialization required: please run \"terraform init\"", | |
| msg, | |
| ) | |
| } | |
| // errBackendWriteSavedDiag creates a diagnostic to present to users when | |
| // an init command experiences an error while writing to the backend state file. | |
| func errBackendWriteSavedDiag(innerError error) tfdiags.Diagnostic { | |
| msg := fmt.Sprintf(`Error saving the backend configuration: %s | |
| Terraform saves the complete backend configuration in a local file for | |
| configuring the backend on future operations. This cannot be disabled. Errors | |
| are usually due to simple file permission errors. Please look at the error | |
| above, resolve it, and try again.`, innerError) | |
| return tfdiags.Sourceless( | |
| tfdiags.Error, | |
| "Backend initialization failed", | |
| msg, | |
| ) | |
| } | |
| // errStateStoreWriteSavedDiag creates a diagnostic to present to users when | |
| // an init command experiences an error while writing to the backend state file. | |
| func errStateStoreWriteSavedDiag(innerError error) tfdiags.Diagnostic { | |
| msg := fmt.Sprintf(`Error saving the state store configuration: %s | |
| Terraform saves the complete state store configuration in a local file for | |
| configuring the state store on future operations. This cannot be disabled. Errors | |
| are usually due to simple file permission errors. Please look at the error | |
| above, resolve it, and try again.`, innerError) | |
| return tfdiags.Sourceless( | |
| tfdiags.Error, | |
| "State store initialization failed", | |
| msg, | |
| ) | |
| } | |
| // errBackendNoExistingWorkspaces is returned by calling code when it expects a backend.Backend | |
| // to report one or more workspaces exist. | |
| // | |
| // The returned error may be used as a sentinel error and acted upon or just wrapped in a | |
| // diagnostic and returned. | |
| type errBackendNoExistingWorkspaces struct{} | |
| func (e *errBackendNoExistingWorkspaces) Error() string { | |
| return `No existing workspaces. | |
| Use the "terraform workspace" command to create and select a new workspace. | |
| If the backend already contains existing workspaces, you may need to update | |
| the backend configuration.` | |
| } | |
| // migrateOrReconfigDiag creates a diagnostic to present to users when | |
| // an init command encounters a mismatch in backend state and the current config | |
| // and Terraform needs users to provide additional instructions about how Terraform | |
| // should proceed. | |
| var migrateOrReconfigDiag = tfdiags.Sourceless( | |
| tfdiags.Error, | |
| "Backend configuration changed", | |
| "A change in the backend configuration has been detected, which may require migrating existing state.\n\n"+ | |
| "If you wish to attempt automatic migration of the state, run \"terraform init -migrate-state\".\n"+ | |
| `If you wish to store the current configuration with no changes to the state, run "terraform init -reconfigure".`) |