mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-07-29 01:28:44 +00:00
Add a branch regex matcher to the server side repo config (#1383)
* Add a branch regex to the repo.yaml config to allow Atlantis to
accept only pull requests with a given base branch (the branch
a PR would merge _into_).
* `branch` is optional. By default its unset and Atlantis will match
webhooks for pull requests for any branch (no change)
Match any PR,
```
repos:
- id: /.*/
branch: /.*/
```
Match only PRs with master or main base branch.
```
repos:
- id: /.*/
branch: /(main|master)/
```
Some repos have special pull request branching practices. For example,
plan/apply from feature branches merging into master, but separate flows
for merging master into a release branch (where Atlantis isn't used).
Adding a regex allows for flexible workflow options. For example, you
could have Atlantis ignore "release" branches.
This commit is contained in:
@@ -28,6 +28,10 @@ repos:
|
||||
# Repo ID's are of the form {VCS hostname}/{org}/{repo name}, ex.
|
||||
# github.com/runatlantis/atlantis.
|
||||
- id: /.*/
|
||||
# branch is an regex matching pull requests by base branch
|
||||
# (the branch the pull request is getting merged into).
|
||||
# By default, all branches are matched
|
||||
branch: /.*/
|
||||
|
||||
# apply_requirements sets the Apply Requirements for all repos that match.
|
||||
apply_requirements: [approved, mergeable]
|
||||
|
||||
@@ -34,7 +34,7 @@ func (w *DefaultPreWorkflowHooksCommandRunner) RunPreHooks(
|
||||
|
||||
preWorkflowHooks := make([]*valid.PreWorkflowHook, 0)
|
||||
for _, repo := range w.GlobalCfg.Repos {
|
||||
if repo.IDMatches(baseRepo.ID()) && len(repo.PreWorkflowHooks) > 0 {
|
||||
if repo.IDMatches(baseRepo.ID()) && repo.BranchMatches(pull.BaseBranch) && len(repo.PreWorkflowHooks) > 0 {
|
||||
preWorkflowHooks = append(preWorkflowHooks, repo.PreWorkflowHooks...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1014,11 +1014,17 @@ func TestParseGlobalCfg(t *testing.T) {
|
||||
- apply_requirements: []`,
|
||||
expErr: "repos: (0: (id: cannot be blank.).).",
|
||||
},
|
||||
"invalid regex": {
|
||||
"invalid id regex": {
|
||||
input: `repos:
|
||||
- id: /?/`,
|
||||
expErr: "repos: (0: (id: parsing: /?/: error parsing regexp: missing argument to repetition operator: `?`.).).",
|
||||
},
|
||||
"invalid branch regex": {
|
||||
input: `repos:
|
||||
- id: /.*/
|
||||
branch: /?/`,
|
||||
expErr: "repos: (0: (branch: parsing: /?/: error parsing regexp: missing argument to repetition operator: `?`.).).",
|
||||
},
|
||||
"workflow doesn't exist": {
|
||||
input: `repos:
|
||||
- id: /.*/
|
||||
@@ -1115,6 +1121,7 @@ repos:
|
||||
allowed_overrides: [apply_requirements, workflow]
|
||||
allow_custom_workflows: true
|
||||
- id: /.*/
|
||||
branch: /(master|main)/
|
||||
pre_workflow_hooks:
|
||||
- run: custom workflow command
|
||||
workflows:
|
||||
@@ -1155,6 +1162,7 @@ policies:
|
||||
},
|
||||
{
|
||||
IDRegex: regexp.MustCompile(".*"),
|
||||
BranchRegex: regexp.MustCompile("(master|main)"),
|
||||
PreWorkflowHooks: preWorkflowHooks,
|
||||
},
|
||||
},
|
||||
@@ -1228,6 +1236,7 @@ workflows:
|
||||
Repos: []valid.Repo{
|
||||
{
|
||||
IDRegex: regexp.MustCompile(".*"),
|
||||
BranchRegex: regexp.MustCompile(".*"),
|
||||
PreWorkflowHooks: []*valid.PreWorkflowHook{},
|
||||
ApplyRequirements: []string{},
|
||||
Workflow: &valid.Workflow{
|
||||
@@ -1301,6 +1310,10 @@ workflows:
|
||||
Assert(t, expRepo.IDRegex.String() == actRepo.IDRegex.String(),
|
||||
"%q != %q for repos[%d]", expRepo.IDRegex.String(), actRepo.IDRegex.String(), i)
|
||||
}
|
||||
if expRepo.BranchRegex != nil {
|
||||
Assert(t, expRepo.BranchRegex.String() == actRepo.BranchRegex.String(),
|
||||
"%q != %q for repos[%d]", expRepo.BranchRegex.String(), actRepo.BranchRegex.String(), i)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ type GlobalCfg struct {
|
||||
// Repo is the raw schema for repos in the server-side repo config.
|
||||
type Repo struct {
|
||||
ID string `yaml:"id" json:"id"`
|
||||
Branch string `yaml:"branch" json:"branch"`
|
||||
ApplyRequirements []string `yaml:"apply_requirements" json:"apply_requirements"`
|
||||
PreWorkflowHooks []PreWorkflowHook `yaml:"pre_workflow_hooks" json:"pre_workflow_hooks"`
|
||||
Workflow *string `yaml:"workflow,omitempty" json:"workflow,omitempty"`
|
||||
@@ -121,6 +122,11 @@ func (r Repo) HasRegexID() bool {
|
||||
return strings.HasPrefix(r.ID, "/") && strings.HasSuffix(r.ID, "/")
|
||||
}
|
||||
|
||||
// HasRegexBranch returns true if a branch regex was set.
|
||||
func (r Repo) HasRegexBranch() bool {
|
||||
return strings.HasPrefix(r.Branch, "/") && strings.HasSuffix(r.Branch, "/")
|
||||
}
|
||||
|
||||
func (r Repo) Validate() error {
|
||||
idValid := func(value interface{}) error {
|
||||
id := value.(string)
|
||||
@@ -131,6 +137,15 @@ func (r Repo) Validate() error {
|
||||
return errors.Wrapf(err, "parsing: %s", id)
|
||||
}
|
||||
|
||||
branchValid := func(value interface{}) error {
|
||||
branch := value.(string)
|
||||
if !r.HasRegexBranch() {
|
||||
return nil
|
||||
}
|
||||
_, err := regexp.Compile(branch[1 : len(branch)-1])
|
||||
return errors.Wrapf(err, "parsing: %s", branch)
|
||||
}
|
||||
|
||||
overridesValid := func(value interface{}) error {
|
||||
overrides := value.([]string)
|
||||
for _, o := range overrides {
|
||||
@@ -149,6 +164,7 @@ func (r Repo) Validate() error {
|
||||
|
||||
return validation.ValidateStruct(&r,
|
||||
validation.Field(&r.ID, validation.Required, validation.By(idValid)),
|
||||
validation.Field(&r.Branch, validation.By(branchValid)),
|
||||
validation.Field(&r.AllowedOverrides, validation.By(overridesValid)),
|
||||
validation.Field(&r.ApplyRequirements, validation.By(validApplyReq)),
|
||||
validation.Field(&r.Workflow, validation.By(workflowExists)),
|
||||
@@ -166,6 +182,13 @@ func (r Repo) ToValid(workflows map[string]valid.Workflow) valid.Repo {
|
||||
id = r.ID
|
||||
}
|
||||
|
||||
var branchRegex *regexp.Regexp
|
||||
if r.HasRegexBranch() {
|
||||
withoutSlashes := r.Branch[1 : len(r.Branch)-1]
|
||||
// Safe to use MustCompile because we test it in Validate().
|
||||
branchRegex = regexp.MustCompile(withoutSlashes)
|
||||
}
|
||||
|
||||
var workflow *valid.Workflow
|
||||
if r.Workflow != nil {
|
||||
// This key is guaranteed to exist because we test for it in
|
||||
@@ -184,6 +207,7 @@ func (r Repo) ToValid(workflows map[string]valid.Workflow) valid.Repo {
|
||||
return valid.Repo{
|
||||
ID: id,
|
||||
IDRegex: idRegex,
|
||||
BranchRegex: branchRegex,
|
||||
ApplyRequirements: r.ApplyRequirements,
|
||||
PreWorkflowHooks: preWorkflowHooks,
|
||||
Workflow: workflow,
|
||||
|
||||
@@ -34,6 +34,7 @@ type Repo struct {
|
||||
// IDRegex is the regex match for this config.
|
||||
// If ID is set then this will be nil.
|
||||
IDRegex *regexp.Regexp
|
||||
BranchRegex *regexp.Regexp
|
||||
ApplyRequirements []string
|
||||
PreWorkflowHooks []*PreWorkflowHook
|
||||
Workflow *Workflow
|
||||
@@ -123,6 +124,7 @@ func NewGlobalCfgWithHooks(allowRepoCfg bool, mergeableReq bool, approvedReq boo
|
||||
Repos: []Repo{
|
||||
{
|
||||
IDRegex: regexp.MustCompile(".*"),
|
||||
BranchRegex: regexp.MustCompile(".*"),
|
||||
ApplyRequirements: applyReqs,
|
||||
PreWorkflowHooks: preWorkflowHooks,
|
||||
Workflow: &defaultWorkflow,
|
||||
@@ -158,6 +160,14 @@ func (r Repo) IDMatches(otherID string) bool {
|
||||
return r.IDRegex.MatchString(otherID)
|
||||
}
|
||||
|
||||
// BranchMatches returns true if the branch other matches a branch regex (if preset).
|
||||
func (r Repo) BranchMatches(other string) bool {
|
||||
if r.BranchRegex == nil {
|
||||
return true
|
||||
}
|
||||
return r.BranchRegex.MatchString(other)
|
||||
}
|
||||
|
||||
// IDString returns a string representation of this config.
|
||||
func (r Repo) IDString() string {
|
||||
if r.ID != "" {
|
||||
|
||||
@@ -50,6 +50,7 @@ func TestNewGlobalCfg(t *testing.T) {
|
||||
Repos: []valid.Repo{
|
||||
{
|
||||
IDRegex: regexp.MustCompile(".*"),
|
||||
BranchRegex: regexp.MustCompile(".*"),
|
||||
ApplyRequirements: []string{},
|
||||
Workflow: &expDefaultWorkflow,
|
||||
AllowedWorkflows: []string{},
|
||||
@@ -108,6 +109,7 @@ func TestNewGlobalCfg(t *testing.T) {
|
||||
// For each test, we change our expected cfg based on the parameters.
|
||||
exp := deepcopy.Copy(baseCfg).(valid.GlobalCfg)
|
||||
exp.Repos[0].IDRegex = regexp.MustCompile(".*") // deepcopy doesn't copy the regex.
|
||||
exp.Repos[0].BranchRegex = regexp.MustCompile(".*")
|
||||
|
||||
if c.allowRepoCfg {
|
||||
exp.Repos[0].AllowCustomWorkflows = Bool(true)
|
||||
@@ -132,6 +134,10 @@ func TestNewGlobalCfg(t *testing.T) {
|
||||
Assert(t, expRepo.IDRegex.String() == actRepo.IDRegex.String(),
|
||||
"%q != %q for repos[%d]", expRepo.IDRegex.String(), actRepo.IDRegex.String(), i)
|
||||
}
|
||||
if expRepo.BranchRegex != nil {
|
||||
Assert(t, expRepo.BranchRegex.String() == actRepo.BranchRegex.String(),
|
||||
"%q != %q for repos[%d]", expRepo.BranchRegex.String(), actRepo.BranchRegex.String(), i)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -726,6 +732,21 @@ func TestRepo_IDString(t *testing.T) {
|
||||
Equals(t, "/regex.*/", (valid.Repo{IDRegex: regexp.MustCompile("regex.*")}).IDString())
|
||||
}
|
||||
|
||||
func TestRepo_BranchMatches(t *testing.T) {
|
||||
// Test matches when no branch regex is set.
|
||||
Equals(t, true, (valid.Repo{}).BranchMatches("main"))
|
||||
|
||||
// Test regexes.
|
||||
Equals(t, true, (valid.Repo{BranchRegex: regexp.MustCompile(".*")}).BranchMatches("main"))
|
||||
Equals(t, true, (valid.Repo{BranchRegex: regexp.MustCompile("main")}).BranchMatches("main"))
|
||||
Equals(t, false, (valid.Repo{BranchRegex: regexp.MustCompile("^main$")}).BranchMatches("foo-main"))
|
||||
Equals(t, false, (valid.Repo{BranchRegex: regexp.MustCompile("^main$")}).BranchMatches("main-foo"))
|
||||
Equals(t, true, (valid.Repo{BranchRegex: regexp.MustCompile("(main|master)")}).BranchMatches("main"))
|
||||
Equals(t, true, (valid.Repo{BranchRegex: regexp.MustCompile("(main|master)")}).BranchMatches("master"))
|
||||
Equals(t, true, (valid.Repo{BranchRegex: regexp.MustCompile("release")}).BranchMatches("release-stage"))
|
||||
Equals(t, false, (valid.Repo{BranchRegex: regexp.MustCompile("release")}).BranchMatches("main"))
|
||||
}
|
||||
|
||||
// String is a helper routine that allocates a new string value
|
||||
// to store v and returns a pointer to it.
|
||||
func String(v string) *string { return &v }
|
||||
|
||||
Reference in New Issue
Block a user