From 94ab83c8beeaa4164d60df1627d4a3cbae107932 Mon Sep 17 00:00:00 2001 From: Han Zhou Date: Tue, 8 Oct 2019 00:28:19 +0900 Subject: [PATCH] feat(tf,plan): extract action part from terraform plan output. **Why** Sometimes we found the part about `Refreshing Terraform state in-memory prior to plan` has too many lines that we do not need to care about in the code review. So make this change can help us to focus on the action part that terraform plan is. --- README.md | 1 + terraform/parser.go | 20 ++++++++++++++++---- terraform/parser_test.go | 20 +++++++++++++++++--- terraform/template.go | 12 +++++++----- terraform/template_test.go | 11 +++++++++++ 5 files changed, 52 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index e53603f..ced1ea7 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ Placeholder | Usage ---|--- `{{ .Title }}` | Like `## Plan result` `{{ .Message }}` | A string that can be set from CLI with `--message` option +`{{ .Action }}` | Using in terraform plan, and matched leading message by parsing like `Terraform will perform the following actions:` `{{ .Result }}` | Matched result by parsing like `Plan: 1 to add` or `No changes` `{{ .Body }}` | The entire of Terraform execution result `{{ .Link }}` | The link of the build page on CI diff --git a/terraform/parser.go b/terraform/parser.go index 1c6926a..35e7f97 100644 --- a/terraform/parser.go +++ b/terraform/parser.go @@ -15,6 +15,7 @@ type Parser interface { type ParseResult struct { Result string HasDestroy bool + Action string ExitCode int Error error } @@ -32,6 +33,7 @@ type FmtParser struct { // PlanParser is a parser for terraform plan type PlanParser struct { Pass *regexp.Regexp + Action *regexp.Regexp Fail *regexp.Regexp HasDestroy *regexp.Regexp } @@ -57,8 +59,9 @@ func NewFmtParser() *FmtParser { // NewPlanParser is PlanParser initialized with its Regexp func NewPlanParser() *PlanParser { return &PlanParser{ - Pass: regexp.MustCompile(`(?m)^(Plan: \d|No changes.)`), - Fail: regexp.MustCompile(`(?m)^(Error: )`), + Pass: regexp.MustCompile(`(?m)^(Plan: \d|No changes.)`), + Action: regexp.MustCompile(`(?m)^(An execution plan has been generated)`), + Fail: regexp.MustCompile(`(?m)^(Error: )`), // "0 to destroy" should be treated as "no destroy" HasDestroy: regexp.MustCompile(`(?m)([1-9][0-9]* to destroy.)`), } @@ -107,9 +110,13 @@ func (p *PlanParser) Parse(body string) ParseResult { } } lines := strings.Split(body, "\n") - var i int - var result, line string + var i, actionStartIdx int + var result, action, line string for i, line = range lines { + if p.Action.MatchString(line) { + // action starts with the line: An execution plan... + actionStartIdx = i + } if p.Pass.MatchString(line) || p.Fail.MatchString(line) { break } @@ -117,6 +124,10 @@ func (p *PlanParser) Parse(body string) ParseResult { switch { case p.Pass.MatchString(line): result = lines[i] + if actionStartIdx != 0 { + // action ends with the line above summary + action = strings.Join(trimLastNewline(lines[actionStartIdx:i]), "\n") + } case p.Fail.MatchString(line): result = strings.Join(trimLastNewline(lines[i:]), "\n") } @@ -126,6 +137,7 @@ func (p *PlanParser) Parse(body string) ParseResult { return ParseResult{ Result: result, HasDestroy: hasDestroy, + Action: action, ExitCode: exitCode, Error: nil, } diff --git a/terraform/parser_test.go b/terraform/parser_test.go index c7f964e..882b510 100644 --- a/terraform/parser_test.go +++ b/terraform/parser_test.go @@ -35,7 +35,7 @@ versions.tf --- old/versions.tf +++ new/versions.tf @@ -1,4 +1,4 @@ - + terraform { - required_version = ">= 0.12" + required_version = ">= 0.12" @@ -309,8 +309,22 @@ func TestPlanParserParse(t *testing.T) { result: ParseResult{ Result: "Plan: 1 to add, 0 to change, 0 to destroy.", HasDestroy: false, - ExitCode: 0, - Error: nil, + Action: `An execution plan has been generated and is shown below. +Resource actions are indicated with the following symbols: + + create + +Terraform will perform the following actions: + + + google_compute_global_address.my_another_project + id: + address: + ip_version: "IPV4" + name: "my-another-project" + project: "my-project" + self_link: +`, + ExitCode: 0, + Error: nil, }, }, { diff --git a/terraform/template.go b/terraform/template.go index 490b946..d92a0f6 100644 --- a/terraform/template.go +++ b/terraform/template.go @@ -102,11 +102,12 @@ type Template interface { // CommonTemplate represents template entities type CommonTemplate struct { - Title string - Message string - Result string - Body string - Link string + Title string + Message string + Action string + Result string + Body string + Link string UseRawOutput bool } @@ -278,6 +279,7 @@ func (t *DestroyWarningTemplate) Execute() (string, error) { data := map[string]interface{}{ "Title": t.Title, "Message": t.Message, + "Action": t.Action, "Result": t.Result, "Body": t.Body, "Link": t.Link, diff --git a/terraform/template_test.go b/terraform/template_test.go index c947061..c0e0e0b 100644 --- a/terraform/template_test.go +++ b/terraform/template_test.go @@ -450,6 +450,17 @@ message }, resp: `a-b-c-d`, }, + { + template: `{{ .Title }}-{{ .Message }}-{{ .Action }}-{{ .Result }}-{{ .Body }}`, + value: CommonTemplate{ + Title: "a", + Message: "b", + Action: "action", + Result: "c", + Body: "d", + }, + resp: `a-b-action-c-d`, + }, } for _, testCase := range testCases { template := NewPlanTemplate(testCase.template)