Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for parallel plans #926

Merged
merged 11 commits into from
May 25, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const (
GitlabUserFlag = "gitlab-user"
GitlabWebhookSecretFlag = "gitlab-webhook-secret" // nolint: gosec
LogLevelFlag = "log-level"
ParallelPlansPoolSize = "parallel-plans-pool-size"
PortFlag = "port"
RepoConfigFlag = "repo-config"
RepoConfigJSONFlag = "repo-config-json"
Expand All @@ -80,18 +81,19 @@ const (
WriteGitCredsFlag = "write-git-creds"

// NOTE: Must manually set these as defaults in the setDefaults function.
DefaultADBasicUser = ""
DefaultADBasicPassword = ""
DefaultCheckoutStrategy = "branch"
DefaultBitbucketBaseURL = bitbucketcloud.BaseURL
DefaultDataDir = "~/.atlantis"
DefaultGHHostname = "github.com"
DefaultGitlabHostname = "gitlab.com"
DefaultLogLevel = "info"
DefaultPort = 4141
DefaultTFDownloadURL = "https://releases.hashicorp.com"
DefaultTFEHostname = "app.terraform.io"
DefaultVCSStatusName = "atlantis"
DefaultADBasicUser = ""
DefaultADBasicPassword = ""
DefaultCheckoutStrategy = "branch"
DefaultBitbucketBaseURL = bitbucketcloud.BaseURL
DefaultDataDir = "~/.atlantis"
DefaultGHHostname = "github.com"
DefaultGitlabHostname = "gitlab.com"
DefaultLogLevel = "info"
DefaultParallelPlansPoolSize = 10
DefaultPort = 4141
DefaultTFDownloadURL = "https://releases.hashicorp.com"
DefaultTFEHostname = "app.terraform.io"
DefaultVCSStatusName = "atlantis"
)

var stringFlags = map[string]stringFlag{
Expand Down Expand Up @@ -275,6 +277,10 @@ var boolFlags = map[string]boolFlag{
},
}
var intFlags = map[string]intFlag{
ParallelPlansPoolSize: {
description: "Max size of the wait group that runs parallel plans (if enabled).",
defaultValue: DefaultParallelPlansPoolSize,
Fauzyy marked this conversation as resolved.
Show resolved Hide resolved
},
PortFlag: {
description: "Port to bind to.",
defaultValue: DefaultPort,
Expand Down
10 changes: 3 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module github.com/runatlantis/atlantis
go 1.13

require (
git.apache.org/thrift.git v0.12.0 // indirect
github.com/Masterminds/semver v1.4.2 // indirect
github.com/Masterminds/sprig v2.15.0+incompatible
github.com/aokoli/goutils v1.0.1 // indirect
Expand All @@ -23,7 +22,6 @@ require (
github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c // indirect
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f // indirect
github.com/gorilla/mux v1.6.2
github.com/grpc-ecosystem/grpc-gateway v1.6.2 // indirect
github.com/hashicorp/go-getter v1.4.0
github.com/hashicorp/go-version v1.2.0
github.com/hashicorp/hcl v0.0.0-20170914154624-68e816d1c783 // indirect
Expand All @@ -36,17 +34,16 @@ require (
github.com/microcosm-cc/bluemonday v1.0.1
github.com/mitchellh/colorstring v0.0.0-20150917214807-8631ce90f286
github.com/mitchellh/go-homedir v1.0.0
github.com/mitchellh/gox v1.0.1 // indirect
github.com/mitchellh/mapstructure v0.0.0-20170523030023-d0303fe80992 // indirect
github.com/mohae/deepcopy v0.0.0-20170603005431-491d3605edfb
github.com/nlopes/slack v0.1.0
github.com/onsi/ginkgo v1.9.0 // indirect
github.com/openzipkin/zipkin-go v0.1.3 // indirect
github.com/pelletier/go-buffruneio v0.2.0 // indirect
github.com/pelletier/go-toml v1.0.0 // indirect
github.com/petergtz/pegomock v2.5.0+incompatible
github.com/petergtz/pegomock v2.7.0+incompatible
github.com/pkg/errors v0.8.0
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 // indirect
github.com/remeh/sizedwaitgroup v1.0.0
github.com/sirupsen/logrus v1.2.0 // indirect
github.com/spf13/afero v0.0.0-20170901052352-ee1bd8ee15a1 // indirect
github.com/spf13/cast v1.1.0 // indirect
github.com/spf13/cobra v0.0.0-20170905172051-b78744579491
Expand All @@ -57,7 +54,6 @@ require (
github.com/urfave/cli v1.20.0
github.com/urfave/negroni v0.2.0
github.com/xanzy/go-gitlab v0.22.2-0.20191127083556-16a492660b8c
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d // indirect
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect
golang.org/x/oauth2 v0.0.0-20191122200657-5d9234df094c // indirect
Expand Down
169 changes: 7 additions & 162 deletions go.sum

Large diffs are not rendered by default.

64 changes: 62 additions & 2 deletions server/events/command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ package events

import (
"fmt"
"sync"

"github.com/google/go-github/v28/github"
"github.com/mcdafydd/go-azuredevops/azuredevops"
"github.com/pkg/errors"
"github.com/remeh/sizedwaitgroup"
"github.com/runatlantis/atlantis/server/events/db"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/vcs"
Expand Down Expand Up @@ -75,6 +77,9 @@ type DefaultCommandRunner struct {
Logger logging.SimpleLogging
// AllowForkPRs controls whether we operate on pull requests from forks.
AllowForkPRs bool
// ParallelPlansPoolSize controls the size of the wait group used to run
// parallel plans (if enabled).
ParallelPlansPoolSize int
// AllowForkPRsFlag is the name of the flag that controls fork PR's. We use
// this in our error message back to the user on a forked PR so they know
// how to enable this functionality.
Expand Down Expand Up @@ -134,7 +139,15 @@ func (c *DefaultCommandRunner) RunAutoplanCommand(baseRepo models.Repo, headRepo
return
}

result := c.runProjectCmds(projectCmds, models.PlanCommand)
// Run our plan commands in parallel if enabled
var result CommandResult
if c.parallelPlansEnabled(ctx, projectCmds) {
ctx.Log.Info("Running plans in parallel")
result = c.runProjectCmdsParallel(projectCmds, models.PlanCommand)
} else {
result = c.runProjectCmds(projectCmds, models.PlanCommand)
}

if c.automergeEnabled(ctx, projectCmds) && result.HasErrors() {
ctx.Log.Info("deleting plans because there were errors and automerge requires all plans succeed")
c.deletePlans(ctx)
Expand Down Expand Up @@ -244,7 +257,14 @@ func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHead
return
}

result := c.runProjectCmds(projectCmds, cmd.Name)
// Run our plan commands in parallel if enabled
var result CommandResult
if cmd.Name == models.PlanCommand && c.parallelPlansEnabled(ctx, projectCmds) {
ctx.Log.Info("Running plans in parallel")
result = c.runProjectCmdsParallel(projectCmds, cmd.Name)
} else {
result = c.runProjectCmds(projectCmds, cmd.Name)
}
if cmd.Name == models.PlanCommand && c.automergeEnabled(ctx, projectCmds) && result.HasErrors() {
ctx.Log.Info("deleting plans because there were errors and automerge requires all plans succeed")
c.deletePlans(ctx)
Expand Down Expand Up @@ -329,6 +349,41 @@ func (c *DefaultCommandRunner) automerge(ctx *CommandContext, pullStatus models.
}
}

func (c *DefaultCommandRunner) runProjectCmdsParallel(cmds []models.ProjectCommandContext, cmdName models.CommandName) CommandResult {
var results []models.ProjectResult
mux := &sync.Mutex{}

wg := sizedwaitgroup.New(c.ParallelPlansPoolSize)
for _, pCmd := range cmds {
pCmd := pCmd
var execute func()
wg.Add()

switch cmdName {
case models.PlanCommand:
execute = func() {
defer wg.Done()
res := c.ProjectCommandRunner.Plan(pCmd)
mux.Lock()
results = append(results, res)
mux.Unlock()
}
case models.ApplyCommand:
Fauzyy marked this conversation as resolved.
Show resolved Hide resolved
execute = func() {
defer wg.Done()
res := c.ProjectCommandRunner.Apply(pCmd)
mux.Lock()
results = append(results, res)
mux.Unlock()
}
}
go execute()
}

wg.Wait()
return CommandResult{ProjectResults: results}
}

func (c *DefaultCommandRunner) runProjectCmds(cmds []models.ProjectCommandContext, cmdName models.CommandName) CommandResult {
var results []models.ProjectResult
for _, pCmd := range cmds {
Expand Down Expand Up @@ -477,6 +532,11 @@ func (c *DefaultCommandRunner) automergeEnabled(ctx *CommandContext, projectCmds
(len(projectCmds) > 0 && projectCmds[0].AutomergeEnabled)
}

// parallelPlansEnabled returns true if parallel plans is enabled in this context.
func (c *DefaultCommandRunner) parallelPlansEnabled(ctx *CommandContext, projectCmds []models.ProjectCommandContext) bool {
return len(projectCmds) > 0 && projectCmds[0].ParallelPlansEnabled
}

// automergeComment is the comment that gets posted when Atlantis automatically
// merges the PR.
var automergeComment = `Automatically merging because all plans have been successfully applied.`
Expand Down
4 changes: 3 additions & 1 deletion server/events/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,11 @@ type ProjectCommandContext struct {
// ApplyRequirements is the list of requirements that must be satisfied
// before we will run the apply stage.
ApplyRequirements []string
// AutoplanEnabled is true if automerge is enabled for the repo that this
// AutomergeEnabled is true if automerge is enabled for the repo that this
// project is in.
AutomergeEnabled bool
// ParallelPlansEnabled is true if parallel plans is enabled for this project.
ParallelPlansEnabled bool
// AutoplanEnabled is true if autoplanning is enabled for this project.
AutoplanEnabled bool
// BaseRepo is the repository that the pull request will be merged into.
Expand Down
50 changes: 28 additions & 22 deletions server/events/project_command_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const (
DefaultWorkspace = "default"
// DefaultAutomergeEnabled is the default for the automerge setting.
DefaultAutomergeEnabled = false
// DefaultParallelPlansEnabled is the default for the parallel plans setting.
DefaultParallelPlansEnabled = false
)

//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_project_command_builder.go ProjectCommandBuilder
Expand Down Expand Up @@ -141,7 +143,7 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *CommandContext,
for _, mp := range matchingProjects {
ctx.Log.Debug("determining config for project at dir: %q workspace: %q", mp.Dir, mp.Workspace)
mergedCfg := p.GlobalCfg.MergeProjectCfg(ctx.Log, ctx.BaseRepo.ID(), mp, repoCfg)
projCtxs = append(projCtxs, p.buildCtx(ctx, models.PlanCommand, mergedCfg, commentFlags, repoCfg.Automerge, verbose, repoDir))
projCtxs = append(projCtxs, p.buildCtx(ctx, models.PlanCommand, mergedCfg, commentFlags, repoCfg.Automerge, repoCfg.ParallelPlans, verbose, repoDir))
}
} else {
// If there is no config file, then we'll plan each project that
Expand All @@ -152,7 +154,7 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *CommandContext,
for _, mp := range modifiedProjects {
ctx.Log.Debug("determining config for project at dir: %q", mp.Path)
pCfg := p.GlobalCfg.DefaultProjCfg(ctx.Log, ctx.BaseRepo.ID(), mp.Path, DefaultWorkspace)
projCtxs = append(projCtxs, p.buildCtx(ctx, models.PlanCommand, pCfg, commentFlags, DefaultAutomergeEnabled, verbose, repoDir))
projCtxs = append(projCtxs, p.buildCtx(ctx, models.PlanCommand, pCfg, commentFlags, DefaultAutomergeEnabled, DefaultParallelPlansEnabled, verbose, repoDir))
}
}

Expand Down Expand Up @@ -283,10 +285,12 @@ func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(
}

automerge := DefaultAutomergeEnabled
parallelPlans := DefaultParallelPlansEnabled
if repoCfgPtr != nil {
automerge = repoCfgPtr.Automerge
parallelPlans = repoCfgPtr.ParallelPlans
}
return p.buildCtx(ctx, cmd, projCfg, commentFlags, automerge, verbose, repoDir), nil
return p.buildCtx(ctx, cmd, projCfg, commentFlags, automerge, parallelPlans, verbose, repoDir), nil
}

// getCfg returns the atlantis.yaml config (if it exists) for this project. If
Expand Down Expand Up @@ -375,6 +379,7 @@ func (p *DefaultProjectCommandBuilder) buildCtx(ctx *CommandContext,
projCfg valid.MergedProjectCfg,
commentArgs []string,
automergeEnabled bool,
parallelPlansEnabled bool,
verbose bool,
absRepoDir string) models.ProjectCommandContext {

Expand All @@ -393,25 +398,26 @@ func (p *DefaultProjectCommandBuilder) buildCtx(ctx *CommandContext,
}

return models.ProjectCommandContext{
ApplyCmd: p.CommentBuilder.BuildApplyComment(projCfg.RepoRelDir, projCfg.Workspace, projCfg.Name),
BaseRepo: ctx.BaseRepo,
EscapedCommentArgs: p.escapeArgs(commentArgs),
AutomergeEnabled: automergeEnabled,
AutoplanEnabled: projCfg.AutoplanEnabled,
Steps: steps,
HeadRepo: ctx.HeadRepo,
Log: ctx.Log,
PullMergeable: ctx.PullMergeable,
Pull: ctx.Pull,
ProjectName: projCfg.Name,
ApplyRequirements: projCfg.ApplyRequirements,
RePlanCmd: p.CommentBuilder.BuildPlanComment(projCfg.RepoRelDir, projCfg.Workspace, projCfg.Name, commentArgs),
RepoRelDir: projCfg.RepoRelDir,
RepoConfigVersion: projCfg.RepoCfgVersion,
TerraformVersion: projCfg.TerraformVersion,
User: ctx.User,
Verbose: verbose,
Workspace: projCfg.Workspace,
ApplyCmd: p.CommentBuilder.BuildApplyComment(projCfg.RepoRelDir, projCfg.Workspace, projCfg.Name),
BaseRepo: ctx.BaseRepo,
EscapedCommentArgs: p.escapeArgs(commentArgs),
AutomergeEnabled: automergeEnabled,
ParallelPlansEnabled: parallelPlansEnabled,
AutoplanEnabled: projCfg.AutoplanEnabled,
Steps: steps,
HeadRepo: ctx.HeadRepo,
Log: ctx.Log,
PullMergeable: ctx.PullMergeable,
Pull: ctx.Pull,
ProjectName: projCfg.Name,
ApplyRequirements: projCfg.ApplyRequirements,
RePlanCmd: p.CommentBuilder.BuildPlanComment(projCfg.RepoRelDir, projCfg.Workspace, projCfg.Name, commentArgs),
RepoRelDir: projCfg.RepoRelDir,
RepoConfigVersion: projCfg.RepoCfgVersion,
TerraformVersion: projCfg.TerraformVersion,
User: ctx.User,
Verbose: verbose,
Workspace: projCfg.Workspace,
}
}

Expand Down
26 changes: 18 additions & 8 deletions server/events/yaml/raw/repo_cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ import (
// DefaultAutomerge is the default setting for automerge.
const DefaultAutomerge = false

// DefaultParallelPlans is the default setting for parallel plans
const DefaultParallelPlans = false
Fauzyy marked this conversation as resolved.
Show resolved Hide resolved

// RepoCfg is the raw schema for repo-level atlantis.yaml config.
type RepoCfg struct {
Version *int `yaml:"version,omitempty"`
Projects []Project `yaml:"projects,omitempty"`
Workflows map[string]Workflow `yaml:"workflows,omitempty"`
Automerge *bool `yaml:"automerge,omitempty"`
Version *int `yaml:"version,omitempty"`
Projects []Project `yaml:"projects,omitempty"`
Workflows map[string]Workflow `yaml:"workflows,omitempty"`
Automerge *bool `yaml:"automerge,omitempty"`
ParallelPlans *bool `yaml:"parallel_plans,omitempty"`
}

func (r RepoCfg) Validate() error {
Expand Down Expand Up @@ -52,10 +56,16 @@ func (r RepoCfg) ToValid() valid.RepoCfg {
automerge = *r.Automerge
}

parallelPlans := DefaultParallelPlans
if r.ParallelPlans != nil {
parallelPlans = *r.ParallelPlans
}

return valid.RepoCfg{
Version: *r.Version,
Projects: validProjects,
Workflows: validWorkflows,
Automerge: automerge,
Version: *r.Version,
Projects: validProjects,
Workflows: validWorkflows,
Automerge: automerge,
ParallelPlans: parallelPlans,
}
}
Loading