mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-08-03 17:58:43 +00:00
Support team owners for policies (#2953)
This commit is contained in:
@@ -57,6 +57,8 @@ GitHub App needs these permissions. These are automatically set when a GitHub ap
|
||||
|
||||
::: tip NOTE
|
||||
Since v0.19.7, a new permission for `Administration` has been added. If you have already created a GitHub app, updating Atlantis to v0.19.7 will not automatically add this permission, so you will need to set it manually.
|
||||
|
||||
Since v0.22.3, a new permission for `Members` has been added, which is required for features that apply permissions to an organizations team members rather than individual users. Like the `Administration` permission above, updating Atlantis will not automatically add this permission, so if you wish to use features that rely on checking team membership you will need to add this manually.
|
||||
:::
|
||||
|
||||
| Type | Access |
|
||||
@@ -69,6 +71,7 @@ Since v0.19.7, a new permission for `Administration` has been added. If you have
|
||||
| Metadata | Read-only (default) |
|
||||
| Pull requests | Read and write |
|
||||
| Webhooks | Read and write |
|
||||
| Members | Read-only |
|
||||
|
||||
### GitLab
|
||||
- Follow: [https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html#create-a-personal-access-token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html#create-a-personal-access-token)
|
||||
|
||||
@@ -521,7 +521,8 @@ If you set a workflow with the key `default`, it will override this.
|
||||
### Owners
|
||||
| Key | Type | Default | Required | Description |
|
||||
|-------------|-------------------|---------|------------|---------------------------------------------------------|
|
||||
| users | []string | none | yes | list of github users that can approve failing policies |
|
||||
| users | []string | none | no | list of github users that can approve failing policies |
|
||||
| teams | []string | none | no | list of github teams that can approve failing policies |
|
||||
|
||||
### PolicySet
|
||||
|
||||
|
||||
@@ -1222,6 +1222,7 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers
|
||||
dbUpdater,
|
||||
silenceNoProjects,
|
||||
false,
|
||||
e2eVCSClient,
|
||||
)
|
||||
|
||||
unlockCommandRunner := events.NewUnlockCommandRunner(
|
||||
|
||||
@@ -121,6 +121,7 @@ func (g *GithubAppController) New(w http.ResponseWriter, r *http.Request) {
|
||||
"repository_hooks": "write",
|
||||
"statuses": "write",
|
||||
"administration": "read",
|
||||
"members": "read",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ func (p PolicySets) ToValid() valid.PolicySets {
|
||||
|
||||
type PolicyOwners struct {
|
||||
Users []string `yaml:"users,omitempty" json:"users,omitempty"`
|
||||
Teams []string `yaml:"teams,omitempty" json:"teams,omitempty"`
|
||||
}
|
||||
|
||||
func (o PolicyOwners) ToValid() valid.PolicyOwners {
|
||||
@@ -48,6 +49,10 @@ func (o PolicyOwners) ToValid() valid.PolicyOwners {
|
||||
if len(o.Users) > 0 {
|
||||
policyOwners.Users = o.Users
|
||||
}
|
||||
|
||||
if len(o.Teams) > 0 {
|
||||
policyOwners.Teams = o.Teams
|
||||
}
|
||||
return policyOwners
|
||||
}
|
||||
|
||||
|
||||
@@ -180,6 +180,9 @@ func TestPolicySets_ToValid(t *testing.T) {
|
||||
Users: []string{
|
||||
"test",
|
||||
},
|
||||
Teams: []string{
|
||||
"testteam",
|
||||
},
|
||||
},
|
||||
PolicySets: []raw.PolicySet{
|
||||
{
|
||||
@@ -199,6 +202,7 @@ func TestPolicySets_ToValid(t *testing.T) {
|
||||
Version: version,
|
||||
Owners: valid.PolicyOwners{
|
||||
Users: []string{"test"},
|
||||
Teams: []string{"testteam"},
|
||||
},
|
||||
PolicySets: []valid.PolicySet{
|
||||
{
|
||||
|
||||
@@ -22,6 +22,7 @@ type PolicySets struct {
|
||||
|
||||
type PolicyOwners struct {
|
||||
Users []string
|
||||
Teams []string
|
||||
}
|
||||
|
||||
type PolicySet struct {
|
||||
@@ -35,12 +36,24 @@ func (p *PolicySets) HasPolicies() bool {
|
||||
return len(p.PolicySets) > 0
|
||||
}
|
||||
|
||||
func (p *PolicySets) IsOwner(username string) bool {
|
||||
func (p *PolicySets) HasTeamOwners() bool {
|
||||
return len(p.Owners.Teams) > 0
|
||||
}
|
||||
|
||||
func (p *PolicySets) IsOwner(username string, userTeams []string) bool {
|
||||
for _, uname := range p.Owners.Users {
|
||||
if strings.EqualFold(uname, username) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, orgTeamName := range p.Owners.Teams {
|
||||
for _, userTeamName := range userTeams {
|
||||
if strings.EqualFold(orgTeamName, userTeamName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/runatlantis/atlantis/server/events/command"
|
||||
"github.com/runatlantis/atlantis/server/events/models"
|
||||
"github.com/runatlantis/atlantis/server/events/vcs"
|
||||
)
|
||||
|
||||
func NewApprovePoliciesCommandRunner(
|
||||
@@ -15,6 +16,7 @@ func NewApprovePoliciesCommandRunner(
|
||||
dbUpdater *DBUpdater,
|
||||
SilenceNoProjects bool,
|
||||
silenceVCSStatusNoProjects bool,
|
||||
vcsClient vcs.Client,
|
||||
) *ApprovePoliciesCommandRunner {
|
||||
return &ApprovePoliciesCommandRunner{
|
||||
commitStatusUpdater: commitStatusUpdater,
|
||||
@@ -24,6 +26,7 @@ func NewApprovePoliciesCommandRunner(
|
||||
dbUpdater: dbUpdater,
|
||||
SilenceNoProjects: SilenceNoProjects,
|
||||
silenceVCSStatusNoProjects: silenceVCSStatusNoProjects,
|
||||
vcsClient: vcsClient,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +40,7 @@ type ApprovePoliciesCommandRunner struct {
|
||||
// are found
|
||||
SilenceNoProjects bool
|
||||
silenceVCSStatusNoProjects bool
|
||||
vcsClient vcs.Client
|
||||
}
|
||||
|
||||
func (a *ApprovePoliciesCommandRunner) Run(ctx *command.Context, cmd *CommentCommand) {
|
||||
@@ -91,9 +95,23 @@ func (a *ApprovePoliciesCommandRunner) buildApprovePolicyCommandResults(ctx *com
|
||||
// Check if vcs user is in the owner list of the PolicySets. All projects
|
||||
// share the same Owners list at this time so no reason to iterate over each
|
||||
// project.
|
||||
if len(prjCmds) > 0 && !prjCmds[0].PolicySets.IsOwner(ctx.User.Username) {
|
||||
result.Error = fmt.Errorf("contact policy owners to approve failing policies")
|
||||
return
|
||||
if len(prjCmds) > 0 {
|
||||
teams := []string{}
|
||||
|
||||
// Only query the users team membership if any teams have been configured as owners.
|
||||
if prjCmds[0].PolicySets.HasTeamOwners() {
|
||||
userTeams, err := a.vcsClient.GetTeamNamesForUser(ctx.Pull.BaseRepo, ctx.User)
|
||||
if err != nil {
|
||||
ctx.Log.Err("unable to get team membership for user: %s", err)
|
||||
return
|
||||
}
|
||||
teams = append(teams, userTeams...)
|
||||
}
|
||||
|
||||
if !prjCmds[0].PolicySets.IsOwner(ctx.User.Username, teams) {
|
||||
result.Error = fmt.Errorf("contact policy owners to approve failing policies")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var prjResults []command.ProjectResult
|
||||
|
||||
@@ -20,16 +20,41 @@ func TestApproveCommandRunner_IsOwner(t *testing.T) {
|
||||
cases := []struct {
|
||||
Description string
|
||||
OwnerUsers []string
|
||||
OwnerTeams []string // Teams configured as owners
|
||||
UserTeams []string // Teams the user is a member of
|
||||
ExpComment string
|
||||
}{
|
||||
{
|
||||
Description: "When user is not an owner, approval fails",
|
||||
OwnerUsers: []string{},
|
||||
OwnerTeams: []string{},
|
||||
ExpComment: "**Approve Policies Error**\n```\ncontact policy owners to approve failing policies\n```\n",
|
||||
},
|
||||
{
|
||||
Description: "When user is an owner, approval succeeds",
|
||||
OwnerUsers: []string{fixtures.User.Username},
|
||||
OwnerTeams: []string{},
|
||||
ExpComment: "Approved Policies for 1 projects:\n\n1. dir: `` workspace: ``\n\n\n",
|
||||
},
|
||||
{
|
||||
Description: "When user is an owner via team membership, approval succeeds",
|
||||
OwnerUsers: []string{},
|
||||
OwnerTeams: []string{"SomeTeam"},
|
||||
UserTeams: []string{"SomeTeam"},
|
||||
ExpComment: "Approved Policies for 1 projects:\n\n1. dir: `` workspace: ``\n\n\n",
|
||||
},
|
||||
{
|
||||
Description: "When user belongs to a team not configured as a owner, approval fails",
|
||||
OwnerUsers: []string{},
|
||||
OwnerTeams: []string{"SomeTeam"},
|
||||
UserTeams: []string{"SomeOtherTeam}"},
|
||||
ExpComment: "**Approve Policies Error**\n```\ncontact policy owners to approve failing policies\n```\n",
|
||||
},
|
||||
{
|
||||
Description: "When user is an owner but not a team member, approval succeeds",
|
||||
OwnerUsers: []string{fixtures.User.Username},
|
||||
OwnerTeams: []string{"SomeTeam"},
|
||||
UserTeams: []string{"SomeOtherTeam"},
|
||||
ExpComment: "Approved Policies for 1 projects:\n\n1. dir: `` workspace: ``\n\n\n",
|
||||
},
|
||||
}
|
||||
@@ -57,10 +82,12 @@ func TestApproveCommandRunner_IsOwner(t *testing.T) {
|
||||
PolicySets: valid.PolicySets{
|
||||
Owners: valid.PolicyOwners{
|
||||
Users: c.OwnerUsers,
|
||||
Teams: c.OwnerTeams,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
When(vcsClient.GetTeamNamesForUser(fixtures.GithubRepo, fixtures.User)).ThenReturn(c.UserTeams, nil)
|
||||
|
||||
approvePoliciesCommandRunner.Run(ctx, &events.CommentCommand{Name: command.ApprovePolicies})
|
||||
|
||||
|
||||
@@ -182,6 +182,7 @@ func setup(t *testing.T, options ...func(testConfig *TestConfig)) *vcsmocks.Mock
|
||||
dbUpdater,
|
||||
testConfig.SilenceNoProjects,
|
||||
false,
|
||||
vcsClient,
|
||||
)
|
||||
|
||||
unlockCommandRunner = events.NewUnlockCommandRunner(
|
||||
|
||||
@@ -714,6 +714,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
|
||||
dbUpdater,
|
||||
userConfig.SilenceNoProjects,
|
||||
userConfig.SilenceVCSStatusNoPlans,
|
||||
vcsClient,
|
||||
)
|
||||
|
||||
unlockCommandRunner := events.NewUnlockCommandRunner(
|
||||
|
||||
Reference in New Issue
Block a user