Skip to content

Commit

Permalink
Make terraform cloud provider able to parse log including ansi codes (#…
Browse files Browse the repository at this point in the history
…2189)

**What this PR does / why we need it**:

**Which issue(s) this PR fixes**:

Fixes #674

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
-->
```release-note
NONE
```

This PR was merged by Kapetanios.
  • Loading branch information
nghialv authored Jul 7, 2021
1 parent 56ffd10 commit 9658d3a
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 32 deletions.
93 changes: 66 additions & 27 deletions pkg/app/piped/cloudprovider/terraform/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,49 @@ import (
"strings"
)

type options struct {
noColor bool
vars []string
varFiles []string
}

type Option func(*options)

func WithoutColor() Option {
return func(opts *options) {
opts.noColor = true
}
}

func WithVars(vars []string) Option {
return func(opts *options) {
opts.vars = vars
}
}

func WithVarFiles(files []string) Option {
return func(opts *options) {
opts.varFiles = files
}
}

type Terraform struct {
execPath string
dir string
vars []string
varFiles []string

options options
}

func NewTerraform(execPath, dir string, vars, varFiles []string) *Terraform {
func NewTerraform(execPath, dir string, opts ...Option) *Terraform {
opt := options{}
for _, o := range opts {
o(&opt)
}

return &Terraform{
execPath: execPath,
dir: dir,
vars: vars,
varFiles: varFiles,
options: opt,
}
}

Expand All @@ -58,12 +88,7 @@ func (t *Terraform) Init(ctx context.Context, w io.Writer) error {
args := []string{
"init",
}
for _, v := range t.vars {
args = append(args, fmt.Sprintf("-var=%s", v))
}
for _, f := range t.varFiles {
args = append(args, fmt.Sprintf("-var-file=%s", f))
}
args = append(args, t.makeCommonCommandArgs()...)

cmd := exec.CommandContext(ctx, t.execPath, args...)
cmd.Dir = t.dir
Expand Down Expand Up @@ -114,17 +139,10 @@ func GetExitCode(err error) int {
func (t *Terraform) Plan(ctx context.Context, w io.Writer) (PlanResult, error) {
args := []string{
"plan",
// TODO: Remove this -no-color flag after parsePlanResult supports parsing the message containing color codes.
"-no-color",
"-lock=false",
"-detailed-exitcode",
}
for _, v := range t.vars {
args = append(args, fmt.Sprintf("-var=%s", v))
}
for _, f := range t.varFiles {
args = append(args, fmt.Sprintf("-var-file=%s", f))
}
args = append(args, t.makeCommonCommandArgs()...)

var buf bytes.Buffer
stdout := io.MultiWriter(w, &buf)
Expand All @@ -140,18 +158,40 @@ func (t *Terraform) Plan(ctx context.Context, w io.Writer) (PlanResult, error) {
case 0:
return PlanResult{}, nil
case 2:
return parsePlanResult(buf.String())
return parsePlanResult(buf.String(), !t.options.noColor)
default:
return PlanResult{}, err
}
}

func (t *Terraform) makeCommonCommandArgs() (args []string) {
if t.options.noColor {
args = append(args, "-no-color")
}
for _, v := range t.options.vars {
args = append(args, fmt.Sprintf("-var=%s", v))
}
for _, f := range t.options.varFiles {
args = append(args, fmt.Sprintf("-var-file=%s", f))
}
return
}

var (
planHasChangeRegex = regexp.MustCompile(`(?m)^Plan: (\d+) to add, (\d+) to change, (\d+) to destroy.$`)
planNoChangesRegex = regexp.MustCompile(`(?m)^No changes. Infrastructure is up-to-date.$`)
)

func parsePlanResult(out string) (PlanResult, error) {
// Borrowed from https://github.com/acarl005/stripansi
const ansi = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"

var ansiRegex = regexp.MustCompile(ansi)

func stripAnsiCodes(str string) string {
return ansiRegex.ReplaceAllString(str, "")
}

func parsePlanResult(out string, ansiIncluded bool) (PlanResult, error) {
parseNums := func(add, change, destroy string) (adds int, changes int, destroys int, err error) {
adds, err = strconv.Atoi(add)
if err != nil {
Expand All @@ -168,6 +208,10 @@ func parsePlanResult(out string) (PlanResult, error) {
return
}

if ansiIncluded {
out = stripAnsiCodes(out)
}

if s := planHasChangeRegex.FindStringSubmatch(out); len(s) == 4 {
adds, changes, destroys, err := parseNums(s[1], s[2], s[3])
if err == nil {
Expand All @@ -192,12 +236,7 @@ func (t *Terraform) Apply(ctx context.Context, w io.Writer) error {
"-auto-approve",
"-input=false",
}
for _, v := range t.vars {
args = append(args, fmt.Sprintf("-var=%s", v))
}
for _, f := range t.varFiles {
args = append(args, fmt.Sprintf("-var-file=%s", f))
}
args = append(args, t.makeCommonCommandArgs()...)

cmd := exec.CommandContext(ctx, t.execPath, args...)
cmd.Dir = t.dir
Expand Down
21 changes: 18 additions & 3 deletions pkg/app/piped/executor/terraform/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@ func (e *deployExecutor) Execute(sig executor.StopSignal) model.StageStatus {
}

func (e *deployExecutor) ensureSync(ctx context.Context) model.StageStatus {
cmd := provider.NewTerraform(e.terraformPath, e.appDir, e.vars, e.deployCfg.Input.VarFiles)
cmd := provider.NewTerraform(
e.terraformPath,
e.appDir,
provider.WithVars(e.vars),
provider.WithVarFiles(e.deployCfg.Input.VarFiles),
)

if ok := showUsingVersion(ctx, cmd, e.LogPersister); !ok {
return model.StageStatus_STAGE_FAILURE
Expand Down Expand Up @@ -127,7 +132,12 @@ func (e *deployExecutor) ensureSync(ctx context.Context) model.StageStatus {
}

func (e *deployExecutor) ensurePlan(ctx context.Context) model.StageStatus {
cmd := provider.NewTerraform(e.terraformPath, e.appDir, e.vars, e.deployCfg.Input.VarFiles)
cmd := provider.NewTerraform(
e.terraformPath,
e.appDir,
provider.WithVars(e.vars),
provider.WithVarFiles(e.deployCfg.Input.VarFiles),
)

if ok := showUsingVersion(ctx, cmd, e.LogPersister); !ok {
return model.StageStatus_STAGE_FAILURE
Expand Down Expand Up @@ -158,7 +168,12 @@ func (e *deployExecutor) ensurePlan(ctx context.Context) model.StageStatus {
}

func (e *deployExecutor) ensureApply(ctx context.Context) model.StageStatus {
cmd := provider.NewTerraform(e.terraformPath, e.appDir, e.vars, e.deployCfg.Input.VarFiles)
cmd := provider.NewTerraform(
e.terraformPath,
e.appDir,
provider.WithVars(e.vars),
provider.WithVarFiles(e.deployCfg.Input.VarFiles),
)

if ok := showUsingVersion(ctx, cmd, e.LogPersister); !ok {
return model.StageStatus_STAGE_FAILURE
Expand Down
7 changes: 6 additions & 1 deletion pkg/app/piped/executor/terraform/rollback.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,12 @@ func (e *rollbackExecutor) ensureRollback(ctx context.Context) model.StageStatus
vars = append(vars, deployCfg.Input.Vars...)

e.LogPersister.Infof("Start rolling back to the state defined at commit %s", e.Deployment.RunningCommitHash)
cmd := provider.NewTerraform(terraformPath, ds.AppDir, vars, deployCfg.Input.VarFiles)
cmd := provider.NewTerraform(
terraformPath,
ds.AppDir,
provider.WithVars(vars),
provider.WithVarFiles(deployCfg.Input.VarFiles),
)

if ok := showUsingVersion(ctx, cmd, e.LogPersister); !ok {
return model.StageStatus_STAGE_FAILURE
Expand Down
8 changes: 7 additions & 1 deletion pkg/app/piped/planpreview/terraformdiff.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,13 @@ func (b *builder) terraformDiff(
vars = append(vars, cpCfg.Vars...)
vars = append(vars, deployCfg.Input.Vars...)

executor := terraformprovider.NewTerraform(terraformPath, ds.AppDir, vars, deployCfg.Input.VarFiles)
executor := terraformprovider.NewTerraform(
terraformPath,
ds.AppDir,
terraformprovider.WithoutColor(),
terraformprovider.WithVars(vars),
terraformprovider.WithVarFiles(deployCfg.Input.VarFiles),
)

if err := executor.Init(ctx, buf); err != nil {
fmt.Fprintf(buf, "failed while executing terraform init (%v)\n", err)
Expand Down

0 comments on commit 9658d3a

Please sign in to comment.