mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-07 22:53:08 -06:00
05caff2ca3
This is part of a general effort to move all of Terraform's non-library package surface under internal in order to reinforce that these are for internal use within Terraform only. If you were previously importing packages under this prefix into an external codebase, you could pin to an earlier release tag as an interim solution until you've make a plan to achieve the same functionality some other way.
228 lines
6.0 KiB
Go
228 lines
6.0 KiB
Go
package views
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
"github.com/hashicorp/terraform/command/arguments"
|
|
"github.com/hashicorp/terraform/command/format"
|
|
"github.com/hashicorp/terraform/command/views/json"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
"github.com/hashicorp/terraform/plans"
|
|
"github.com/hashicorp/terraform/states/statefile"
|
|
"github.com/hashicorp/terraform/terraform"
|
|
)
|
|
|
|
type Operation interface {
|
|
Interrupted()
|
|
FatalInterrupt()
|
|
Stopping()
|
|
Cancelled(planMode plans.Mode)
|
|
|
|
EmergencyDumpState(stateFile *statefile.File) error
|
|
|
|
PlannedChange(change *plans.ResourceInstanceChangeSrc)
|
|
Plan(plan *plans.Plan, schemas *terraform.Schemas)
|
|
PlanNextStep(planPath string)
|
|
|
|
Diagnostics(diags tfdiags.Diagnostics)
|
|
}
|
|
|
|
func NewOperation(vt arguments.ViewType, inAutomation bool, view *View) Operation {
|
|
switch vt {
|
|
case arguments.ViewHuman:
|
|
return &OperationHuman{view: view, inAutomation: inAutomation}
|
|
default:
|
|
panic(fmt.Sprintf("unknown view type %v", vt))
|
|
}
|
|
}
|
|
|
|
type OperationHuman struct {
|
|
view *View
|
|
|
|
// inAutomation indicates that commands are being run by an
|
|
// automated system rather than directly at a command prompt.
|
|
//
|
|
// This is a hint not to produce messages that expect that a user can
|
|
// run a follow-up command, perhaps because Terraform is running in
|
|
// some sort of workflow automation tool that abstracts away the
|
|
// exact commands that are being run.
|
|
inAutomation bool
|
|
}
|
|
|
|
var _ Operation = (*OperationHuman)(nil)
|
|
|
|
func (v *OperationHuman) Interrupted() {
|
|
v.view.streams.Println(format.WordWrap(interrupted, v.view.outputColumns()))
|
|
}
|
|
|
|
func (v *OperationHuman) FatalInterrupt() {
|
|
v.view.streams.Eprintln(format.WordWrap(fatalInterrupt, v.view.errorColumns()))
|
|
}
|
|
|
|
func (v *OperationHuman) Stopping() {
|
|
v.view.streams.Println("Stopping operation...")
|
|
}
|
|
|
|
func (v *OperationHuman) Cancelled(planMode plans.Mode) {
|
|
switch planMode {
|
|
case plans.DestroyMode:
|
|
v.view.streams.Println("Destroy cancelled.")
|
|
default:
|
|
v.view.streams.Println("Apply cancelled.")
|
|
}
|
|
}
|
|
|
|
func (v *OperationHuman) EmergencyDumpState(stateFile *statefile.File) error {
|
|
stateBuf := new(bytes.Buffer)
|
|
jsonErr := statefile.Write(stateFile, stateBuf)
|
|
if jsonErr != nil {
|
|
return jsonErr
|
|
}
|
|
v.view.streams.Eprintln(stateBuf)
|
|
return nil
|
|
}
|
|
|
|
func (v *OperationHuman) Plan(plan *plans.Plan, schemas *terraform.Schemas) {
|
|
renderPlan(plan, schemas, v.view)
|
|
}
|
|
|
|
func (v *OperationHuman) PlannedChange(change *plans.ResourceInstanceChangeSrc) {
|
|
// PlannedChange is primarily for machine-readable output in order to
|
|
// get a per-resource-instance change description. We don't use it
|
|
// with OperationHuman because the output of Plan already includes the
|
|
// change details for all resource instances.
|
|
}
|
|
|
|
// PlanNextStep gives the user some next-steps, unless we're running in an
|
|
// automation tool which is presumed to provide its own UI for further actions.
|
|
func (v *OperationHuman) PlanNextStep(planPath string) {
|
|
if v.inAutomation {
|
|
return
|
|
}
|
|
v.view.outputHorizRule()
|
|
|
|
if planPath == "" {
|
|
v.view.streams.Print(
|
|
"\n" + strings.TrimSpace(format.WordWrap(planHeaderNoOutput, v.view.outputColumns())) + "\n",
|
|
)
|
|
} else {
|
|
v.view.streams.Printf(
|
|
"\n"+strings.TrimSpace(format.WordWrap(planHeaderYesOutput, v.view.outputColumns()))+"\n",
|
|
planPath, planPath,
|
|
)
|
|
}
|
|
}
|
|
|
|
func (v *OperationHuman) Diagnostics(diags tfdiags.Diagnostics) {
|
|
v.view.Diagnostics(diags)
|
|
}
|
|
|
|
type OperationJSON struct {
|
|
view *JSONView
|
|
}
|
|
|
|
var _ Operation = (*OperationJSON)(nil)
|
|
|
|
func (v *OperationJSON) Interrupted() {
|
|
v.view.Log(interrupted)
|
|
}
|
|
|
|
func (v *OperationJSON) FatalInterrupt() {
|
|
v.view.Log(fatalInterrupt)
|
|
}
|
|
|
|
func (v *OperationJSON) Stopping() {
|
|
v.view.Log("Stopping operation...")
|
|
}
|
|
|
|
func (v *OperationJSON) Cancelled(planMode plans.Mode) {
|
|
switch planMode {
|
|
case plans.DestroyMode:
|
|
v.view.Log("Destroy cancelled")
|
|
default:
|
|
v.view.Log("Apply cancelled")
|
|
}
|
|
}
|
|
|
|
func (v *OperationJSON) EmergencyDumpState(stateFile *statefile.File) error {
|
|
stateBuf := new(bytes.Buffer)
|
|
jsonErr := statefile.Write(stateFile, stateBuf)
|
|
if jsonErr != nil {
|
|
return jsonErr
|
|
}
|
|
v.view.StateDump(stateBuf.String())
|
|
return nil
|
|
}
|
|
|
|
// Log a change summary and a series of "planned" messages for the changes in
|
|
// the plan.
|
|
func (v *OperationJSON) Plan(plan *plans.Plan, schemas *terraform.Schemas) {
|
|
cs := &json.ChangeSummary{
|
|
Operation: json.OperationPlanned,
|
|
}
|
|
for _, change := range plan.Changes.Resources {
|
|
if change.Action == plans.Delete && change.Addr.Resource.Resource.Mode == addrs.DataResourceMode {
|
|
// Avoid rendering data sources on deletion
|
|
continue
|
|
}
|
|
switch change.Action {
|
|
case plans.Create:
|
|
cs.Add++
|
|
case plans.Delete:
|
|
cs.Remove++
|
|
case plans.Update:
|
|
cs.Change++
|
|
case plans.CreateThenDelete, plans.DeleteThenCreate:
|
|
cs.Add++
|
|
cs.Remove++
|
|
}
|
|
|
|
if change.Action != plans.NoOp {
|
|
v.view.PlannedChange(json.NewResourceInstanceChange(change))
|
|
}
|
|
}
|
|
|
|
v.view.ChangeSummary(cs)
|
|
}
|
|
|
|
func (v *OperationJSON) PlannedChange(change *plans.ResourceInstanceChangeSrc) {
|
|
if change.Action == plans.Delete && change.Addr.Resource.Resource.Mode == addrs.DataResourceMode {
|
|
// Avoid rendering data sources on deletion
|
|
return
|
|
}
|
|
v.view.PlannedChange(json.NewResourceInstanceChange(change))
|
|
}
|
|
|
|
// PlanNextStep does nothing for the JSON view as it is a hook for user-facing
|
|
// output only applicable to human-readable UI.
|
|
func (v *OperationJSON) PlanNextStep(planPath string) {
|
|
}
|
|
|
|
func (v *OperationJSON) Diagnostics(diags tfdiags.Diagnostics) {
|
|
v.view.Diagnostics(diags)
|
|
}
|
|
|
|
const fatalInterrupt = `
|
|
Two interrupts received. Exiting immediately. Note that data loss may have occurred.
|
|
`
|
|
|
|
const interrupted = `
|
|
Interrupt received.
|
|
Please wait for Terraform to exit or data loss may occur.
|
|
Gracefully shutting down...
|
|
`
|
|
|
|
const planHeaderNoOutput = `
|
|
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
|
|
`
|
|
|
|
const planHeaderYesOutput = `
|
|
Saved the plan to: %s
|
|
|
|
To perform exactly these actions, run the following command to apply:
|
|
terraform apply %q
|
|
`
|