1package arguments
2
3import (
4	"fmt"
5
6	"github.com/hashicorp/terraform/internal/plans"
7	"github.com/hashicorp/terraform/internal/tfdiags"
8)
9
10// Apply represents the command-line arguments for the apply command.
11type Apply struct {
12	// State, Operation, and Vars are the common extended flags
13	State     *State
14	Operation *Operation
15	Vars      *Vars
16
17	// AutoApprove skips the manual verification step for the apply operation.
18	AutoApprove bool
19
20	// InputEnabled is used to disable interactive input for unspecified
21	// variable and backend config values. Default is true.
22	InputEnabled bool
23
24	// PlanPath contains an optional path to a stored plan file
25	PlanPath string
26
27	// ViewType specifies which output format to use
28	ViewType ViewType
29}
30
31// ParseApply processes CLI arguments, returning an Apply value and errors.
32// If errors are encountered, an Apply value is still returned representing
33// the best effort interpretation of the arguments.
34func ParseApply(args []string) (*Apply, tfdiags.Diagnostics) {
35	var diags tfdiags.Diagnostics
36	apply := &Apply{
37		State:     &State{},
38		Operation: &Operation{},
39		Vars:      &Vars{},
40	}
41
42	cmdFlags := extendedFlagSet("apply", apply.State, apply.Operation, apply.Vars)
43	cmdFlags.BoolVar(&apply.AutoApprove, "auto-approve", false, "auto-approve")
44	cmdFlags.BoolVar(&apply.InputEnabled, "input", true, "input")
45
46	var json bool
47	cmdFlags.BoolVar(&json, "json", false, "json")
48
49	if err := cmdFlags.Parse(args); err != nil {
50		diags = diags.Append(tfdiags.Sourceless(
51			tfdiags.Error,
52			"Failed to parse command-line flags",
53			err.Error(),
54		))
55	}
56
57	args = cmdFlags.Args()
58	if len(args) > 0 {
59		apply.PlanPath = args[0]
60		args = args[1:]
61	}
62
63	if len(args) > 0 {
64		diags = diags.Append(tfdiags.Sourceless(
65			tfdiags.Error,
66			"Too many command line arguments",
67			"Expected at most one positional argument.",
68		))
69	}
70
71	// JSON view currently does not support input, so we disable it here.
72	if json {
73		apply.InputEnabled = false
74	}
75
76	// JSON view cannot confirm apply, so we require either a plan file or
77	// auto-approve to be specified. We intentionally fail here rather than
78	// override auto-approve, which would be dangerous.
79	if json && apply.PlanPath == "" && !apply.AutoApprove {
80		diags = diags.Append(tfdiags.Sourceless(
81			tfdiags.Error,
82			"Plan file or auto-approve required",
83			"Terraform cannot ask for interactive approval when -json is set. You can either apply a saved plan file, or enable the -auto-approve option.",
84		))
85	}
86
87	diags = diags.Append(apply.Operation.Parse())
88
89	switch {
90	case json:
91		apply.ViewType = ViewJSON
92	default:
93		apply.ViewType = ViewHuman
94	}
95
96	return apply, diags
97}
98
99// ParseApplyDestroy is a special case of ParseApply that deals with the
100// "terraform destroy" command, which is effectively an alias for
101// "terraform apply -destroy".
102func ParseApplyDestroy(args []string) (*Apply, tfdiags.Diagnostics) {
103	apply, diags := ParseApply(args)
104
105	// So far ParseApply was using the command line options like -destroy
106	// and -refresh-only to determine the plan mode. For "terraform destroy"
107	// we expect neither of those arguments to be set, and so the plan mode
108	// should currently be set to NormalMode, which we'll replace with
109	// DestroyMode here. If it's already set to something else then that
110	// suggests incorrect usage.
111	switch apply.Operation.PlanMode {
112	case plans.NormalMode:
113		// This indicates that the user didn't specify any mode options at
114		// all, which is correct, although we know from the command that
115		// they actually intended to use DestroyMode here.
116		apply.Operation.PlanMode = plans.DestroyMode
117	case plans.DestroyMode:
118		diags = diags.Append(tfdiags.Sourceless(
119			tfdiags.Error,
120			"Invalid mode option",
121			"The -destroy option is not valid for \"terraform destroy\", because this command always runs in destroy mode.",
122		))
123	case plans.RefreshOnlyMode:
124		diags = diags.Append(tfdiags.Sourceless(
125			tfdiags.Error,
126			"Invalid mode option",
127			"The -refresh-only option is not valid for \"terraform destroy\".",
128		))
129	default:
130		// This is a non-ideal error message for if we forget to handle a
131		// newly-handled plan mode in Operation.Parse. Ideally they should all
132		// have cases above so we can produce better error messages.
133		diags = diags.Append(tfdiags.Sourceless(
134			tfdiags.Error,
135			"Invalid mode option",
136			fmt.Sprintf("The \"terraform destroy\" command doesn't support %s.", apply.Operation.PlanMode),
137		))
138	}
139
140	// NOTE: It's also invalid to have apply.PlanPath set in this codepath,
141	// but we don't check that in here because we'll return a different error
142	// message depending on whether the given path seems to refer to a saved
143	// plan file or to a configuration directory. The apply command
144	// implementation itself therefore handles this situation.
145
146	return apply, diags
147}
148