github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/views/apply.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package views
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/terramate-io/tf/command/arguments"
    10  	"github.com/terramate-io/tf/command/format"
    11  	"github.com/terramate-io/tf/command/views/json"
    12  	"github.com/terramate-io/tf/states"
    13  	"github.com/terramate-io/tf/terraform"
    14  	"github.com/terramate-io/tf/tfdiags"
    15  )
    16  
    17  // The Apply view is used for the apply command.
    18  type Apply interface {
    19  	ResourceCount(stateOutPath string)
    20  	Outputs(outputValues map[string]*states.OutputValue)
    21  
    22  	Operation() Operation
    23  	Hooks() []terraform.Hook
    24  
    25  	Diagnostics(diags tfdiags.Diagnostics)
    26  	HelpPrompt()
    27  }
    28  
    29  // NewApply returns an initialized Apply implementation for the given ViewType.
    30  func NewApply(vt arguments.ViewType, destroy bool, view *View) Apply {
    31  	switch vt {
    32  	case arguments.ViewJSON:
    33  		return &ApplyJSON{
    34  			view:      NewJSONView(view),
    35  			destroy:   destroy,
    36  			countHook: &countHook{},
    37  		}
    38  	case arguments.ViewHuman:
    39  		return &ApplyHuman{
    40  			view:         view,
    41  			destroy:      destroy,
    42  			inAutomation: view.RunningInAutomation(),
    43  			countHook:    &countHook{},
    44  		}
    45  	default:
    46  		panic(fmt.Sprintf("unknown view type %v", vt))
    47  	}
    48  }
    49  
    50  // The ApplyHuman implementation renders human-readable text logs, suitable for
    51  // a scrolling terminal.
    52  type ApplyHuman struct {
    53  	view *View
    54  
    55  	destroy      bool
    56  	inAutomation bool
    57  
    58  	countHook *countHook
    59  }
    60  
    61  var _ Apply = (*ApplyHuman)(nil)
    62  
    63  func (v *ApplyHuman) ResourceCount(stateOutPath string) {
    64  	if v.destroy {
    65  		v.view.streams.Printf(
    66  			v.view.colorize.Color("[reset][bold][green]\nDestroy complete! Resources: %d destroyed.\n"),
    67  			v.countHook.Removed,
    68  		)
    69  	} else if v.countHook.Imported > 0 {
    70  		v.view.streams.Printf(
    71  			v.view.colorize.Color("[reset][bold][green]\nApply complete! Resources: %d imported, %d added, %d changed, %d destroyed.\n"),
    72  			v.countHook.Imported,
    73  			v.countHook.Added,
    74  			v.countHook.Changed,
    75  			v.countHook.Removed,
    76  		)
    77  	} else {
    78  		v.view.streams.Printf(
    79  			v.view.colorize.Color("[reset][bold][green]\nApply complete! Resources: %d added, %d changed, %d destroyed.\n"),
    80  			v.countHook.Added,
    81  			v.countHook.Changed,
    82  			v.countHook.Removed,
    83  		)
    84  	}
    85  	if (v.countHook.Added > 0 || v.countHook.Changed > 0) && stateOutPath != "" {
    86  		v.view.streams.Printf("\n%s\n\n", format.WordWrap(stateOutPathPostApply, v.view.outputColumns()))
    87  		v.view.streams.Printf("State path: %s\n", stateOutPath)
    88  	}
    89  }
    90  
    91  func (v *ApplyHuman) Outputs(outputValues map[string]*states.OutputValue) {
    92  	if len(outputValues) > 0 {
    93  		v.view.streams.Print(v.view.colorize.Color("[reset][bold][green]\nOutputs:\n\n"))
    94  		NewOutput(arguments.ViewHuman, v.view).Output("", outputValues)
    95  	}
    96  }
    97  
    98  func (v *ApplyHuman) Operation() Operation {
    99  	return NewOperation(arguments.ViewHuman, v.inAutomation, v.view)
   100  }
   101  
   102  func (v *ApplyHuman) Hooks() []terraform.Hook {
   103  	return []terraform.Hook{
   104  		v.countHook,
   105  		NewUiHook(v.view),
   106  	}
   107  }
   108  
   109  func (v *ApplyHuman) Diagnostics(diags tfdiags.Diagnostics) {
   110  	v.view.Diagnostics(diags)
   111  }
   112  
   113  func (v *ApplyHuman) HelpPrompt() {
   114  	command := "apply"
   115  	if v.destroy {
   116  		command = "destroy"
   117  	}
   118  	v.view.HelpPrompt(command)
   119  }
   120  
   121  const stateOutPathPostApply = "The state of your infrastructure has been saved to the path below. This state is required to modify and destroy your infrastructure, so keep it safe. To inspect the complete state use the `terraform show` command."
   122  
   123  // The ApplyJSON implementation renders streaming JSON logs, suitable for
   124  // integrating with other software.
   125  type ApplyJSON struct {
   126  	view *JSONView
   127  
   128  	destroy bool
   129  
   130  	countHook *countHook
   131  }
   132  
   133  var _ Apply = (*ApplyJSON)(nil)
   134  
   135  func (v *ApplyJSON) ResourceCount(stateOutPath string) {
   136  	operation := json.OperationApplied
   137  	if v.destroy {
   138  		operation = json.OperationDestroyed
   139  	}
   140  	v.view.ChangeSummary(&json.ChangeSummary{
   141  		Add:       v.countHook.Added,
   142  		Change:    v.countHook.Changed,
   143  		Remove:    v.countHook.Removed,
   144  		Import:    v.countHook.Imported,
   145  		Operation: operation,
   146  	})
   147  }
   148  
   149  func (v *ApplyJSON) Outputs(outputValues map[string]*states.OutputValue) {
   150  	outputs, diags := json.OutputsFromMap(outputValues)
   151  	if diags.HasErrors() {
   152  		v.Diagnostics(diags)
   153  	} else {
   154  		v.view.Outputs(outputs)
   155  	}
   156  }
   157  
   158  func (v *ApplyJSON) Operation() Operation {
   159  	return &OperationJSON{view: v.view}
   160  }
   161  
   162  func (v *ApplyJSON) Hooks() []terraform.Hook {
   163  	return []terraform.Hook{
   164  		v.countHook,
   165  		newJSONHook(v.view),
   166  	}
   167  }
   168  
   169  func (v *ApplyJSON) Diagnostics(diags tfdiags.Diagnostics) {
   170  	v.view.Diagnostics(diags)
   171  }
   172  
   173  func (v *ApplyJSON) HelpPrompt() {
   174  }