Skip to content

Commit c4a74db

Browse files
authored
fix: parse error when only outputs is changed (#896)
* fix: parse error when only outputs is changed * If U+2500 is used as a rule, `ChangedResult` cannot be extracted correctly. * fix: `Change Result` is not output when only outputs is changed * chore: improve plan results message when only Outputs will be changed
1 parent 7cb1a89 commit c4a74db

File tree

2 files changed

+144
-6
lines changed

2 files changed

+144
-6
lines changed

pkg/terraform/parser.go

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func NewDefaultParser() *DefaultParser {
6464
// NewPlanParser is PlanParser initialized with its Regexp
6565
func NewPlanParser() *PlanParser {
6666
return &PlanParser{
67-
Pass: regexp.MustCompile(`(?m)^(Plan: \d|No changes.)`),
67+
Pass: regexp.MustCompile(`(?m)^(Plan: \d|No changes.|Changes to Outputs:)`),
6868
Fail: regexp.MustCompile(`(?m)^(Error: )`),
6969
// "0 to destroy" should be treated as "no destroy"
7070
HasDestroy: regexp.MustCompile(`(?m)([1-9][0-9]* to destroy.)`),
@@ -150,14 +150,22 @@ func (p *PlanParser) Parse(body string) ParseResult { //nolint:cyclop
150150
if line == "Terraform will perform the following actions:" { // https://github.com/hashicorp/terraform/blob/332045a4e4b1d256c45f98aac74e31102ace7af7/internal/command/views/plan.go#L252
151151
startChangeOutput = i + 1
152152
}
153-
if startChangeOutput != -1 && endChangeOutput == -1 && strings.HasPrefix(line, "Plan: ") { // https://github.com/hashicorp/terraform/blob/dfc12a6a9e1cff323829026d51873c1b80200757/internal/command/views/plan.go#L306
154-
endChangeOutput = i + 1
153+
// If we have output changes but not resource changes, Terraform
154+
// does not output `Terraform will perform the following actions:`.
155+
if line == "Changes to Outputs:" && startChangeOutput == -1 {
156+
startChangeOutput = i
155157
}
156158
if strings.HasPrefix(line, "Warning:") && startWarning == -1 {
157159
startWarning = i
158160
}
159-
if strings.HasPrefix(line, "─────") && startWarning != -1 && endWarning == -1 {
160-
endWarning = i
161+
// Terraform uses two types of rules.
162+
if strings.HasPrefix(line, "─────") || strings.HasPrefix(line, "-----") {
163+
if startWarning != -1 && endWarning == -1 {
164+
endWarning = i
165+
}
166+
if startChangeOutput != -1 && endChangeOutput == -1 {
167+
endChangeOutput = i - 1
168+
}
161169
}
162170
if firstMatchLineIndex == -1 {
163171
if p.Pass.MatchString(line) || p.Fail.MatchString(line) {
@@ -201,6 +209,10 @@ func (p *PlanParser) Parse(body string) ParseResult { //nolint:cyclop
201209

202210
changeResult := ""
203211
if startChangeOutput != -1 {
212+
// if we get here before finding a horizontal rule, output all remaining.
213+
if endChangeOutput == -1 {
214+
endChangeOutput = len(lines) - 1
215+
}
204216
changeResult = strings.Join(lines[startChangeOutput:endChangeOutput], "\n")
205217
}
206218

@@ -214,7 +226,7 @@ func (p *PlanParser) Parse(body string) ParseResult { //nolint:cyclop
214226
}
215227

216228
return ParseResult{
217-
Result: result,
229+
Result: refinePlanResult(result),
218230
ChangedResult: changeResult,
219231
OutsideTerraform: outsideTerraform,
220232
Warning: warnings,
@@ -233,6 +245,15 @@ func (p *PlanParser) Parse(body string) ParseResult { //nolint:cyclop
233245
}
234246
}
235247

248+
// It can be difficult to understand if we just cut out a part of
249+
// Terraform's output, so rewrite the text in a way that users can understand.
250+
func refinePlanResult(s string) string {
251+
if s == "Changes to Outputs:" {
252+
return "Only Outputs will be changed."
253+
}
254+
return s
255+
}
256+
236257
type MovedResource struct {
237258
Before string
238259
After string

pkg/terraform/parser_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,69 @@ can't guarantee that exactly these actions will be performed if
5252
"terraform apply" is subsequently run.
5353
`
5454

55+
const planOnlyOutputChangesSuccessResult0_12 = `
56+
Refreshing Terraform state in-memory prior to plan...
57+
The refreshed state will be used to calculate this plan, but will not be
58+
persisted to local or remote state storage.
59+
60+
data.terraform_remote_state.teams_platform_development: Refreshing state...
61+
google_project.my_project: Refreshing state...
62+
aws_iam_policy.datadog_aws_integration: Refreshing state...
63+
aws_iam_user.teams_terraform: Refreshing state...
64+
aws_iam_role.datadog_aws_integration: Refreshing state...
65+
google_project_services.my_project: Refreshing state...
66+
google_bigquery_dataset.gateway_access_log: Refreshing state...
67+
aws_iam_role_policy_attachment.datadog_aws_integration: Refreshing state...
68+
google_logging_project_sink.gateway_access_log_bigquery_sink: Refreshing state...
69+
google_project_iam_member.gateway_access_log_bigquery_sink_writer_is_bigquery_data_editor: Refreshing state...
70+
google_dns_managed_zone.tfnotifyapps_com: Refreshing state...
71+
google_dns_record_set.dev_tfnotifyapps_com: Refreshing state...
72+
73+
------------------------------------------------------------------------
74+
75+
An execution plan has been generated and is shown below.
76+
Resource actions are indicated with the following symbols:
77+
+ create
78+
79+
Terraform will perform the following actions:
80+
81+
Plan: 0 to add, 0 to change, 0 to destroy.
82+
83+
Changes to Outputs:
84+
+ aws_instance_name = "my-instance"
85+
86+
------------------------------------------------------------------------
87+
88+
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
89+
can't guarantee that exactly these actions will be performed if
90+
"terraform apply" is subsequently run.
91+
`
92+
93+
const planOnlyOutputChangesSuccessResult0_15 = `
94+
null_resource.this: Refreshing state... [id=6068603774747257119]
95+
96+
Changes to Outputs:
97+
+ test = 42
98+
99+
You can apply this plan to save these new output values to the Terraform
100+
state, without changing any real infrastructure.
101+
102+
─────────────────────────────────────────────────────────────────────────────
103+
104+
Note: You didn't use the -out option to save this plan, so Terraform can't
105+
guarantee to take exactly these actions if you run "terraform apply" now.
106+
`
107+
108+
const planOnlyOutputChangesSuccessInAutomationResult = `
109+
null_resource.this: Refreshing state... [id=6068603774747257119]
110+
111+
Changes to Outputs:
112+
+ test = 42
113+
114+
You can apply this plan to save these new output values to the Terraform
115+
state, without changing any real infrastructure.
116+
`
117+
55118
const planFailureResult = `
56119
xxxxxxxxx
57120
xxxxxxxxx
@@ -351,6 +414,60 @@ func TestPlanParserParse(t *testing.T) {
351414
Plan: 1 to add, 0 to change, 0 to destroy.`,
352415
},
353416
},
417+
{
418+
name: "plan output changes only pattern 0.12",
419+
body: planOnlyOutputChangesSuccessResult0_12,
420+
result: ParseResult{
421+
Result: "Plan: 0 to add, 0 to change, 0 to destroy.",
422+
HasAddOrUpdateOnly: true,
423+
HasDestroy: false,
424+
HasNoChanges: false,
425+
HasPlanError: false,
426+
ExitCode: 0,
427+
Error: nil,
428+
ChangedResult: `
429+
Plan: 0 to add, 0 to change, 0 to destroy.
430+
431+
Changes to Outputs:
432+
+ aws_instance_name = "my-instance"`,
433+
},
434+
},
435+
{
436+
name: "plan output changes only pattern 0.15",
437+
body: planOnlyOutputChangesSuccessResult0_15,
438+
result: ParseResult{
439+
Result: "Only Outputs will be changed.",
440+
HasAddOrUpdateOnly: true,
441+
HasDestroy: false,
442+
HasNoChanges: false,
443+
HasPlanError: false,
444+
ExitCode: 0,
445+
Error: nil,
446+
ChangedResult: `Changes to Outputs:
447+
+ test = 42
448+
449+
You can apply this plan to save these new output values to the Terraform
450+
state, without changing any real infrastructure.`,
451+
},
452+
},
453+
{
454+
name: "plan output changes only pattern with TF_IN_AUTOMATION",
455+
body: planOnlyOutputChangesSuccessInAutomationResult,
456+
result: ParseResult{
457+
Result: "Only Outputs will be changed.",
458+
HasAddOrUpdateOnly: true,
459+
HasDestroy: false,
460+
HasNoChanges: false,
461+
HasPlanError: false,
462+
ExitCode: 0,
463+
Error: nil,
464+
ChangedResult: `Changes to Outputs:
465+
+ test = 42
466+
467+
You can apply this plan to save these new output values to the Terraform
468+
state, without changing any real infrastructure.`,
469+
},
470+
},
354471
{
355472
name: "no stdin",
356473
body: "",

0 commit comments

Comments
 (0)