Skip to content

Commit

Permalink
Skip autoplan if a pull request matches skip keywords for GitHub
Browse files Browse the repository at this point in the history
This is an attempt to partially support for runatlantis#932.

If an author of the pull request has a confidence of "no changes for
real resources", it would be great if atlantis could skip autoplan.

The initial implementation of this feature as follows:

If a title of pull request contains the following keywords, skip autoplan.

* [skip atlantis]
* [skip ci]
* [atlantis skip]
* [ci skip]

We should always force to invoke plan with explicit comment command.

This feature is currently implemented only for GitHub just because
I'm a user of GitHub, but I expect it's possible to support other VCS
providers.

Note:

Most of general purpose CI/CD platforms support a concept for skip
build. For examples:
https://circleci.com/docs/2.0/skip-build/
https://docs.github.com/en/actions/guides/about-continuous-integration#skipping-workflow-runs

As far as I know, they check all commits included in the pull request,
not a title of the pull request because they need to support triggered
on push event. On the other hand, the current implementation of atlantis
doesn't triggered on push event and doesn't have all commits on open
event. To simplify the implementation, I think checking the title is
reasonable. Of course it's possible to get all commits included in the
pull request dynamically via additional API calls, please let me know if
we should check commit messages instead of the title.

The original feature request said that the 'keyword' could be
configurable, but I don't think most of users including me need such a
flexibility. So the initial implementation embeds keywords in source. If
someone need to be configurable, feel free to open another feature
request.
  • Loading branch information
minamijoyo committed Sep 8, 2021
1 parent 7f884fa commit f1b2637
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 0 deletions.
9 changes: 9 additions & 0 deletions runatlantis.io/docs/autoplanning.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,12 @@ or disable it all together you need to create an `atlantis.yaml` file.
See
* [Disabling Autoplanning](repo-level-atlantis-yaml.html#disabling-autoplanning)
* [Configuring Planning](repo-level-atlantis-yaml.html#configuring-planning)

::: tip
If a title of pull request contains the following keywords, Atlantis will skip autoplanning. (This feature is currently implemented only for GitHub)

* [skip atlantis]
* [skip ci]
* [atlantis skip]
* [ci skip]
:::
6 changes: 6 additions & 0 deletions server/events/command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ func (c *DefaultCommandRunner) RunAutoplanCommand(baseRepo models.Repo, headRepo
return
}

// Skip autoplan if a pull request matches skip keywords.
// We can always force to invoke plan with explicit comment command.
if pull.SkipByKeyword() {
return
}

err = c.PreWorkflowHooksCommandRunner.RunPreHooks(ctx)

if err != nil {
Expand Down
9 changes: 9 additions & 0 deletions server/events/command_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -777,3 +777,12 @@ func TestRunAutoplanCommand_DrainNotOngoing(t *testing.T) {
projectCommandBuilder.VerifyWasCalledOnce().BuildAutoplanCommands(matchers.AnyPtrToEventsCommandContext())
Equals(t, 0, drainer.GetStatus().InProgressOps)
}

func TestRunAutoplanCommand_SkipKeywordMatch(t *testing.T) {
t.Log("if a title of the pull request contains skip keywords then skip autoplan")
setup(t)
fixtures.Pull.BaseRepo = fixtures.GithubRepo
fixtures.Pull.Title = "[skip atlantis] foo"
ch.RunAutoplanCommand(fixtures.GithubRepo, fixtures.GithubRepo, fixtures.Pull, fixtures.User)
projectCommandBuilder.VerifyWasCalled(Never()).BuildAutoplanCommands(matchers.AnyPtrToEventsCommandContext())
}
6 changes: 6 additions & 0 deletions server/events/event_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,11 @@ func (e *EventParser) ParseGithubPull(pull *github.PullRequest) (pullModel model
pullState = models.OpenPullState
}

title := ""
if pull.Title != nil {
title = *pull.Title
}

pullModel = models.PullRequest{
Author: authorUsername,
HeadBranch: headBranch,
Expand All @@ -526,6 +531,7 @@ func (e *EventParser) ParseGithubPull(pull *github.PullRequest) (pullModel model
State: pullState,
BaseRepo: baseRepo,
BaseBranch: baseBranch,
Title: title,
}
return
}
Expand Down
15 changes: 15 additions & 0 deletions server/events/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,21 @@ type PullRequest struct {
State PullRequestState
// BaseRepo is the repository that the pull request will be merged into.
BaseRepo Repo
// Title is a title of the pull request.
// Note: This attribute is currently implemented only for GitHub.
Title string
}

// A regex for skip ci
// - [skip atlantis]
// - [skip ci]
// - [atlantis skip]
// - [ci skip]
var skipPullRequestKeywordRegex = regexp.MustCompile(`(\[skip (atlantis|ci)\])|(\[(atlantis|ci) skip\])`)

// SkipByKeyword returns true if a title of the pull request motches skip keywords.
func (p *PullRequest) SkipByKeyword() bool {
return skipPullRequestKeywordRegex.MatchString(p.Title)
}

// PullRequestOptions is used to set optional paralmeters for PullRequest
Expand Down
45 changes: 45 additions & 0 deletions server/events/models/models_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,51 @@ func TestNewRepo_HTTPSAuth(t *testing.T) {
}, repo)
}

func TestPullRequest_SkipByKeyword(t *testing.T) {
cases := []struct {
title string
exp bool
}{
{
"[skip atlantis] foo",
true,
},
{
"[skip ci] foo",
true,
},
{
"[atlantis skip] foo",
true,
},
{
"[ci skip] foo",
true,
},
{
"foo [ci skip]",
true,
},
{
"foo",
false,
},
{
"",
false,
},
}

for _, c := range cases {
t.Run(c.title, func(t *testing.T) {
pull := &models.PullRequest{
Title: c.title,
}
Equals(t, c.exp, pull.SkipByKeyword())
})
}
}

func TestProject_String(t *testing.T) {
Equals(t, "repofullname=owner/repo path=my/path", (models.Project{
RepoFullName: "owner/repo",
Expand Down

0 comments on commit f1b2637

Please sign in to comment.