Skip to content

Commit

Permalink
Implement a new policy check workflow (#1317)
Browse files Browse the repository at this point in the history
* Adding policy_check support into yaml config

* Added policy check model and runtime structs

* Adding BuildPolicyCheckCommand to ProjectCommandBuilder

* Return incorrectly deleted code

* Remove BuildAutoPolicyPlanCommand from ProjectCommandBuilder

* Split runAutoCommand into two functions

runAutoPlanCommand - does what originally RunAutoplancommand was doing
except now it returns CommandResult and []models.ProjectCommandContext
runAutoPolicyCheckCommand - accepts CommandContext, CommandResult, and
[]models.ProjectCommandContext as arguments and runs PolicyCheckStep runner

* Refactor RunCommentCommand
* Remove BuildPolicyCheckCommand and rename StepCmdExec back to TerraformExec
* Add policy step runner logic and conftest interfaces.
* Add show step runner to policy check stage.

* Adding models.PolicyCheckCommand to buildCtx

This also means buildPlanAllCommands call buildCtx twice once with
models.PlanCommand and once with models.PolicyCheckCommand

* Adding new project_command_builder that supports policy_check

* Refactoring PolicyCheck specific logic into a PolicyCheckProjectCommandBuilder

* Moving events.CommandContext to models.CommandContext this will allow me
to remove buildCtx method and move ProjectCommandContext creation into
models package

* Policy Owners might be different types, for that reason we are
refactoring Owners into its own struct with specific keys defining
different owner types

Co-authored-by: Nish Krishnan <[email protected]>
Co-authored-by: Nish Krishnan <[email protected]>
  • Loading branch information
3 people authored Feb 11, 2021
1 parent 10d7b28 commit af2a806
Show file tree
Hide file tree
Showing 126 changed files with 7,635 additions and 1,176 deletions.
16 changes: 16 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ RUN AVAILABLE_TERRAFORM_VERSIONS="0.8.8 0.9.11 0.10.8 0.11.14 0.12.30 0.13.6 ${D
done && \
ln -s /usr/local/bin/tf/versions/${DEFAULT_TERRAFORM_VERSION}/terraform /usr/local/bin/terraform

ENV DEFAULT_CONFTEST_VERSION=0.21.0

RUN AVAILABLE_CONFTEST_VERSIONS="${DEFAULT_CONFTEST_VERSION}" && \
for VERSION in ${AVAILABLE_CONFTEST_VERSIONS}; do \
curl -LOs https://github.com/open-policy-agent/conftest/releases/download/v${VERSION}/conftest_${VERSION}_Linux_x86_64.tar.gz && \
curl -LOs https://github.com/open-policy-agent/conftest/releases/download/v${VERSION}/checksums.txt && \
sed -n "/conftest_${VERSION}_Linux_x86_64.tar.gz/p" checksums.txt | sha256sum -c && \
mkdir -p /usr/local/bin/cft/versions/${VERSION} && \
tar -C /usr/local/bin/cft/versions/${VERSION} -xzf conftest_${VERSION}_Linux_x86_64.tar.gz && \
ln -s /usr/local/bin/cft/versions/${VERSION}/conftest /usr/local/bin/conftest${VERSION} && \
rm conftest_${VERSION}_Linux_x86_64.tar.gz && \
rm checksums.txt; \
done

RUN ln -s /usr/local/bin/cft/versions/${DEFAULT_CONFTEST_VERSION}/conftest /usr/local/bin/conftest

# copy binary
COPY atlantis /usr/local/bin/atlantis

Expand Down
3 changes: 3 additions & 0 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
FROM runatlantis/atlantis:latest
COPY atlantis /usr/local/bin/atlantis
# TODO: remove this once we get this in the base image
ENV DEFAULT_CONFTEST_VERSION=0.21.0

WORKDIR /atlantis/src
6 changes: 5 additions & 1 deletion cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const (
DisableAutoplanFlag = "disable-autoplan"
DisableMarkdownFoldingFlag = "disable-markdown-folding"
DisableRepoLockingFlag = "disable-repo-locking"
EnablePolicyChecksFlag = "enable-policy-checks"
GHHostnameFlag = "gh-hostname"
GHTokenFlag = "gh-token"
GHUserFlag = "gh-user"
Expand Down Expand Up @@ -293,7 +294,10 @@ var boolFlags = map[string]boolFlag{
defaultValue: false,
},
DisableRepoLockingFlag: {
description: "Disable atlantis locking repos",
description: "Disable atlantis locking repos",
},
EnablePolicyChecksFlag: {
description: "Enable atlantis to run user defined policy checks. This is explicitly disabled for TFE/TFC backends since plan files are inaccessible.",
defaultValue: false,
},
AllowDraftPRs: {
Expand Down
1 change: 1 addition & 0 deletions cmd/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ var testFlags = map[string]interface{}{
VCSStatusName: "my-status",
WriteGitCredsFlag: true,
DisableAutoplanFlag: true,
EnablePolicyChecksFlag: false,
}

func TestExecute_Defaults(t *testing.T) {
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ require (
github.com/hashicorp/terraform-config-inspect v0.0.0-20200806211835-c481b8bfa41e
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/leodido/go-urn v1.2.0 // indirect
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect
github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018 // indirect
github.com/mcdafydd/go-azuredevops v0.12.0
github.com/microcosm-cc/bluemonday v1.0.1
github.com/mitchellh/colorstring v0.0.0-20150917214807-8631ce90f286
Expand Down
11 changes: 8 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/graph-gophers/graphql-go v0.0.0-20200309224638-dae41bde9ef9/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
Expand Down Expand Up @@ -223,6 +222,10 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 h1:AsEBgzv3DhuYHI/GiQh2HxvTP71HCCE9E/tzGUzGdtU=
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5/go.mod h1:c2mYKRyMb1BPkO5St0c/ps62L4S0W2NAkaTXj9qEI+0=
github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018 h1:MNApn+Z+fIT4NPZopPfCc1obT6aY3SVM6DOctz1A9ZU=
github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
Expand Down Expand Up @@ -263,8 +266,6 @@ github.com/mohae/deepcopy v0.0.0-20170603005431-491d3605edfb/go.mod h1:TaXosZuwd
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nlopes/slack v0.4.0 h1:OVnHm7lv5gGT5gkcHsZAyw++oHVFihbjWbL3UceUpiA=
github.com/nlopes/slack v0.4.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM=
github.com/nlopes/slack v0.6.0 h1:jt0jxVQGhssx1Ib7naAOZEZcGdtIhTzkP0nopK0AsRA=
github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.9.0 h1:SZjF721BByVj8QH636/8S2DnX4n0Re3SteMmw3N+tzc=
Expand All @@ -277,7 +278,9 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/petergtz/pegomock v2.9.0+incompatible h1:BKfb5XfkJfehe5T+O1xD4Zm26Sb9dnRj7tHxLYwUPiI=
github.com/petergtz/pegomock v2.9.0+incompatible/go.mod h1:nuBLWZpVyv/fLo56qTwt/AUau7jgouO1h7bEvZCq82o=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down Expand Up @@ -462,6 +465,7 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0 h1:Dh6fw+p6FyRl5x/FvNswO1ji0lIGzm3KP8Y9VkS9PTE=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
Expand Down Expand Up @@ -498,6 +502,7 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
Expand Down
186 changes: 186 additions & 0 deletions server/events/apply_command_runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package events

import (
"github.com/runatlantis/atlantis/server/events/db"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/vcs"
)

func NewApplyCommandRunner(
vcsClient vcs.Client,
disableApplyAll bool,
disableApply bool,
commitStatusUpdater CommitStatusUpdater,
prjCommandBuilder ProjectApplyCommandBuilder,
prjCmdRunner ProjectApplyCommandRunner,
autoMerger *AutoMerger,
pullUpdater *PullUpdater,
dbUpdater *DBUpdater,
db *db.BoltDB,
parallelPoolSize int,
) *ApplyCommandRunner {
return &ApplyCommandRunner{
vcsClient: vcsClient,
DisableApplyAll: disableApplyAll,
DisableApply: disableApply,
commitStatusUpdater: commitStatusUpdater,
prjCmdBuilder: prjCommandBuilder,
prjCmdRunner: prjCmdRunner,
autoMerger: autoMerger,
pullUpdater: pullUpdater,
dbUpdater: dbUpdater,
DB: db,
parallelPoolSize: parallelPoolSize,
}
}

type ApplyCommandRunner struct {
DisableApplyAll bool
DisableApply bool
DB *db.BoltDB
vcsClient vcs.Client
commitStatusUpdater CommitStatusUpdater
prjCmdBuilder ProjectApplyCommandBuilder
prjCmdRunner ProjectApplyCommandRunner
autoMerger *AutoMerger
pullUpdater *PullUpdater
dbUpdater *DBUpdater
parallelPoolSize int
}

func (a *ApplyCommandRunner) Run(ctx *CommandContext, cmd *CommentCommand) {
var err error
baseRepo := ctx.Pull.BaseRepo
pull := ctx.Pull

if a.DisableApply {
ctx.Log.Info("ignoring apply command since apply disabled globally")
if err := a.vcsClient.CreateComment(baseRepo, pull.Num, applyDisabledComment, models.ApplyCommand.String()); err != nil {
ctx.Log.Err("unable to comment on pull request: %s", err)
}

return
}

if a.DisableApplyAll && !cmd.IsForSpecificProject() {
ctx.Log.Info("ignoring apply command without flags since apply all is disabled")
if err := a.vcsClient.CreateComment(baseRepo, pull.Num, applyAllDisabledComment, models.ApplyCommand.String()); err != nil {
ctx.Log.Err("unable to comment on pull request: %s", err)
}

return
}

// Get the mergeable status before we set any build statuses of our own.
// We do this here because when we set a "Pending" status, if users have
// required the Atlantis status checks to pass, then we've now changed
// the mergeability status of the pull request.
ctx.PullMergeable, err = a.vcsClient.PullIsMergeable(baseRepo, pull)
if err != nil {
// On error we continue the request with mergeable assumed false.
// We want to continue because not all apply's will need this status,
// only if they rely on the mergeability requirement.
ctx.PullMergeable = false
ctx.Log.Warn("unable to get mergeable status: %s. Continuing with mergeable assumed false", err)
}

// TODO: This needs to be revisited and new PullMergeable like conditions should
// be added to check against it.
if a.anyFailedPolicyChecks(pull) {
ctx.PullMergeable = false
ctx.Log.Warn("when using policy checks all policies have to be approved or pass. Continuing with mergeable assumed false")
}

ctx.Log.Info("pull request mergeable status: %t", ctx.PullMergeable)

if err = a.commitStatusUpdater.UpdateCombined(baseRepo, pull, models.PendingCommitStatus, cmd.CommandName()); err != nil {
ctx.Log.Warn("unable to update commit status: %s", err)
}

var projectCmds []models.ProjectCommandContext
projectCmds, err = a.prjCmdBuilder.BuildApplyCommands(ctx, cmd)

if err != nil {
if statusErr := a.commitStatusUpdater.UpdateCombined(ctx.Pull.BaseRepo, ctx.Pull, models.FailedCommitStatus, cmd.CommandName()); statusErr != nil {
ctx.Log.Warn("unable to update commit status: %s", statusErr)
}
a.pullUpdater.updatePull(ctx, cmd, CommandResult{Error: err})
return
}

// Only run commands in parallel if enabled
var result CommandResult
if a.isParallelEnabled(projectCmds) {
ctx.Log.Info("Running applies in parallel")
result = runProjectCmdsParallel(projectCmds, a.prjCmdRunner.Apply, a.parallelPoolSize)
} else {
result = runProjectCmds(projectCmds, a.prjCmdRunner.Apply)
}

a.pullUpdater.updatePull(
ctx,
cmd,
result)

pullStatus, err := a.dbUpdater.updateDB(ctx, pull, result.ProjectResults)
if err != nil {
ctx.Log.Err("writing results: %s", err)
return
}

a.updateCommitStatus(ctx, pullStatus)

if a.autoMerger.automergeEnabled(projectCmds) {
a.autoMerger.automerge(ctx, pullStatus)
}
}

func (a *ApplyCommandRunner) isParallelEnabled(projectCmds []models.ProjectCommandContext) bool {
return len(projectCmds) > 0 && projectCmds[0].ParallelApplyEnabled
}

func (a *ApplyCommandRunner) updateCommitStatus(ctx *CommandContext, pullStatus models.PullStatus) {
var numSuccess int
var numErrored int
status := models.SuccessCommitStatus

numSuccess = pullStatus.StatusCount(models.AppliedPlanStatus)
numErrored = pullStatus.StatusCount(models.ErroredApplyStatus)

if numErrored > 0 {
status = models.FailedCommitStatus
} else if numSuccess < len(pullStatus.Projects) {
// If there are plans that haven't been applied yet, we'll use a pending
// status.
status = models.PendingCommitStatus
}

if err := a.commitStatusUpdater.UpdateCombinedCount(
ctx.Pull.BaseRepo,
ctx.Pull,
status,
models.ApplyCommand,
numSuccess,
len(pullStatus.Projects),
); err != nil {
ctx.Log.Warn("unable to update commit status: %s", err)
}
}

func (a *ApplyCommandRunner) anyFailedPolicyChecks(pull models.PullRequest) bool {
policyCheckPullStatus, _ := a.DB.GetPullStatus(pull)
if policyCheckPullStatus != nil && policyCheckPullStatus.StatusCount(models.ErroredPolicyCheckStatus) > 0 {
return true
}

return false

}

// applyAllDisabledComment is posted when apply all commands (i.e. "atlantis apply")
// are disabled and an apply all command is issued.
var applyAllDisabledComment = "**Error:** Running `atlantis apply` without flags is disabled." +
" You must specify which project to apply via the `-d <dir>`, `-w <workspace>` or `-p <project name>` flags."

// applyDisabledComment is posted when apply commands are disabled globally and an apply command is issued.
var applyDisabledComment = "**Error:** Running `atlantis apply` is disabled."
Loading

0 comments on commit af2a806

Please sign in to comment.