diff --git a/.changes/v1.15/BUG FIXES-20251119-103000.yaml b/.changes/v1.15/BUG FIXES-20251119-103000.yaml new file mode 100644 index 000000000000..28eba4864164 --- /dev/null +++ b/.changes/v1.15/BUG FIXES-20251119-103000.yaml @@ -0,0 +1,5 @@ +kind: BUG FIXES +body: 'cli: Fixed `terraform init -json` to properly format all backend configuration messages as JSON instead of plain text' +time: 2025-11-19T10:30:00.000000Z +custom: + Issue: "37911" diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index d8282bac9823..cef0dc2908f8 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -1132,10 +1132,11 @@ func (m *Meta) backend_c_r_S( // Get the backend type for output backendType := s.Backend.Type + view := views.NewInit(vt, m.View) if cloudMode == cloud.ConfigMigrationOut { - m.Ui.Output("Migrating from HCP Terraform or Terraform Enterprise to local state.") + view.Output(views.BackendCloudMigrateLocalMessage) } else { - m.Ui.Output(fmt.Sprintf(strings.TrimSpace(outputBackendMigrateLocal), s.Backend.Type)) + view.Output(views.BackendMigrateLocalMessage, s.Backend.Type) } // Grab a purely local backend to get the local state if it exists @@ -1177,9 +1178,7 @@ func (m *Meta) backend_c_r_S( } if output { - m.Ui.Output(m.Colorize().Color(fmt.Sprintf( - "[reset][green]\n\n"+ - strings.TrimSpace(successBackendUnset), backendType))) + view.Output(views.BackendConfiguredUnsetMessage, backendType) } // Return no backend @@ -1348,8 +1347,8 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.Local // By now the backend is successfully configured. If using HCP Terraform, the success // message is handled as part of the final init message if _, ok := b.(*cloud.Cloud); !ok { - m.Ui.Output(m.Colorize().Color(fmt.Sprintf( - "[reset][green]\n"+strings.TrimSpace(successBackendSet), s.Backend.Type))) + view := views.NewInit(vt, m.View) + view.Output(views.BackendConfiguredSuccessMessage, s.Backend.Type) } return b, diags @@ -1377,23 +1376,19 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista if output { // Notify the user + view := views.NewInit(vt, m.View) switch cloudMode { case cloud.ConfigChangeInPlace: - m.Ui.Output("HCP Terraform configuration has changed.") + view.Output(views.BackendCloudChangeInPlaceMessage) case cloud.ConfigMigrationIn: - m.Ui.Output(fmt.Sprintf("Migrating from backend %q to HCP Terraform.", s.Backend.Type)) + view.Output(views.BackendMigrateToCloudMessage, s.Backend.Type) case cloud.ConfigMigrationOut: - m.Ui.Output(fmt.Sprintf("Migrating from HCP Terraform to backend %q.", c.Type)) + view.Output(views.BackendMigrateFromCloudMessage, c.Type) default: if s.Backend.Type != c.Type { - output := fmt.Sprintf(outputBackendMigrateChange, s.Backend.Type, c.Type) - m.Ui.Output(m.Colorize().Color(fmt.Sprintf( - "[reset]%s\n", - strings.TrimSpace(output)))) + view.Output(views.BackendMigrateTypeChangeMessage, s.Backend.Type, c.Type) } else { - m.Ui.Output(m.Colorize().Color(fmt.Sprintf( - "[reset]%s\n", - strings.TrimSpace(outputBackendReconfigure)))) + view.Output(views.BackendReconfigureMessage) } } } @@ -1479,8 +1474,8 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista // By now the backend is successfully configured. If using HCP Terraform, the success // message is handled as part of the final init message if _, ok := b.(*cloud.Cloud); !ok { - m.Ui.Output(m.Colorize().Color(fmt.Sprintf( - "[reset][green]\n"+strings.TrimSpace(successBackendSet), s.Backend.Type))) + view := views.NewInit(vt, m.View) + view.Output(views.BackendConfiguredSuccessMessage, s.Backend.Type) } } @@ -1866,7 +1861,8 @@ func (m *Meta) stateStore_c_S(ssSMgr *clistate.LocalState, viewType arguments.Vi s := ssSMgr.State() stateStoreType := s.StateStore.Type - m.Ui.Output(fmt.Sprintf(strings.TrimSpace(outputStateStoreMigrateLocal), stateStoreType)) + view := views.NewInit(viewType, m.View) + view.Output(views.StateMigrateLocalMessage, stateStoreType) // Grab a purely local backend to get the local state if it exists localB, moreDiags := m.Backend(&BackendOpts{ForceLocal: true, Init: true}) @@ -2625,25 +2621,6 @@ func (m *Meta) StateStoreProviderFactoryFromConfigState(cfgState *workdir.StateS // Output constants and initialization code //------------------------------------------------------------------- -const outputBackendMigrateChange = ` -Terraform detected that the backend type changed from %q to %q. -` - -const outputBackendMigrateLocal = ` -Terraform has detected you're unconfiguring your previously set %q backend. -` - -const outputStateStoreMigrateLocal = ` -Terraform has detected you're unconfiguring your previously set %q state store. -` - -const outputBackendReconfigure = ` -[reset][bold]Backend configuration changed![reset] - -Terraform has detected that the configuration specified for the backend -has changed. Terraform will now check for existing state in the backends. -` - const inputCloudInitCreateWorkspace = ` There are no workspaces with the configured tags (%s) in your HCP Terraform organization. To finish initializing, Terraform needs at @@ -2652,12 +2629,3 @@ least one workspace available. Terraform can create a properly tagged workspace for you now. Please enter a name to create a new HCP Terraform workspace. ` - -const successBackendUnset = ` -Successfully unset the backend %q. Terraform will now operate locally. -` - -const successBackendSet = ` -Successfully configured the backend %q! Terraform will automatically -use this backend unless the backend configuration changes. -` diff --git a/internal/command/views/init.go b/internal/command/views/init.go index 4a633ce69b8b..2669572d4700 100644 --- a/internal/command/views/init.go +++ b/internal/command/views/init.go @@ -262,6 +262,46 @@ var MessageRegistry map[InitMessageCode]InitMessage = map[InitMessageCode]InitMe HumanValue: "[reset][green]\n\nSuccessfully unset the state store %q. Terraform will now operate locally.", JSONValue: "Successfully unset the state store %q. Terraform will now operate locally.", }, + "backend_configured_success": { + HumanValue: backendConfiguredSuccessHuman, + JSONValue: backendConfiguredSuccessJSON, + }, + "backend_configured_unset": { + HumanValue: backendConfiguredUnsetHuman, + JSONValue: backendConfiguredUnsetJSON, + }, + "backend_migrate_to_cloud": { + HumanValue: "Migrating from backend %q to HCP Terraform.", + JSONValue: "Migrating from backend %q to HCP Terraform.", + }, + "backend_migrate_from_cloud": { + HumanValue: "Migrating from HCP Terraform to backend %q.", + JSONValue: "Migrating from HCP Terraform to backend %q.", + }, + "backend_cloud_change_in_place": { + HumanValue: "HCP Terraform configuration has changed.", + JSONValue: "HCP Terraform configuration has changed.", + }, + "backend_migrate_type_change": { + HumanValue: backendMigrateTypeChangeHuman, + JSONValue: backendMigrateTypeChangeJSON, + }, + "backend_reconfigure": { + HumanValue: backendReconfigureHuman, + JSONValue: backendReconfigureJSON, + }, + "backend_migrate_local": { + HumanValue: backendMigrateLocalHuman, + JSONValue: backendMigrateLocalJSON, + }, + "backend_cloud_migrate_local": { + HumanValue: "Migrating from HCP Terraform or Terraform Enterprise to local state.", + JSONValue: "Migrating from HCP Terraform or Terraform Enterprise to local state.", + }, + "state_store_migrate_local": { + HumanValue: stateMigrateLocalHuman, + JSONValue: stateMigrateLocalJSON, + }, "empty_message": { HumanValue: "", JSONValue: "", @@ -302,6 +342,26 @@ const ( // InitConfigError indicates problems encountered during initialisation InitConfigError InitMessageCode = "init_config_error" + // BackendConfiguredSuccessMessage indicates successful backend configuration + BackendConfiguredSuccessMessage InitMessageCode = "backend_configured_success" + // BackendConfiguredUnsetMessage indicates successful backend unsetting + BackendConfiguredUnsetMessage InitMessageCode = "backend_configured_unset" + // BackendMigrateToCloudMessage indicates migration to HCP Terraform + BackendMigrateToCloudMessage InitMessageCode = "backend_migrate_to_cloud" + // BackendMigrateFromCloudMessage indicates migration from HCP Terraform + BackendMigrateFromCloudMessage InitMessageCode = "backend_migrate_from_cloud" + // BackendCloudChangeInPlaceMessage indicates HCP Terraform configuration change + BackendCloudChangeInPlaceMessage InitMessageCode = "backend_cloud_change_in_place" + // BackendMigrateTypeChangeMessage indicates backend type change + BackendMigrateTypeChangeMessage InitMessageCode = "backend_migrate_type_change" + // BackendReconfigureMessage indicates backend reconfiguration + BackendReconfigureMessage InitMessageCode = "backend_reconfigure" + // BackendMigrateLocalMessage indicates migration to local backend + BackendMigrateLocalMessage InitMessageCode = "backend_migrate_local" + // BackendCloudMigrateLocalMessage indicates migration from cloud to local + BackendCloudMigrateLocalMessage InitMessageCode = "backend_cloud_migrate_local" + // StateMigrateLocalMessage indicates migration from state store to local + StateMigrateLocalMessage InitMessageCode = "state_store_migrate_local" // FindingMatchingVersionMessage indicates that Terraform is looking for a provider version that matches the constraint during installation FindingMatchingVersionMessage InitMessageCode = "finding_matching_version_message" // InstalledProviderVersionInfo describes a successfully installed provider along with its version @@ -433,3 +493,40 @@ with the configuration, described below. The Terraform configuration must be valid before initialization so that Terraform can determine which modules and providers need to be installed. ` + +const backendConfiguredSuccessHuman = `[reset][green] +Successfully configured the backend %q! Terraform will automatically +use this backend unless the backend configuration changes.` + +const backendConfiguredSuccessJSON = `Successfully configured the backend %q! Terraform will automatically +use this backend unless the backend configuration changes.` + +const backendConfiguredUnsetHuman = `[reset][green] + +Successfully unset the backend %q. Terraform will now operate locally.` + +const backendConfiguredUnsetJSON = `Successfully unset the backend %q. Terraform will now operate locally.` + +const backendMigrateTypeChangeHuman = `[reset]Terraform detected that the backend type changed from %q to %q. +` + +const backendMigrateTypeChangeJSON = `Terraform detected that the backend type changed from %q to %q.` + +const backendReconfigureHuman = `[reset][bold]Backend configuration changed![reset] + +Terraform has detected that the configuration specified for the backend +has changed. Terraform will now check for existing state in the backends. +` + +const backendReconfigureJSON = `Backend configuration changed! + +Terraform has detected that the configuration specified for the backend +has changed. Terraform will now check for existing state in the backends.` + +const backendMigrateLocalHuman = `Terraform has detected you're unconfiguring your previously set %q backend.` + +const backendMigrateLocalJSON = `Terraform has detected you're unconfiguring your previously set %q backend.` + +const stateMigrateLocalHuman = `Terraform has detected you're unconfiguring your previously set %q state store.` + +const stateMigrateLocalJSON = `Terraform has detected you're unconfiguring your previously set %q state store.`