mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-03-20 22:01:25 -04:00
423 lines
11 KiB
Go
423 lines
11 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package views
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/command/arguments"
|
|
"github.com/opentofu/opentofu/internal/states"
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
)
|
|
|
|
type State interface {
|
|
Diagnostics(diags tfdiags.Diagnostics)
|
|
|
|
// General `state` output
|
|
StateNotFound()
|
|
StateLoadingFailure(baseError string)
|
|
StateSavingError(baseError string)
|
|
|
|
// `tofu state list` specific
|
|
StateListAddr(resAddr addrs.AbsResourceInstance)
|
|
|
|
// `tofu state mv` specific
|
|
ErrorMovingToAlreadyExistingDst()
|
|
ResourceMoveStatus(dryRun bool, src, dest string)
|
|
DryRunMovedStatus(moved int)
|
|
MoveFinalStatus(moved int)
|
|
|
|
// `tofu state pull` specific
|
|
PrintPulledState(state string)
|
|
|
|
// `tofu state replace-provider` specific
|
|
NoMatchingResourcesForProviderReplacement()
|
|
ReplaceProviderOverview(from, to addrs.Provider, willReplace []*states.Resource)
|
|
ReplaceProviderCancelled()
|
|
ProviderReplaced(forResources int)
|
|
|
|
// `tofu state rm` specific
|
|
ResourceRemoveStatus(dryRun bool, target string)
|
|
DryRunRemovedStatus(removed int)
|
|
RemoveFinalStatus(count int)
|
|
}
|
|
|
|
// NewState returns an initialized State implementation for the given ViewType.
|
|
func NewState(args arguments.ViewOptions, view *View) State {
|
|
var ret State
|
|
switch args.ViewType {
|
|
case arguments.ViewJSON:
|
|
ret = &StateJSON{view: NewJSONView(view, nil)}
|
|
case arguments.ViewHuman:
|
|
ret = &StateHuman{view: view}
|
|
default:
|
|
panic(fmt.Sprintf("unknown view type %v", args.ViewType))
|
|
}
|
|
|
|
if args.JSONInto != nil {
|
|
ret = &StateMulti{ret, &StateJSON{view: NewJSONView(view, args.JSONInto)}}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
type StateMulti []State
|
|
|
|
var _ State = (StateMulti)(nil)
|
|
|
|
func (m StateMulti) Diagnostics(diags tfdiags.Diagnostics) {
|
|
for _, o := range m {
|
|
o.Diagnostics(diags)
|
|
}
|
|
}
|
|
|
|
func (m StateMulti) StateNotFound() {
|
|
for _, o := range m {
|
|
o.StateNotFound()
|
|
}
|
|
}
|
|
|
|
func (m StateMulti) StateLoadingFailure(baseError string) {
|
|
for _, o := range m {
|
|
o.StateLoadingFailure(baseError)
|
|
}
|
|
}
|
|
|
|
func (m StateMulti) StateSavingError(baseError string) {
|
|
for _, o := range m {
|
|
o.StateSavingError(baseError)
|
|
}
|
|
}
|
|
|
|
func (m StateMulti) StateListAddr(resAddr addrs.AbsResourceInstance) {
|
|
for _, o := range m {
|
|
o.StateListAddr(resAddr)
|
|
}
|
|
}
|
|
|
|
func (m StateMulti) ErrorMovingToAlreadyExistingDst() {
|
|
for _, o := range m {
|
|
o.ErrorMovingToAlreadyExistingDst()
|
|
}
|
|
}
|
|
|
|
func (m StateMulti) ResourceMoveStatus(dryRun bool, src, dest string) {
|
|
for _, o := range m {
|
|
o.ResourceMoveStatus(dryRun, src, dest)
|
|
}
|
|
}
|
|
|
|
func (m StateMulti) DryRunMovedStatus(moved int) {
|
|
for _, o := range m {
|
|
o.DryRunMovedStatus(moved)
|
|
}
|
|
}
|
|
|
|
func (m StateMulti) MoveFinalStatus(moved int) {
|
|
for _, o := range m {
|
|
o.MoveFinalStatus(moved)
|
|
}
|
|
}
|
|
|
|
func (m StateMulti) PrintPulledState(state string) {
|
|
for _, o := range m {
|
|
o.PrintPulledState(state)
|
|
}
|
|
}
|
|
|
|
func (m StateMulti) NoMatchingResourcesForProviderReplacement() {
|
|
for _, o := range m {
|
|
o.NoMatchingResourcesForProviderReplacement()
|
|
}
|
|
}
|
|
|
|
func (m StateMulti) ReplaceProviderOverview(from, to addrs.Provider, willReplace []*states.Resource) {
|
|
for _, o := range m {
|
|
o.ReplaceProviderOverview(from, to, willReplace)
|
|
}
|
|
}
|
|
|
|
func (m StateMulti) ReplaceProviderCancelled() {
|
|
for _, o := range m {
|
|
o.ReplaceProviderCancelled()
|
|
}
|
|
}
|
|
|
|
func (m StateMulti) ProviderReplaced(forResources int) {
|
|
for _, o := range m {
|
|
o.ProviderReplaced(forResources)
|
|
}
|
|
}
|
|
|
|
func (m StateMulti) ResourceRemoveStatus(dryRun bool, target string) {
|
|
for _, o := range m {
|
|
o.ResourceRemoveStatus(dryRun, target)
|
|
}
|
|
}
|
|
|
|
func (m StateMulti) DryRunRemovedStatus(removed int) {
|
|
for _, o := range m {
|
|
o.DryRunRemovedStatus(removed)
|
|
}
|
|
}
|
|
|
|
func (m StateMulti) RemoveFinalStatus(count int) {
|
|
for _, o := range m {
|
|
o.RemoveFinalStatus(count)
|
|
}
|
|
}
|
|
|
|
type StateHuman struct {
|
|
view *View
|
|
}
|
|
|
|
var _ State = (*StateHuman)(nil)
|
|
|
|
func (v *StateHuman) Diagnostics(diags tfdiags.Diagnostics) {
|
|
v.view.Diagnostics(diags)
|
|
}
|
|
|
|
func (v *StateHuman) StateNotFound() {
|
|
v.view.errorln(errStateNotFound)
|
|
}
|
|
|
|
func (v *StateHuman) StateLoadingFailure(baseError string) {
|
|
v.view.errorln(fmt.Sprintf(errStateLoadingState, baseError))
|
|
}
|
|
|
|
func (v *StateHuman) StateSavingError(baseError string) {
|
|
v.view.errorln(fmt.Sprintf(errStateRmPersist, baseError))
|
|
}
|
|
|
|
func (v *StateHuman) StateListAddr(resAddr addrs.AbsResourceInstance) {
|
|
_, _ = v.view.streams.Println(resAddr.String())
|
|
}
|
|
|
|
func (v *StateHuman) ErrorMovingToAlreadyExistingDst() {
|
|
v.view.errorln(errStateMvDstExists)
|
|
}
|
|
|
|
func (v *StateHuman) ResourceMoveStatus(dryRun bool, src, dest string) {
|
|
if dryRun {
|
|
_, _ = v.view.streams.Println(fmt.Sprintf("Would move %q to %q", src, dest))
|
|
return
|
|
}
|
|
_, _ = v.view.streams.Println(fmt.Sprintf("Move %q to %q", src, dest))
|
|
}
|
|
|
|
func (v *StateHuman) DryRunMovedStatus(moved int) {
|
|
if moved == 0 {
|
|
_, _ = v.view.streams.Println("Would have moved nothing.")
|
|
}
|
|
}
|
|
|
|
func (v *StateHuman) MoveFinalStatus(moved int) {
|
|
if moved == 0 {
|
|
_, _ = v.view.streams.Println("No matching objects found.")
|
|
return
|
|
}
|
|
_, _ = v.view.streams.Println(fmt.Sprintf("Successfully moved %d object(s).", moved))
|
|
}
|
|
|
|
func (v *StateHuman) PrintPulledState(state string) {
|
|
_, _ = v.view.streams.Println(state)
|
|
}
|
|
|
|
func (v *StateHuman) NoMatchingResourcesForProviderReplacement() {
|
|
_, _ = v.view.streams.Println("No matching resources found.")
|
|
}
|
|
|
|
func (v *StateHuman) ReplaceProviderOverview(from, to addrs.Provider, willReplace []*states.Resource) {
|
|
colorize := v.view.colorize.Color
|
|
printer := func(args ...any) { _, _ = v.view.streams.Println(args...) }
|
|
printer("OpenTofu will perform the following actions:\n")
|
|
printer(colorize(" [yellow]~[reset] Updating provider:"))
|
|
printer(colorize(fmt.Sprintf(" [red]-[reset] %s", from)))
|
|
printer(colorize(fmt.Sprintf(" [green]+[reset] %s\n", to)))
|
|
|
|
printer(colorize(fmt.Sprintf("[bold]Changing[reset] %d resources:\n", len(willReplace))))
|
|
for _, resource := range willReplace {
|
|
printer(colorize(fmt.Sprintf(" %s", resource.Addr)))
|
|
}
|
|
}
|
|
|
|
func (v *StateHuman) ReplaceProviderCancelled() {
|
|
_, _ = v.view.streams.Println("Cancelled replacing providers.")
|
|
}
|
|
|
|
func (v *StateHuman) ProviderReplaced(forResources int) {
|
|
_, _ = v.view.streams.Println(fmt.Sprintf("Successfully replaced provider for %d resources.", forResources))
|
|
}
|
|
|
|
func (v *StateHuman) ResourceRemoveStatus(dryRun bool, target string) {
|
|
if dryRun {
|
|
_, _ = v.view.streams.Println(fmt.Sprintf("Would remove %s", target))
|
|
return
|
|
}
|
|
_, _ = v.view.streams.Println(fmt.Sprintf("Removed %s", target))
|
|
}
|
|
|
|
func (v *StateHuman) DryRunRemovedStatus(removed int) {
|
|
if removed == 0 {
|
|
_, _ = v.view.streams.Println("Would have removed nothing.")
|
|
}
|
|
}
|
|
|
|
func (v *StateHuman) RemoveFinalStatus(count int) {
|
|
if count == 0 {
|
|
// NOTE: printing nothing here since this case needs to be handled by the caller
|
|
return
|
|
}
|
|
_, _ = v.view.streams.Println(fmt.Sprintf("Successfully removed %d resource instance(s).", count))
|
|
}
|
|
|
|
type StateJSON struct {
|
|
view *JSONView
|
|
}
|
|
|
|
var _ State = (*StateJSON)(nil)
|
|
|
|
func (v *StateJSON) Diagnostics(diags tfdiags.Diagnostics) {
|
|
v.view.Diagnostics(diags)
|
|
}
|
|
|
|
func (v *StateJSON) StateNotFound() {
|
|
cleanedUp := strings.ReplaceAll(
|
|
strings.ReplaceAll(errStateNotFound, "\n", " "),
|
|
" ", " ",
|
|
)
|
|
v.view.Error(cleanedUp)
|
|
}
|
|
|
|
func (v *StateJSON) StateLoadingFailure(baseError string) {
|
|
cleanedUp := strings.ReplaceAll(
|
|
strings.ReplaceAll(errStateLoadingState, "\n", " "),
|
|
" ", " ",
|
|
)
|
|
if baseError != "" && !strings.HasSuffix(baseError, ".") {
|
|
baseError += "."
|
|
}
|
|
msg := fmt.Sprintf(cleanedUp, baseError)
|
|
v.view.Error(msg)
|
|
}
|
|
|
|
func (v *StateJSON) StateSavingError(baseError string) {
|
|
cleanedUp := strings.ReplaceAll(
|
|
strings.ReplaceAll(errStateRmPersist, "\n", " "),
|
|
" ", " ",
|
|
)
|
|
if baseError != "" && !strings.HasSuffix(baseError, ".") {
|
|
baseError += "."
|
|
}
|
|
msg := fmt.Sprintf(cleanedUp, baseError)
|
|
v.view.Error(msg)
|
|
}
|
|
|
|
func (v *StateJSON) StateListAddr(resAddr addrs.AbsResourceInstance) {
|
|
v.view.log.Info(resAddr.String(), "type", "resource_address")
|
|
}
|
|
|
|
func (v *StateJSON) ErrorMovingToAlreadyExistingDst() {
|
|
cleanedUp := strings.ReplaceAll(
|
|
strings.ReplaceAll(errStateMvDstExists, "\n", " "),
|
|
" ", " ",
|
|
)
|
|
v.view.Error(cleanedUp)
|
|
}
|
|
|
|
func (v *StateJSON) ResourceMoveStatus(dryRun bool, src, dest string) {
|
|
if dryRun {
|
|
v.view.Info(fmt.Sprintf("Would move %q to %q", src, dest))
|
|
return
|
|
}
|
|
v.view.Info(fmt.Sprintf("Move %q to %q", src, dest))
|
|
}
|
|
|
|
func (v *StateJSON) DryRunMovedStatus(moved int) {
|
|
if moved == 0 {
|
|
v.view.Info("Would have moved nothing")
|
|
}
|
|
}
|
|
|
|
func (v *StateJSON) MoveFinalStatus(moved int) {
|
|
if moved == 0 {
|
|
v.view.Info("No matching objects found")
|
|
return
|
|
}
|
|
v.view.Info(fmt.Sprintf("Successfully moved %d object(s)", moved))
|
|
}
|
|
|
|
func (v *StateJSON) PrintPulledState(_ string) {
|
|
v.view.Error("printing the pulled state is not available in the JSON view. The `tofu state pull` should not be configured with the `-json` flag")
|
|
}
|
|
|
|
func (v *StateJSON) NoMatchingResourcesForProviderReplacement() {
|
|
v.view.log.Info("No matching resources found")
|
|
}
|
|
|
|
func (v *StateJSON) ReplaceProviderOverview(from, to addrs.Provider, willReplace []*states.Resource) {
|
|
replacedResources := make([]string, len(willReplace))
|
|
for i, resource := range willReplace {
|
|
replacedResources[i] = resource.Addr.String()
|
|
}
|
|
msg := fmt.Sprintf("OpenTofu will replace provider from %s to %s for %d resources", from, to, len(willReplace))
|
|
v.view.log.Info(msg, "resources", replacedResources, "type", "replace_provider", "from", from.String(), "to", to.String())
|
|
}
|
|
|
|
func (v *StateJSON) ReplaceProviderCancelled() {
|
|
v.view.Info("Cancelled replacing providers")
|
|
}
|
|
|
|
func (v *StateJSON) ProviderReplaced(forResources int) {
|
|
v.view.Info(fmt.Sprintf("Successfully replaced provider for %d resources", forResources))
|
|
}
|
|
|
|
func (v *StateJSON) ResourceRemoveStatus(dryRun bool, target string) {
|
|
if dryRun {
|
|
v.view.Info(fmt.Sprintf("Would remove %s", target))
|
|
return
|
|
}
|
|
v.view.Info(fmt.Sprintf("Removed %s", target))
|
|
}
|
|
|
|
func (v *StateJSON) DryRunRemovedStatus(removed int) {
|
|
if removed == 0 {
|
|
v.view.Info("Would have removed nothing")
|
|
}
|
|
}
|
|
|
|
func (v *StateJSON) RemoveFinalStatus(count int) {
|
|
if count == 0 {
|
|
// NOTE: printing nothing here since this case needs to be handled by the caller
|
|
return
|
|
}
|
|
v.view.Info(fmt.Sprintf("Successfully removed %d resource instance(s)", count))
|
|
}
|
|
|
|
const errStateLoadingState = `Error loading the state: %[1]s
|
|
|
|
Please ensure that your OpenTofu state exists and that you've
|
|
configured it properly. You can use the "-state" flag to point
|
|
OpenTofu at another state file.`
|
|
|
|
const errStateNotFound = `No state file was found!
|
|
|
|
State management commands require a state file. Run this command
|
|
in a directory where OpenTofu has been run or use the -state flag
|
|
to point the command to a specific state location.`
|
|
|
|
const errStateMvDstExists = `Error moving state: destination module already exists.
|
|
|
|
Please ensure your addresses and state paths are valid. No
|
|
state was persisted. Your existing states are untouched.`
|
|
|
|
const errStateRmPersist = `Error saving the state: %s
|
|
|
|
The state was not saved. No items were removed from the persisted
|
|
state. No backup was created since no modification occurred. Please
|
|
resolve the issue above and try again.`
|