Skip to content

Commit

Permalink
implement better handling for terraform errors
Browse files Browse the repository at this point in the history
  • Loading branch information
djelusic committed Nov 9, 2021
1 parent 6244c32 commit 820f7f5
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 43 deletions.
2 changes: 1 addition & 1 deletion cli/ui/progress/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (p *Terraform) initProgress() {
pes = append(pes, p.counter)
}
pes = append(pes, NewDots())
p.progress = New(p.parser.Output(), LogFunc, pes...)
p.progress = New(p.parser.StateLabel(), LogFunc, pes...)
p.progress.Run()
}

Expand Down
11 changes: 7 additions & 4 deletions node/api/setup/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,13 @@ func (s *Setup) terraformCreate(req *dto.SetupRequest) (*dto.SetupResponse, erro
if err := tf.Create(); err != nil {
return nil, err
}
url := tf.Outputs["url"]
cliRole := tf.Outputs["cli_role"]
if url == "" {
return nil, fmt.Errorf("can't find terraform output in %#v", tf.Outputs)
url, err := tf.Output("url")
if err != nil {
return nil, err
}
cliRole, err := tf.Output("cli_role")
if err != nil {
return nil, err
}
return &dto.SetupResponse{
APIGatewayRestURL: url,
Expand Down
39 changes: 27 additions & 12 deletions node/terraform/log_parser.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package terraform

import (
"fmt"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -34,9 +35,11 @@ const (
)

type Parser struct {
Outputs map[string]string
counter *resourceCounter
state ParserState
Outputs map[string]string
counter *resourceCounter
state ParserState
collectingError bool
errorMessage string
}

// NewLogParser creates terraform log parser.
Expand All @@ -59,6 +62,18 @@ func (p *Parser) Parse(line string) bool {
return true
}
matchers := []func(string) bool{
func(line string) bool {
if p.isError(line) {
p.collectingError = true
p.state = StateDone
}
if p.collectingError {

This comment has been minimized.

Copy link
@ianic

ianic Dec 9, 2021

Member

ovaj p.collectingError se moze izbaciti:

                       if p.isError(line) {
			        p.state = StateDone
				line = strings.TrimPrefix(line, logPrefix)
				p.errorMessage += line + "\n"
				return true
			}
			return false

ili u Go duhu:

                       if !p.isError(line) {
                           return false
                       }
	               p.state = StateDone
		       line = strings.TrimPrefix(line, logPrefix)
		       p.errorMessage += line + "\n"
		       return true

			
line = strings.Trim(line, logPrefix)
p.errorMessage += line + "\n"
return true
}
return false
},
func(line string) bool {
match := outputRegExp.FindStringSubmatch(line)
if len(match) == 3 {
Expand Down Expand Up @@ -125,13 +140,6 @@ func (p *Parser) Parse(line string) bool {
}
return false
},
func(line string) bool {
if p.isError(line) {
p.state = StateDone
return true
}
return false
},
}
for _, m := range matchers {
if updated := m(line); updated {
Expand All @@ -141,7 +149,7 @@ func (p *Parser) Parse(line string) bool {
return true
}

func (p *Parser) Output() string {
func (p *Parser) StateLabel() string {
switch p.state {
case StateInitial:
return ""
Expand All @@ -161,6 +169,13 @@ func (p *Parser) State() ParserState {
return p.state
}

func (p *Parser) Error() error {
if p.errorMessage == "" {
return nil
}
return fmt.Errorf(p.errorMessage)
}

func (p *Parser) TotalResourceCount() int {
if p.counter == nil {
return 0
Expand All @@ -180,7 +195,7 @@ func (p *Parser) isApplying() bool {
}

func (p *Parser) isError(line string) bool {
if strings.HasPrefix(line, "TF: Error") {
if strings.Contains(line, "TF: Error") {
// skip api gateway conflict errors since we are handling them on the backend
return !strings.Contains(line, "ConflictException: Unable to complete operation due to concurrent modification. Please try again later.")
}
Expand Down
59 changes: 33 additions & 26 deletions node/terraform/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,15 @@ type Terraform struct {
destroyPath string
createContent []byte
destroyContent []byte
Outputs map[string]string
parser *Parser
}

func New(createPath, destroyPath string) *Terraform {
return &Terraform{
createPath: createPath,
destroyPath: destroyPath,
parser: NewLogParser(),
}
}

type SetupTemplateData struct {
Expand Down Expand Up @@ -115,7 +123,7 @@ func (t *Terraform) initPlanApply(destroy bool) error {
func (t *Terraform) init() error {
if _, err := os.Stat(t.path + "/.terraform"); os.IsNotExist(err) { // only if .terraform folder not found
args := []string{"terraform", "init", "-no-color", "-input=false", "-migrate-state"}
return t.shellExec(args)
return t.shellExecDefault(args)
}
return nil
}
Expand All @@ -125,7 +133,7 @@ func (t *Terraform) plan(destroy bool) error {
if destroy {
args = append(args, "-destroy")
}
return t.shellExec(args)
return t.shellExecDefault(args)
}

func (t *Terraform) apply(destroy bool) error {
Expand All @@ -138,23 +146,19 @@ func (t *Terraform) apply(destroy bool) error {
opt.ErrorsMap = map[string]error{
"ConflictException: Unable to complete operation due to concurrent modification. Please try again later.": conflictException,
}
return shell.Exec(opt)
return t.shellExec(opt)
}

func (t *Terraform) output() error {
args := []string{"terraform", "output", "-no-color"}
return shell.Exec(t.shellExecOpts(outputLogPrefix, args))
return t.shellExec(t.shellExecOpts(outputLogPrefix, args))
}

/////////////// shell exec

var conflictException = fmt.Errorf("ConflictException")

func (t *Terraform) shellExecOpts(logPrefix string, args []string) shell.ExecOptions {
findOutput := args[1] == "output"
if findOutput {
t.Outputs = make(map[string]string)
}
opt := shell.ExecOptions{
Args: args,
WorkDir: t.path,
Expand All @@ -163,13 +167,8 @@ func (t *Terraform) shellExecOpts(logPrefix string, args []string) shell.ExecOpt
Logger: func(format string, v ...interface{}) {
format = logPrefix + format
line := fmt.Sprintf(format, v...)
t.parser.Parse(line)
stdlog.Print(line)
if findOutput {
match := outputRegExp.FindStringSubmatch(line)
if len(match) == 3 {
t.Outputs[match[1]] = match[2]
}
}
},
}
if p := aws.TestProfile(); p != "" {
Expand All @@ -178,18 +177,26 @@ func (t *Terraform) shellExecOpts(logPrefix string, args []string) shell.ExecOpt
return opt
}

func (t *Terraform) shellExec(args []string) error {
return shell.Exec(t.shellExecOpts(logPrefix, args))
func (t *Terraform) shellExecDefault(args []string) error {
return t.shellExec(t.shellExecOpts(logPrefix, args))
}

func (t *Terraform) shellExec(opts shell.ExecOptions) error {
err := shell.Exec(opts)
if perr := t.parser.Error(); perr != nil {
return perr
}
return err
}

/////////////// rendering templates

func renderProject(data dto.StageTemplate) (*Terraform, error) {
stageDir := fmt.Sprintf("%s-%s", data.Project, data.Stage)
t := &Terraform{
createPath: path.Join(rootPath, stageDir, createDir),
destroyPath: path.Join(rootPath, stageDir, destroyDir),
}
t := New(
path.Join(rootPath, stageDir, createDir),
path.Join(rootPath, stageDir, destroyDir),
)
var err error
if t.createContent, err = t.render(projectTemplateName, t.createPath, data); err != nil {
return nil, err
Expand All @@ -202,10 +209,10 @@ func renderProject(data dto.StageTemplate) (*Terraform, error) {

func renderSetup(data SetupTemplateData) (*Terraform, error) {
data.BucketPrefix = setupBucketPrefix
t := &Terraform{
createPath: path.Join(rootPath, setupBucketPrefix, createDir),
destroyPath: path.Join(rootPath, setupBucketPrefix, destroyDir),
}
t := New(
path.Join(rootPath, setupBucketPrefix, createDir),
path.Join(rootPath, setupBucketPrefix, destroyDir),
)
var err error
if t.createContent, err = t.render(setupTemplateName, t.createPath, data); err != nil {
return nil, err
Expand Down Expand Up @@ -271,7 +278,7 @@ func (t *Terraform) render(name string, pth string, data interface{}) ([]byte, e
}

func (t *Terraform) Output(key string) (string, error) {
val, ok := t.Outputs[key]
val, ok := t.parser.Outputs[key]
if !ok {
return "", fmt.Errorf("output variable %s not found", key)
}
Expand Down

0 comments on commit 820f7f5

Please sign in to comment.