mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-07-31 17:38:51 +00:00
187 lines
6.4 KiB
Go
187 lines
6.4 KiB
Go
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"path/filepath"
|
|
|
|
version "github.com/hashicorp/go-version"
|
|
"github.com/hootsuite/atlantis/aws"
|
|
"github.com/hootsuite/atlantis/github"
|
|
"github.com/hootsuite/atlantis/locking"
|
|
"github.com/hootsuite/atlantis/models"
|
|
"github.com/hootsuite/atlantis/prerun"
|
|
"github.com/hootsuite/atlantis/terraform"
|
|
)
|
|
|
|
type ApplyExecutor struct {
|
|
github *github.Client
|
|
githubStatus *GithubStatus
|
|
awsConfig *aws.Config
|
|
terraform *terraform.Client
|
|
githubCommentRenderer *GithubCommentRenderer
|
|
lockingClient *locking.Client
|
|
requireApproval bool
|
|
preRun *prerun.PreRun
|
|
configReader *ConfigReader
|
|
concurrentRunLocker *ConcurrentRunLocker
|
|
workspace *Workspace
|
|
}
|
|
|
|
func (a *ApplyExecutor) execute(ctx *CommandContext) {
|
|
a.githubStatus.Update(ctx.BaseRepo, ctx.Pull, Pending, ApplyStep)
|
|
res := a.setupAndApply(ctx)
|
|
res.Command = Apply
|
|
comment := a.githubCommentRenderer.render(res, ctx.Log.History.String(), ctx.Command.Verbose)
|
|
a.github.CreateComment(ctx.BaseRepo, ctx.Pull, comment)
|
|
}
|
|
|
|
func (a *ApplyExecutor) setupAndApply(ctx *CommandContext) CommandResponse {
|
|
if a.concurrentRunLocker.TryLock(ctx.BaseRepo.FullName, ctx.Command.Environment, ctx.Pull.Num) != true {
|
|
return a.failureResponse(ctx,
|
|
fmt.Sprintf("The %s environment is currently locked by another command that is running for this pull request. Wait until command is complete and try again.", ctx.Command.Environment))
|
|
}
|
|
defer a.concurrentRunLocker.Unlock(ctx.BaseRepo.FullName, ctx.Command.Environment, ctx.Pull.Num)
|
|
|
|
if a.requireApproval {
|
|
approved, err := a.github.PullIsApproved(ctx.BaseRepo, ctx.Pull)
|
|
if err != nil {
|
|
return a.errorResponse(ctx, errors.Wrap(err, "checking if pull request was approved"))
|
|
}
|
|
if !approved {
|
|
return a.failureResponse(ctx, "Pull request must be approved before running apply.")
|
|
}
|
|
}
|
|
|
|
repoDir, err := a.workspace.GetWorkspace(ctx)
|
|
if err != nil {
|
|
return a.failureResponse(ctx, "No workspace found. Did you run plan?")
|
|
}
|
|
|
|
// plans are stored at project roots by their environment names. We just need to find them
|
|
var plans []models.Plan
|
|
filepath.Walk(repoDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// if the plan is for the right env,
|
|
if !info.IsDir() && info.Name() == ctx.Command.Environment+".tfplan" {
|
|
rel, _ := filepath.Rel(repoDir, filepath.Dir(path))
|
|
plans = append(plans, models.Plan{
|
|
Project: models.NewProject(ctx.BaseRepo.FullName, rel),
|
|
LocalPath: path,
|
|
})
|
|
}
|
|
return nil
|
|
})
|
|
if len(plans) == 0 {
|
|
return a.failureResponse(ctx, "No plans found for that environment.")
|
|
}
|
|
|
|
results := []ProjectResult{}
|
|
for _, plan := range plans {
|
|
result := a.apply(ctx, repoDir, plan)
|
|
result.Path = plan.LocalPath
|
|
results = append(results, result)
|
|
}
|
|
a.githubStatus.UpdatePathResult(ctx, results)
|
|
return CommandResponse{ProjectResults: results}
|
|
}
|
|
|
|
func (a *ApplyExecutor) apply(ctx *CommandContext, repoDir string, plan models.Plan) ProjectResult {
|
|
tfEnv := ctx.Command.Environment
|
|
lockAttempt, err := a.lockingClient.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)}
|
|
}
|
|
|
|
// check if config file is found, if not we continue the run
|
|
projectAbsolutePath := filepath.Dir(plan.LocalPath)
|
|
var terraformApplyExtraArgs []string
|
|
var config ProjectConfig
|
|
if a.configReader.Exists(projectAbsolutePath) {
|
|
ctx.Log.Info("Config file found in %s", projectAbsolutePath)
|
|
config, err := a.configReader.Read(projectAbsolutePath)
|
|
if err != nil {
|
|
return ProjectResult{Error: err}
|
|
}
|
|
|
|
// add terraform arguments from project config
|
|
terraformApplyExtraArgs = 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) {
|
|
// run terraform init and environment
|
|
outputs, err := a.terraform.RunInitAndEnv(projectAbsolutePath, tfEnv, config.GetExtraArguments("init"))
|
|
if err != nil {
|
|
return ProjectResult{Error: err}
|
|
}
|
|
ctx.Log.Info("terraform init and environment commands ran successfully %s", outputs)
|
|
}
|
|
|
|
// if there are pre plan commands then run them
|
|
if len(config.PreApply.Commands) > 0 {
|
|
preRunOutput, err := a.preRun.Start(config.PreApply.Commands, projectAbsolutePath, ctx.Command.Environment, config.TerraformVersion)
|
|
if err != nil {
|
|
return ProjectResult{Error: err}
|
|
}
|
|
ctx.Log.Info("Pre run output: \n%s", preRunOutput)
|
|
}
|
|
|
|
// need to get auth data from assumed role
|
|
// todo: de-duplicate calls to assumeRole
|
|
a.awsConfig.SessionName = ctx.User.Username
|
|
awsSession, err := a.awsConfig.CreateSession()
|
|
if err != nil {
|
|
return ProjectResult{Error: err}
|
|
}
|
|
|
|
credVals, err := awsSession.Config.Credentials.Get()
|
|
if err != nil {
|
|
err = errors.Wrap(err, "getting assumed role credentials")
|
|
ctx.Log.Err(err.Error())
|
|
return ProjectResult{Error: err}
|
|
}
|
|
|
|
ctx.Log.Info("running apply from %q", plan.Project.Path)
|
|
tfApplyCmd := []string{"apply", "-no-color", plan.LocalPath}
|
|
// append terraform arguments from config file
|
|
tfApplyCmd = append(tfApplyCmd, terraformApplyExtraArgs...)
|
|
output, err := a.terraform.RunCommand(projectAbsolutePath, tfApplyCmd, []string{
|
|
fmt.Sprintf("AWS_ACCESS_KEY_ID=%s", credVals.AccessKeyID),
|
|
fmt.Sprintf("AWS_SECRET_ACCESS_KEY=%s", credVals.SecretAccessKey),
|
|
fmt.Sprintf("AWS_SESSION_TOKEN=%s", credVals.SessionToken),
|
|
})
|
|
if err != nil {
|
|
return ProjectResult{Error: fmt.Errorf("%s\n%s", err.Error(), output)}
|
|
}
|
|
|
|
return ProjectResult{ApplySuccess: output}
|
|
}
|
|
|
|
func (a *ApplyExecutor) failureResponse(ctx *CommandContext, msg string) CommandResponse {
|
|
ctx.Log.Warn(msg)
|
|
a.githubStatus.Update(ctx.BaseRepo, ctx.Pull, Failure, ApplyStep)
|
|
return CommandResponse{Failure: msg}
|
|
}
|
|
|
|
func (a *ApplyExecutor) errorResponse(ctx *CommandContext, err error) CommandResponse {
|
|
ctx.Log.Err(err.Error())
|
|
a.githubStatus.Update(ctx.BaseRepo, ctx.Pull, Error, ApplyStep)
|
|
return CommandResponse{Error: err}
|
|
}
|