mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-07-29 22:17:59 +00:00
Signed-off-by: Andrew Borg <andrew.b.borg@gmail.com> Co-authored-by: PePe Amengual <2208324+jamengual@users.noreply.github.com> Co-authored-by: Simon Heather <32168619+X-Guardian@users.noreply.github.com>
329 lines
10 KiB
Go
329 lines
10 KiB
Go
package events
|
|
|
|
import (
|
|
"path/filepath"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/runatlantis/atlantis/server/core/config/valid"
|
|
"github.com/runatlantis/atlantis/server/core/terraform/tfclient"
|
|
"github.com/runatlantis/atlantis/server/events/command"
|
|
"github.com/runatlantis/atlantis/server/events/models"
|
|
tally "github.com/uber-go/tally/v4"
|
|
)
|
|
|
|
func NewProjectCommandContextBuilder(policyCheckEnabled bool, commentBuilder CommentBuilder, scope tally.Scope) ProjectCommandContextBuilder {
|
|
projectCommandContextBuilder := &DefaultProjectCommandContextBuilder{
|
|
CommentBuilder: commentBuilder,
|
|
}
|
|
|
|
if policyCheckEnabled {
|
|
return &PolicyCheckProjectCommandContextBuilder{
|
|
CommentBuilder: commentBuilder,
|
|
ProjectCommandContextBuilder: projectCommandContextBuilder,
|
|
}
|
|
}
|
|
|
|
return &CommandScopedStatsProjectCommandContextBuilder{
|
|
ProjectCommandContextBuilder: projectCommandContextBuilder,
|
|
ProjectCounter: scope.Counter("projects"),
|
|
}
|
|
}
|
|
|
|
type ProjectCommandContextBuilder interface {
|
|
// BuildProjectContext builds project command contexts for atlantis commands
|
|
BuildProjectContext(
|
|
ctx *command.Context,
|
|
cmdName command.Name,
|
|
subCmdName string,
|
|
prjCfg valid.MergedProjectCfg,
|
|
commentFlags []string,
|
|
repoDir string,
|
|
automerge, parallelApply, parallelPlan, verbose, abortOnExecutionOrderFail bool, terraformClient tfclient.Client,
|
|
) []command.ProjectContext
|
|
}
|
|
|
|
// CommandScopedStatsProjectCommandContextBuilder ensures that project command context contains a scoped stats
|
|
// object relevant to the command it applies to.
|
|
type CommandScopedStatsProjectCommandContextBuilder struct {
|
|
ProjectCommandContextBuilder
|
|
// Consciously making this global since it gets flushed periodically anyways
|
|
ProjectCounter tally.Counter
|
|
}
|
|
|
|
// BuildProjectContext builds the context and injects the appropriate command level scope after the fact.
|
|
func (cb *CommandScopedStatsProjectCommandContextBuilder) BuildProjectContext(
|
|
ctx *command.Context,
|
|
cmdName command.Name,
|
|
subCmdName string,
|
|
prjCfg valid.MergedProjectCfg,
|
|
commentFlags []string,
|
|
repoDir string,
|
|
automerge, parallelApply, parallelPlan, verbose, abortOnExecutionOrderFail bool,
|
|
terraformClient tfclient.Client,
|
|
) (projectCmds []command.ProjectContext) {
|
|
cb.ProjectCounter.Inc(1)
|
|
|
|
cmds := cb.ProjectCommandContextBuilder.BuildProjectContext(
|
|
ctx, cmdName, subCmdName, prjCfg, commentFlags, repoDir, automerge, parallelApply, parallelPlan, verbose, abortOnExecutionOrderFail, terraformClient,
|
|
)
|
|
|
|
projectCmds = []command.ProjectContext{}
|
|
|
|
for _, cmd := range cmds {
|
|
|
|
// specifically use the command name in the context instead of the arg
|
|
// since we can return multiple commands worth of contexts for a given command name arg
|
|
// to effectively pipeline them.
|
|
cmd.Scope = cmd.SetProjectScopeTags(cmd.Scope)
|
|
projectCmds = append(projectCmds, cmd)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
type DefaultProjectCommandContextBuilder struct {
|
|
CommentBuilder CommentBuilder
|
|
}
|
|
|
|
func (cb *DefaultProjectCommandContextBuilder) BuildProjectContext(
|
|
ctx *command.Context,
|
|
cmdName command.Name,
|
|
subName string,
|
|
prjCfg valid.MergedProjectCfg,
|
|
commentFlags []string,
|
|
repoDir string,
|
|
automerge, parallelApply, parallelPlan, verbose, abortOnExecutionOrderFail bool,
|
|
terraformClient tfclient.Client,
|
|
) (projectCmds []command.ProjectContext) {
|
|
ctx.Log.Debug("Building project command context for %s", cmdName)
|
|
|
|
var steps []valid.Step
|
|
switch cmdName {
|
|
case command.Plan:
|
|
steps = prjCfg.Workflow.Plan.Steps
|
|
case command.Apply:
|
|
steps = prjCfg.Workflow.Apply.Steps
|
|
case command.Version:
|
|
// Setting statically since there will only be one step
|
|
steps = []valid.Step{{
|
|
StepName: "version",
|
|
}}
|
|
case command.Import:
|
|
steps = prjCfg.Workflow.Import.Steps
|
|
case command.State:
|
|
switch subName {
|
|
case "rm":
|
|
steps = prjCfg.Workflow.StateRm.Steps
|
|
default:
|
|
// comment_parser prevent invalid subcommand, so not need to handle this.
|
|
// if comes here, state_command_runner will respond on PR, so it's enough to do log only.
|
|
ctx.Log.Err("unknown state subcommand: %s", subName)
|
|
}
|
|
}
|
|
|
|
// If TerraformVersion not defined in config file look for a
|
|
// terraform.require_version block.
|
|
if prjCfg.TerraformVersion == nil {
|
|
prjCfg.TerraformVersion = terraformClient.DetectVersion(ctx.Log, filepath.Join(repoDir, prjCfg.RepoRelDir))
|
|
}
|
|
|
|
projectCmdContext := newProjectCommandContext(
|
|
ctx,
|
|
cmdName,
|
|
cb.CommentBuilder.BuildApplyComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name, prjCfg.AutoMergeDisabled, prjCfg.AutoMergeMethod),
|
|
cb.CommentBuilder.BuildApprovePoliciesComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name),
|
|
cb.CommentBuilder.BuildPlanComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name, commentFlags),
|
|
prjCfg,
|
|
steps,
|
|
prjCfg.PolicySets,
|
|
escapeArgs(commentFlags),
|
|
automerge,
|
|
parallelApply,
|
|
parallelPlan,
|
|
verbose,
|
|
abortOnExecutionOrderFail,
|
|
ctx.Scope,
|
|
ctx.PullRequestStatus,
|
|
ctx.PullStatus,
|
|
ctx.TeamAllowlistChecker,
|
|
)
|
|
|
|
projectCmds = append(projectCmds, projectCmdContext)
|
|
|
|
return
|
|
}
|
|
|
|
type PolicyCheckProjectCommandContextBuilder struct {
|
|
ProjectCommandContextBuilder *DefaultProjectCommandContextBuilder
|
|
CommentBuilder CommentBuilder
|
|
}
|
|
|
|
func (cb *PolicyCheckProjectCommandContextBuilder) BuildProjectContext(
|
|
ctx *command.Context,
|
|
cmdName command.Name,
|
|
subCmdName string,
|
|
prjCfg valid.MergedProjectCfg,
|
|
commentFlags []string,
|
|
repoDir string,
|
|
automerge, parallelApply, parallelPlan, verbose, abortOnExecutionOrderFail bool,
|
|
terraformClient tfclient.Client,
|
|
) (projectCmds []command.ProjectContext) {
|
|
if prjCfg.PolicyCheck {
|
|
ctx.Log.Debug("PolicyChecks are enabled")
|
|
} else {
|
|
// PolicyCheck is disabled at repository level
|
|
ctx.Log.Debug("PolicyChecks are disabled on this repository")
|
|
}
|
|
|
|
// If TerraformVersion not defined in config file look for a
|
|
// terraform.require_version block.
|
|
if prjCfg.TerraformVersion == nil {
|
|
prjCfg.TerraformVersion = terraformClient.DetectVersion(ctx.Log, filepath.Join(repoDir, prjCfg.RepoRelDir))
|
|
}
|
|
|
|
projectCmds = cb.ProjectCommandContextBuilder.BuildProjectContext(
|
|
ctx,
|
|
cmdName,
|
|
subCmdName,
|
|
prjCfg,
|
|
commentFlags,
|
|
repoDir,
|
|
automerge,
|
|
parallelApply,
|
|
parallelPlan,
|
|
verbose,
|
|
abortOnExecutionOrderFail,
|
|
terraformClient,
|
|
)
|
|
|
|
if cmdName == command.Plan && prjCfg.PolicyCheck {
|
|
ctx.Log.Debug("Building project command context for %s", command.PolicyCheck)
|
|
steps := prjCfg.Workflow.PolicyCheck.Steps
|
|
|
|
projectCmds = append(projectCmds, newProjectCommandContext(
|
|
ctx,
|
|
command.PolicyCheck,
|
|
cb.CommentBuilder.BuildApplyComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name, prjCfg.AutoMergeDisabled, prjCfg.AutoMergeMethod),
|
|
cb.CommentBuilder.BuildApprovePoliciesComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name),
|
|
cb.CommentBuilder.BuildPlanComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name, commentFlags),
|
|
prjCfg,
|
|
steps,
|
|
prjCfg.PolicySets,
|
|
escapeArgs(commentFlags),
|
|
automerge,
|
|
parallelApply,
|
|
parallelPlan,
|
|
verbose,
|
|
abortOnExecutionOrderFail,
|
|
ctx.Scope,
|
|
ctx.PullRequestStatus,
|
|
ctx.PullStatus,
|
|
ctx.TeamAllowlistChecker,
|
|
))
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// newProjectCommandContext is a initializer method that handles constructing the
|
|
// ProjectCommandContext.
|
|
func newProjectCommandContext(ctx *command.Context,
|
|
cmd command.Name,
|
|
applyCmd string,
|
|
approvePoliciesCmd string,
|
|
planCmd string,
|
|
projCfg valid.MergedProjectCfg,
|
|
steps []valid.Step,
|
|
policySets valid.PolicySets,
|
|
escapedCommentArgs []string,
|
|
automergeEnabled bool,
|
|
parallelApplyEnabled bool,
|
|
parallelPlanEnabled bool,
|
|
verbose bool,
|
|
abortOnExecutionOrderFail bool,
|
|
scope tally.Scope,
|
|
pullReqStatus models.PullReqStatus,
|
|
pullStatus *models.PullStatus,
|
|
teamAllowlistChecker command.TeamAllowlistChecker,
|
|
) command.ProjectContext {
|
|
|
|
var projectPlanStatus models.ProjectPlanStatus
|
|
var projectPolicyStatus []models.PolicySetStatus
|
|
|
|
if ctx.PullStatus != nil {
|
|
for _, project := range ctx.PullStatus.Projects {
|
|
|
|
// if name is not used, let's match the directory
|
|
if projCfg.Name == "" && project.RepoRelDir == projCfg.RepoRelDir {
|
|
projectPlanStatus = project.Status
|
|
projectPolicyStatus = project.PolicyStatus
|
|
break
|
|
}
|
|
|
|
if projCfg.Name != "" && project.ProjectName == projCfg.Name {
|
|
projectPlanStatus = project.Status
|
|
projectPolicyStatus = project.PolicyStatus
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return command.ProjectContext{
|
|
CommandName: cmd,
|
|
ApplyCmd: applyCmd,
|
|
ApprovePoliciesCmd: approvePoliciesCmd,
|
|
BaseRepo: ctx.Pull.BaseRepo,
|
|
EscapedCommentArgs: escapedCommentArgs,
|
|
AutomergeEnabled: automergeEnabled,
|
|
DeleteSourceBranchOnMerge: projCfg.DeleteSourceBranchOnMerge,
|
|
RepoLocksMode: projCfg.RepoLocks.Mode,
|
|
CustomPolicyCheck: projCfg.CustomPolicyCheck,
|
|
ParallelApplyEnabled: parallelApplyEnabled,
|
|
ParallelPlanEnabled: parallelPlanEnabled,
|
|
ParallelPolicyCheckEnabled: parallelPlanEnabled,
|
|
DependsOn: projCfg.DependsOn,
|
|
AutoplanEnabled: projCfg.AutoplanEnabled,
|
|
Steps: steps,
|
|
HeadRepo: ctx.HeadRepo,
|
|
Log: ctx.Log,
|
|
Scope: scope,
|
|
ProjectPlanStatus: projectPlanStatus,
|
|
ProjectPolicyStatus: projectPolicyStatus,
|
|
Pull: ctx.Pull,
|
|
ProjectName: projCfg.Name,
|
|
PlanRequirements: projCfg.PlanRequirements,
|
|
ApplyRequirements: projCfg.ApplyRequirements,
|
|
ImportRequirements: projCfg.ImportRequirements,
|
|
RePlanCmd: planCmd,
|
|
RepoRelDir: projCfg.RepoRelDir,
|
|
RepoConfigVersion: projCfg.RepoCfgVersion,
|
|
TerraformDistribution: projCfg.TerraformDistribution,
|
|
TerraformVersion: projCfg.TerraformVersion,
|
|
User: ctx.User,
|
|
Verbose: verbose,
|
|
Workspace: projCfg.Workspace,
|
|
PolicySets: policySets,
|
|
PolicySetTarget: ctx.PolicySet,
|
|
ClearPolicyApproval: ctx.ClearPolicyApproval,
|
|
PullReqStatus: pullReqStatus,
|
|
PullStatus: pullStatus,
|
|
JobID: uuid.New().String(),
|
|
ExecutionOrderGroup: projCfg.ExecutionOrderGroup,
|
|
AbortOnExecutionOrderFail: abortOnExecutionOrderFail,
|
|
SilencePRComments: projCfg.SilencePRComments,
|
|
TeamAllowlistChecker: teamAllowlistChecker,
|
|
}
|
|
}
|
|
|
|
func escapeArgs(args []string) []string {
|
|
var escaped []string
|
|
for _, arg := range args {
|
|
var escapedArg string
|
|
for i := range arg {
|
|
escapedArg += "\\" + string(arg[i])
|
|
}
|
|
escaped = append(escaped, escapedArg)
|
|
}
|
|
return escaped
|
|
}
|