github.com/hugorut/terraform@v1.1.3/src/command/views/operation.go (about)

     1  package views
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/hugorut/terraform/src/addrs"
     9  	"github.com/hugorut/terraform/src/command/arguments"
    10  	"github.com/hugorut/terraform/src/command/format"
    11  	"github.com/hugorut/terraform/src/command/views/json"
    12  	"github.com/hugorut/terraform/src/plans"
    13  	"github.com/hugorut/terraform/src/states/statefile"
    14  	"github.com/hugorut/terraform/src/terraform"
    15  	"github.com/hugorut/terraform/src/tfdiags"
    16  )
    17  
    18  type Operation interface {
    19  	Interrupted()
    20  	FatalInterrupt()
    21  	Stopping()
    22  	Cancelled(planMode plans.Mode)
    23  
    24  	EmergencyDumpState(stateFile *statefile.File) error
    25  
    26  	PlannedChange(change *plans.ResourceInstanceChangeSrc)
    27  	Plan(plan *plans.Plan, schemas *terraform.Schemas)
    28  	PlanNextStep(planPath string)
    29  
    30  	Diagnostics(diags tfdiags.Diagnostics)
    31  }
    32  
    33  func NewOperation(vt arguments.ViewType, inAutomation bool, view *View) Operation {
    34  	switch vt {
    35  	case arguments.ViewHuman:
    36  		return &OperationHuman{view: view, inAutomation: inAutomation}
    37  	default:
    38  		panic(fmt.Sprintf("unknown view type %v", vt))
    39  	}
    40  }
    41  
    42  type OperationHuman struct {
    43  	view *View
    44  
    45  	// inAutomation indicates that commands are being run by an
    46  	// automated system rather than directly at a command prompt.
    47  	//
    48  	// This is a hint not to produce messages that expect that a user can
    49  	// run a follow-up command, perhaps because Terraform is running in
    50  	// some sort of workflow automation tool that abstracts away the
    51  	// exact commands that are being run.
    52  	inAutomation bool
    53  }
    54  
    55  var _ Operation = (*OperationHuman)(nil)
    56  
    57  func (v *OperationHuman) Interrupted() {
    58  	v.view.streams.Println(format.WordWrap(interrupted, v.view.outputColumns()))
    59  }
    60  
    61  func (v *OperationHuman) FatalInterrupt() {
    62  	v.view.streams.Eprintln(format.WordWrap(fatalInterrupt, v.view.errorColumns()))
    63  }
    64  
    65  func (v *OperationHuman) Stopping() {
    66  	v.view.streams.Println("Stopping operation...")
    67  }
    68  
    69  func (v *OperationHuman) Cancelled(planMode plans.Mode) {
    70  	switch planMode {
    71  	case plans.DestroyMode:
    72  		v.view.streams.Println("Destroy cancelled.")
    73  	default:
    74  		v.view.streams.Println("Apply cancelled.")
    75  	}
    76  }
    77  
    78  func (v *OperationHuman) EmergencyDumpState(stateFile *statefile.File) error {
    79  	stateBuf := new(bytes.Buffer)
    80  	jsonErr := statefile.Write(stateFile, stateBuf)
    81  	if jsonErr != nil {
    82  		return jsonErr
    83  	}
    84  	v.view.streams.Eprintln(stateBuf)
    85  	return nil
    86  }
    87  
    88  func (v *OperationHuman) Plan(plan *plans.Plan, schemas *terraform.Schemas) {
    89  	renderPlan(plan, schemas, v.view)
    90  }
    91  
    92  func (v *OperationHuman) PlannedChange(change *plans.ResourceInstanceChangeSrc) {
    93  	// PlannedChange is primarily for machine-readable output in order to
    94  	// get a per-resource-instance change description. We don't use it
    95  	// with OperationHuman because the output of Plan already includes the
    96  	// change details for all resource instances.
    97  }
    98  
    99  // PlanNextStep gives the user some next-steps, unless we're running in an
   100  // automation tool which is presumed to provide its own UI for further actions.
   101  func (v *OperationHuman) PlanNextStep(planPath string) {
   102  	if v.inAutomation {
   103  		return
   104  	}
   105  	v.view.outputHorizRule()
   106  
   107  	if planPath == "" {
   108  		v.view.streams.Print(
   109  			"\n" + strings.TrimSpace(format.WordWrap(planHeaderNoOutput, v.view.outputColumns())) + "\n",
   110  		)
   111  	} else {
   112  		v.view.streams.Printf(
   113  			"\n"+strings.TrimSpace(format.WordWrap(planHeaderYesOutput, v.view.outputColumns()))+"\n",
   114  			planPath, planPath,
   115  		)
   116  	}
   117  }
   118  
   119  func (v *OperationHuman) Diagnostics(diags tfdiags.Diagnostics) {
   120  	v.view.Diagnostics(diags)
   121  }
   122  
   123  type OperationJSON struct {
   124  	view *JSONView
   125  }
   126  
   127  var _ Operation = (*OperationJSON)(nil)
   128  
   129  func (v *OperationJSON) Interrupted() {
   130  	v.view.Log(interrupted)
   131  }
   132  
   133  func (v *OperationJSON) FatalInterrupt() {
   134  	v.view.Log(fatalInterrupt)
   135  }
   136  
   137  func (v *OperationJSON) Stopping() {
   138  	v.view.Log("Stopping operation...")
   139  }
   140  
   141  func (v *OperationJSON) Cancelled(planMode plans.Mode) {
   142  	switch planMode {
   143  	case plans.DestroyMode:
   144  		v.view.Log("Destroy cancelled")
   145  	default:
   146  		v.view.Log("Apply cancelled")
   147  	}
   148  }
   149  
   150  func (v *OperationJSON) EmergencyDumpState(stateFile *statefile.File) error {
   151  	stateBuf := new(bytes.Buffer)
   152  	jsonErr := statefile.Write(stateFile, stateBuf)
   153  	if jsonErr != nil {
   154  		return jsonErr
   155  	}
   156  	v.view.StateDump(stateBuf.String())
   157  	return nil
   158  }
   159  
   160  // Log a change summary and a series of "planned" messages for the changes in
   161  // the plan.
   162  func (v *OperationJSON) Plan(plan *plans.Plan, schemas *terraform.Schemas) {
   163  	for _, dr := range plan.DriftedResources {
   164  		// In refresh-only mode, we output all resources marked as drifted,
   165  		// including those which have moved without other changes. In other plan
   166  		// modes, move-only changes will be included in the planned changes, so
   167  		// we skip them here.
   168  		if dr.Action != plans.NoOp || plan.UIMode == plans.RefreshOnlyMode {
   169  			v.view.ResourceDrift(json.NewResourceInstanceChange(dr))
   170  		}
   171  	}
   172  
   173  	cs := &json.ChangeSummary{
   174  		Operation: json.OperationPlanned,
   175  	}
   176  	for _, change := range plan.Changes.Resources {
   177  		if change.Action == plans.Delete && change.Addr.Resource.Resource.Mode == addrs.DataResourceMode {
   178  			// Avoid rendering data sources on deletion
   179  			continue
   180  		}
   181  		switch change.Action {
   182  		case plans.Create:
   183  			cs.Add++
   184  		case plans.Delete:
   185  			cs.Remove++
   186  		case plans.Update:
   187  			cs.Change++
   188  		case plans.CreateThenDelete, plans.DeleteThenCreate:
   189  			cs.Add++
   190  			cs.Remove++
   191  		}
   192  
   193  		if change.Action != plans.NoOp || !change.Addr.Equal(change.PrevRunAddr) {
   194  			v.view.PlannedChange(json.NewResourceInstanceChange(change))
   195  		}
   196  	}
   197  
   198  	v.view.ChangeSummary(cs)
   199  
   200  	var rootModuleOutputs []*plans.OutputChangeSrc
   201  	for _, output := range plan.Changes.Outputs {
   202  		if !output.Addr.Module.IsRoot() {
   203  			continue
   204  		}
   205  		rootModuleOutputs = append(rootModuleOutputs, output)
   206  	}
   207  	if len(rootModuleOutputs) > 0 {
   208  		v.view.Outputs(json.OutputsFromChanges(rootModuleOutputs))
   209  	}
   210  }
   211  
   212  func (v *OperationJSON) PlannedChange(change *plans.ResourceInstanceChangeSrc) {
   213  	if change.Action == plans.Delete && change.Addr.Resource.Resource.Mode == addrs.DataResourceMode {
   214  		// Avoid rendering data sources on deletion
   215  		return
   216  	}
   217  	v.view.PlannedChange(json.NewResourceInstanceChange(change))
   218  }
   219  
   220  // PlanNextStep does nothing for the JSON view as it is a hook for user-facing
   221  // output only applicable to human-readable UI.
   222  func (v *OperationJSON) PlanNextStep(planPath string) {
   223  }
   224  
   225  func (v *OperationJSON) Diagnostics(diags tfdiags.Diagnostics) {
   226  	v.view.Diagnostics(diags)
   227  }
   228  
   229  const fatalInterrupt = `
   230  Two interrupts received. Exiting immediately. Note that data loss may have occurred.
   231  `
   232  
   233  const interrupted = `
   234  Interrupt received.
   235  Please wait for Terraform to exit or data loss may occur.
   236  Gracefully shutting down...
   237  `
   238  
   239  const planHeaderNoOutput = `
   240  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.
   241  `
   242  
   243  const planHeaderYesOutput = `
   244  Saved the plan to: %s
   245  
   246  To perform exactly these actions, run the following command to apply:
   247      terraform apply %q
   248  `