From 031a1eba4f576dec1bc8d71e7958c09b8e726bbb Mon Sep 17 00:00:00 2001 From: Luke Kysow Date: Thu, 19 Oct 2017 07:43:10 -0700 Subject: [PATCH] Refactor plan/apply into ProjectPreExecute --- server/events/apply_executor.go | 63 ++++---------------- server/events/plan_executor.go | 69 ++++------------------ server/events/project_pre_execute.go | 88 ++++++++++++++++++++++++++++ server/server.go | 13 ++-- 4 files changed, 120 insertions(+), 113 deletions(-) create mode 100644 server/events/project_pre_execute.go diff --git a/server/events/apply_executor.go b/server/events/apply_executor.go index 4ed256d2f..baa41c0e3 100644 --- a/server/events/apply_executor.go +++ b/server/events/apply_executor.go @@ -8,9 +8,7 @@ import ( "path/filepath" - version "github.com/hashicorp/go-version" "github.com/hootsuite/atlantis/server/events/github" - "github.com/hootsuite/atlantis/server/events/locking" "github.com/hootsuite/atlantis/server/events/models" "github.com/hootsuite/atlantis/server/events/run" "github.com/hootsuite/atlantis/server/events/terraform" @@ -19,11 +17,10 @@ import ( type ApplyExecutor struct { Github github.Client Terraform *terraform.Client - Locker locking.Locker RequireApproval bool Run *run.Run - ConfigReader *ConfigReader Workspace Workspace + ProjectPreExecute *ProjectPreExecute } func (a *ApplyExecutor) Execute(ctx *CommandContext) CommandResponse { @@ -80,63 +77,25 @@ func (a *ApplyExecutor) Execute(ctx *CommandContext) CommandResponse { } func (a *ApplyExecutor) apply(ctx *CommandContext, repoDir string, plan models.Plan) ProjectResult { - tfEnv := ctx.Command.Environment - lockAttempt, err := a.Locker.TryLock(plan.Project, tfEnv, ctx.Pull, ctx.User) - if err != nil { - return ProjectResult{Error: errors.Wrap(err, "acquiring lock")} - } - if lockAttempt.LockAcquired != true && lockAttempt.CurrLock.Pull.Num != ctx.Pull.Num { - return ProjectResult{Failure: fmt.Sprintf( - "This project is currently locked by #%d. The locking plan must be applied or discarded before future plans can execute.", - lockAttempt.CurrLock.Pull.Num)} - } - ctx.Log.Info("acquired lock with id %q", lockAttempt.LockKey) - - // check if config file is found, if not we continue the run - absolutePath := filepath.Dir(plan.LocalPath) - var applyExtraArgs []string - var config ProjectConfig - if a.ConfigReader.Exists(absolutePath) { - config, err = a.ConfigReader.Read(absolutePath) - if err != nil { - return ProjectResult{Error: err} - } - ctx.Log.Info("parsed atlantis config file in %q", absolutePath) - applyExtraArgs = config.GetExtraArguments(ctx.Command.Name.String()) - } - - // check if terraform version is >= 0.9.0 - terraformVersion := a.Terraform.Version() - if config.TerraformVersion != nil { - terraformVersion = config.TerraformVersion - } - constraints, _ := version.NewConstraint(">= 0.9.0") - if constraints.Check(terraformVersion) { - ctx.Log.Info("determined that we are running terraform with version >= 0.9.0. Running version %s", terraformVersion) - _, err := a.Terraform.RunInitAndEnv(ctx.Log, absolutePath, tfEnv, config.GetExtraArguments("init"), terraformVersion) - if err != nil { - return ProjectResult{Error: err} - } - } - - // if there are pre apply commands then run them - if len(config.PreApply.Commands) > 0 { - _, err := a.Run.Execute(ctx.Log, config.PreApply.Commands, absolutePath, tfEnv, terraformVersion, "pre_apply") - if err != nil { - return ProjectResult{Error: errors.Wrap(err, "running pre apply commands")} - } + preExecute := a.ProjectPreExecute.Execute(ctx, repoDir, plan.Project) + if preExecute.ProjectResult != (ProjectResult{}) { + return preExecute.ProjectResult } + config := preExecute.ProjectConfig + terraformVersion := preExecute.TerraformVersion + applyExtraArgs := config.GetExtraArguments(ctx.Command.Name.String()) + absolutePath := filepath.Join(repoDir, plan.Project.Path) + env := ctx.Command.Environment tfApplyCmd := append(append(append([]string{"apply", "-no-color"}, applyExtraArgs...), ctx.Command.Flags...), plan.LocalPath) - output, err := a.Terraform.RunCommandWithVersion(ctx.Log, absolutePath, tfApplyCmd, terraformVersion, tfEnv) + output, err := a.Terraform.RunCommandWithVersion(ctx.Log, absolutePath, tfApplyCmd, terraformVersion, env) if err != nil { return ProjectResult{Error: fmt.Errorf("%s\n%s", err.Error(), output)} } ctx.Log.Info("apply succeeded") - // if there are post apply commands then run them if len(config.PostApply.Commands) > 0 { - _, err := a.Run.Execute(ctx.Log, config.PostApply.Commands, absolutePath, tfEnv, terraformVersion, "post_apply") + _, err := a.Run.Execute(ctx.Log, config.PostApply.Commands, absolutePath, env, terraformVersion, "post_apply") if err != nil { return ProjectResult{Error: errors.Wrap(err, "running post apply commands")} } diff --git a/server/events/plan_executor.go b/server/events/plan_executor.go index c3c30af3f..d5c16188e 100644 --- a/server/events/plan_executor.go +++ b/server/events/plan_executor.go @@ -7,13 +7,12 @@ import ( "path/filepath" "strings" - version "github.com/hashicorp/go-version" "github.com/hootsuite/atlantis/server/events/github" - "github.com/hootsuite/atlantis/server/events/locking" "github.com/hootsuite/atlantis/server/events/models" "github.com/hootsuite/atlantis/server/events/run" "github.com/hootsuite/atlantis/server/events/terraform" "github.com/pkg/errors" + "github.com/hootsuite/atlantis/server/events/locking" ) //go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_lock_url_generator.go LockURLGenerator @@ -36,8 +35,8 @@ type PlanExecutor struct { Locker locking.Locker LockURL func(id string) (url string) Run *run.Run - ConfigReader *ConfigReader Workspace Workspace + ProjectPreExecute *ProjectPreExecute } type PlanSuccess struct { @@ -88,63 +87,18 @@ func (p *PlanExecutor) Execute(ctx *CommandContext) CommandResponse { // plan runs the steps necessary to run `terraform plan`. If there is an error, the error message will be encapsulated in error // and the GeneratePlanResponse struct will also contain the full log including the error func (p *PlanExecutor) plan(ctx *CommandContext, repoDir string, project models.Project) ProjectResult { + preExecute := p.ProjectPreExecute.Execute(ctx, repoDir, project) + if preExecute.ProjectResult != (ProjectResult{}) { + return preExecute.ProjectResult + } + config := preExecute.ProjectConfig + terraformVersion := preExecute.TerraformVersion tfEnv := ctx.Command.Environment - lockAttempt, err := p.Locker.TryLock(project, tfEnv, ctx.Pull, ctx.User) - if err != nil { - return ProjectResult{Error: errors.Wrap(err, "acquiring lock")} - } - if lockAttempt.LockAcquired == false && lockAttempt.CurrLock.Pull.Num != ctx.Pull.Num { - return ProjectResult{Failure: fmt.Sprintf( - "This project is currently locked by #%d. The locking plan must be applied or discarded before future plans can execute.", - lockAttempt.CurrLock.Pull.Num)} - } - ctx.Log.Info("acquired lock with id %q", lockAttempt.LockKey) - - // check if config file is found, if not we continue the run - var config ProjectConfig - absolutePath := filepath.Join(repoDir, project.Path) - var planExtraArgs []string - if p.ConfigReader.Exists(absolutePath) { - config, err = p.ConfigReader.Read(absolutePath) - if err != nil { - return ProjectResult{Error: err} - } - ctx.Log.Info("parsed atlantis config file in %q", absolutePath) - planExtraArgs = config.GetExtraArguments(ctx.Command.Name.String()) - } - - // check if terraform version is >= 0.9.0 - terraformVersion := p.Terraform.Version() - if config.TerraformVersion != nil { - terraformVersion = config.TerraformVersion - } - constraints, _ := version.NewConstraint(">= 0.9.0") - if constraints.Check(terraformVersion) { - ctx.Log.Info("determined that we are running terraform with version >= 0.9.0. Running version %s", terraformVersion) - _, err := p.Terraform.RunInitAndEnv(ctx.Log, absolutePath, tfEnv, config.GetExtraArguments("init"), terraformVersion) - if err != nil { - return ProjectResult{Error: err} - } - } else { - ctx.Log.Info("determined that we are running terraform with version < 0.9.0. Running version %s", terraformVersion) - terraformGetCmd := append([]string{"get", "-no-color"}, config.GetExtraArguments("get")...) - _, err := p.Terraform.RunCommandWithVersion(ctx.Log, absolutePath, terraformGetCmd, terraformVersion, tfEnv) - if err != nil { - return ProjectResult{Error: err} - } - } - - // if there are pre plan commands then run them - if len(config.PrePlan.Commands) > 0 { - _, err := p.Run.Execute(ctx.Log, config.PrePlan.Commands, absolutePath, tfEnv, terraformVersion, "pre_plan") - if err != nil { - return ProjectResult{Error: errors.Wrap(err, "running pre plan commands")} - } - } // Run terraform plan planFile := filepath.Join(repoDir, project.Path, fmt.Sprintf("%s.tfplan", tfEnv)) userVar := fmt.Sprintf("%s=%s", atlantisUserTFVar, ctx.User.Username) + planExtraArgs := config.GetExtraArguments(ctx.Command.Name.String()) tfPlanCmd := append(append([]string{"plan", "-refresh", "-no-color", "-out", planFile, "-var", userVar}, planExtraArgs...), ctx.Command.Flags...) // check if env/{environment}.tfvars exist @@ -155,7 +109,7 @@ func (p *PlanExecutor) plan(ctx *CommandContext, repoDir string, project models. output, err := p.Terraform.RunCommandWithVersion(ctx.Log, filepath.Join(repoDir, project.Path), tfPlanCmd, terraformVersion, tfEnv) if err != nil { // plan failed so unlock the state - if _, err := p.Locker.Unlock(lockAttempt.LockKey); err != nil { + if _, err := p.Locker.Unlock(preExecute.LockResponse.LockKey); err != nil { ctx.Log.Err("error unlocking state: %v", err) } return ProjectResult{Error: fmt.Errorf("%s\n%s", err.Error(), output)} @@ -164,6 +118,7 @@ func (p *PlanExecutor) plan(ctx *CommandContext, repoDir string, project models. // if there are post plan commands then run them if len(config.PostPlan.Commands) > 0 { + absolutePath := filepath.Join(repoDir, project.Path) _, err := p.Run.Execute(ctx.Log, config.PostPlan.Commands, absolutePath, tfEnv, terraformVersion, "post_plan") if err != nil { return ProjectResult{Error: errors.Wrap(err, "running post plan commands")} @@ -173,7 +128,7 @@ func (p *PlanExecutor) plan(ctx *CommandContext, repoDir string, project models. return ProjectResult{ PlanSuccess: &PlanSuccess{ TerraformOutput: output, - LockURL: p.LockURL(lockAttempt.LockKey), + LockURL: p.LockURL(preExecute.LockResponse.LockKey), }, } } diff --git a/server/events/project_pre_execute.go b/server/events/project_pre_execute.go new file mode 100644 index 000000000..ec85427d0 --- /dev/null +++ b/server/events/project_pre_execute.go @@ -0,0 +1,88 @@ +package events + +import ( + "path/filepath" + "fmt" + "github.com/hootsuite/atlantis/server/events/locking" + "github.com/pkg/errors" + "github.com/hootsuite/atlantis/server/events/models" + "github.com/hootsuite/atlantis/server/events/terraform" + "github.com/hashicorp/go-version" + "github.com/hootsuite/atlantis/server/events/run" + "strings" +) + +type ProjectPreExecute struct { + Locker locking.Locker + ConfigReader *ConfigReader + Terraform *terraform.Client + Run *run.Run +} + +type PreExecuteResult struct { + ProjectResult ProjectResult + ProjectConfig ProjectConfig + TerraformVersion *version.Version + LockResponse locking.TryLockResponse +} + +func (p *ProjectPreExecute) Execute(ctx *CommandContext, repoDir string, project models.Project) PreExecuteResult { + tfEnv := ctx.Command.Environment + lockAttempt, err := p.Locker.TryLock(project, tfEnv, ctx.Pull, ctx.User) + if err != nil { + return PreExecuteResult{ProjectResult: ProjectResult{Error: errors.Wrap(err, "acquiring lock")}} + } + if lockAttempt.LockAcquired == false && lockAttempt.CurrLock.Pull.Num != ctx.Pull.Num { + return PreExecuteResult{ProjectResult: ProjectResult{Failure: fmt.Sprintf( + "This project is currently locked by #%d. The locking plan must be applied or discarded before future plans can execute.", + lockAttempt.CurrLock.Pull.Num)}} + } + ctx.Log.Info("acquired lock with id %q", lockAttempt.LockKey) + + // check if config file is found, if not we continue the run + var config ProjectConfig + absolutePath := filepath.Join(repoDir, project.Path) + if p.ConfigReader.Exists(absolutePath) { + config, err = p.ConfigReader.Read(absolutePath) + if err != nil { + return PreExecuteResult{ProjectResult: ProjectResult{Error: err}} + } + ctx.Log.Info("parsed atlantis config file in %q", absolutePath) + } + + // check if terraform version is >= 0.9.0 + terraformVersion := p.Terraform.Version() + if config.TerraformVersion != nil { + terraformVersion = config.TerraformVersion + } + constraints, _ := version.NewConstraint(">= 0.9.0") + if constraints.Check(terraformVersion) { + ctx.Log.Info("determined that we are running terraform with version >= 0.9.0. Running version %s", terraformVersion) + _, err := p.Terraform.RunInitAndEnv(ctx.Log, absolutePath, tfEnv, config.GetExtraArguments("init"), terraformVersion) + if err != nil { + return PreExecuteResult{ProjectResult: ProjectResult{Error: err}} + } + } else { + ctx.Log.Info("determined that we are running terraform with version < 0.9.0. Running version %s", terraformVersion) + terraformGetCmd := append([]string{"get", "-no-color"}, config.GetExtraArguments("get")...) + _, err := p.Terraform.RunCommandWithVersion(ctx.Log, absolutePath, terraformGetCmd, terraformVersion, tfEnv) + if err != nil { + return PreExecuteResult{ProjectResult: ProjectResult{Error: err}} + } + } + + stage := fmt.Sprintf("pre_%s", strings.ToLower(ctx.Command.Name.String())) + var commands []string + if ctx.Command.Name == Plan { + commands = config.PrePlan.Commands + } else { + commands = config.PreApply.Commands + } + if len(commands) > 0 { + _, err := p.Run.Execute(ctx.Log, commands, absolutePath, tfEnv, terraformVersion, stage) + if err != nil { + return PreExecuteResult{ProjectResult: ProjectResult{Error: errors.Wrapf(err, "running pre %s commands", stage)}} + } + } + return PreExecuteResult{ProjectConfig: config, TerraformVersion: terraformVersion, LockResponse: lockAttempt} +} diff --git a/server/server.go b/server/server.go index 5e1f14afb..08cbc90a1 100644 --- a/server/server.go +++ b/server/server.go @@ -87,22 +87,27 @@ func NewServer(config ServerConfig) (*Server, error) { workspace := &events.FileWorkspace{ DataDir: config.DataDir, } + projectPreExecute := &events.ProjectPreExecute{ + Locker: lockingClient, + Run: run, + ConfigReader: configReader, + Terraform: terraformClient, + } applyExecutor := &events.ApplyExecutor{ Github: githubClient, Terraform: terraformClient, - Locker: lockingClient, RequireApproval: config.RequireApproval, Run: run, - ConfigReader: configReader, Workspace: workspace, + ProjectPreExecute: projectPreExecute, } planExecutor := &events.PlanExecutor{ Github: githubClient, Terraform: terraformClient, - Locker: lockingClient, Run: run, - ConfigReader: configReader, Workspace: workspace, + ProjectPreExecute: projectPreExecute, + Locker: lockingClient, } helpExecutor := &events.HelpExecutor{} pullClosedExecutor := &events.PullClosedExecutor{