-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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 GitHub Nested Teams 🐣 #8486
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -219,7 +219,11 @@ func dumpOrgConfig(client dumpClient, orgName string) (*org.Config, error) { | |
return nil, fmt.Errorf("failed to list teams: %v", err) | ||
} | ||
|
||
out.Teams = make(map[string]org.Team, len(teams)) | ||
names := map[int]string{} // what's the name of a team? | ||
idMap := map[int]org.Team{} // metadata for a team | ||
children := map[int][]int{} // what children does it have | ||
var tops []int // what are the top-level teams | ||
|
||
for _, t := range teams { | ||
p := org.Privacy(t.Privacy) | ||
d := t.Description | ||
|
@@ -230,6 +234,7 @@ func dumpOrgConfig(client dumpClient, orgName string) (*org.Config, error) { | |
}, | ||
Maintainers: []string{}, | ||
Members: []string{}, | ||
Children: map[string]org.Team{}, | ||
} | ||
maintainers, err := client.ListTeamMembers(t.ID, github.RoleMaintainer) | ||
if err != nil { | ||
|
@@ -245,8 +250,32 @@ func dumpOrgConfig(client dumpClient, orgName string) (*org.Config, error) { | |
for _, m := range teamMembers { | ||
nt.Members = append(nt.Members, m.Login) | ||
} | ||
out.Teams[t.Name] = nt | ||
|
||
names[t.ID] = t.Name | ||
idMap[t.ID] = nt | ||
|
||
if t.Parent == nil { // top level team | ||
tops = append(tops, t.ID) | ||
} else { // add this id to the list of the parent's children | ||
children[t.Parent.ID] = append(children[t.Parent.ID], t.ID) | ||
} | ||
} | ||
|
||
var makeChild func(id int) org.Team | ||
makeChild = func(id int) org.Team { | ||
t := idMap[id] | ||
for _, cid := range children[id] { | ||
child := makeChild(cid) | ||
t.Children[names[cid]] = child | ||
} | ||
return t | ||
} | ||
|
||
out.Teams = make(map[string]org.Team, len(tops)) | ||
for _, id := range tops { | ||
out.Teams[names[id]] = makeChild(id) | ||
} | ||
|
||
return &out, nil | ||
} | ||
|
||
|
@@ -360,10 +389,7 @@ func configureOrgMembers(opt options, client orgClient, orgName string, orgConfi | |
return err | ||
} | ||
|
||
if err = configureMembers(have, want, adder, remover); err != nil { | ||
return err | ||
} | ||
return nil | ||
return configureMembers(have, want, adder, remover) | ||
} | ||
|
||
type memberships struct { | ||
|
@@ -633,20 +659,39 @@ func configureOrg(opt options, client *github.Client, orgName string, orgConfig | |
} | ||
|
||
for name, team := range orgConfig.Teams { | ||
gt, ok := githubTeams[name] | ||
if !ok { // configureTeams is buggy if this is the case | ||
return fmt.Errorf("%s not found in id list", name) | ||
err := configureTeamAndMembers(client, githubTeams, name, orgName, team, nil) | ||
if err != nil { | ||
return fmt.Errorf("failed to configure %s teams: %v", orgName, err) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func configureTeamAndMembers(client *github.Client, githubTeams map[string]github.Team, name, orgName string, team org.Team, parent *int) error { | ||
gt, ok := githubTeams[name] | ||
if !ok { // configureTeams is buggy if this is the case | ||
return fmt.Errorf("%s not found in id list", name) | ||
} | ||
|
||
// Configure team metadata | ||
err := configureTeam(client, orgName, name, team, gt) | ||
// Configure team metadata | ||
err := configureTeam(client, orgName, name, team, gt, parent) | ||
if err != nil { | ||
return fmt.Errorf("failed to update %s metadata: %v", name, err) | ||
} | ||
|
||
// Configure team members | ||
err = configureTeamMembers(client, gt.ID, team) | ||
if err != nil { | ||
return fmt.Errorf("failed to update %s members: %v", name, err) | ||
} | ||
|
||
for childName, childTeam := range team.Children { | ||
err = configureTeamAndMembers(client, githubTeams, childName, orgName, childTeam, >.ID) | ||
if err != nil { | ||
return fmt.Errorf("failed to update %s: %v", orgName, err) | ||
return fmt.Errorf("failed to update %s child teams: %v", name, err) | ||
} | ||
|
||
// Add/remove/update members to the team. | ||
return configureTeamMembers(client, gt.ID, team) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
|
@@ -655,7 +700,7 @@ type editTeamClient interface { | |
} | ||
|
||
// configureTeam patches the team name/description/privacy when values differ | ||
func configureTeam(client editTeamClient, orgName, teamName string, team org.Team, gt github.Team) error { | ||
func configureTeam(client editTeamClient, orgName, teamName string, team org.Team, gt github.Team, parent *int) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not understand the motivation behind / value of changing this to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. got it, thanks! Can you include a unit test for removing the parent from a team then? |
||
// Do we need to reconfigure any team settings? | ||
patch := false | ||
if gt.Name != teamName { | ||
|
@@ -675,6 +720,22 @@ func configureTeam(client editTeamClient, orgName, teamName string, team org.Tea | |
} else { | ||
gt.Privacy = "" | ||
} | ||
// doesn't have parent in github, but has parent in config | ||
if gt.Parent == nil && parent != nil { | ||
patch = true | ||
gt.ParentTeamID = parent | ||
} | ||
if gt.Parent != nil { // has parent in github ... | ||
if parent == nil { // ... but doesn't need one | ||
patch = true | ||
gt.Parent = nil | ||
gt.ParentTeamID = parent | ||
} else if gt.Parent.ID != *parent { // but it's different than the config | ||
patch = true | ||
gt.Parent = nil | ||
gt.ParentTeamID = parent | ||
} | ||
} | ||
|
||
if patch { // yes we need to patch | ||
if _, err := client.EditTeam(gt); err != nil { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -698,6 +698,13 @@ func (c *fakeTeamClient) EditTeam(team github.Team) (*github.Team, error) { | |
if team.Privacy != "" { | ||
t.Privacy = team.Privacy | ||
} | ||
if team.ParentTeamID != nil { | ||
t.Parent = &github.Team{ | ||
ID: *team.ParentTeamID, | ||
} | ||
} else { | ||
t.Parent = nil | ||
} | ||
c.teams[id] = t | ||
return &t, nil | ||
} | ||
|
@@ -974,10 +981,12 @@ func TestConfigureTeam(t *testing.T) { | |
pfail := org.Privacy(fail) | ||
whatev := "whatever" | ||
secret := org.Secret | ||
parent := 2 | ||
cases := []struct { | ||
name string | ||
err bool | ||
teamName string | ||
parent *int | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where do you use this value? |
||
config org.Team | ||
github github.Team | ||
expected github.Team | ||
|
@@ -1000,6 +1009,7 @@ func TestConfigureTeam(t *testing.T) { | |
{ | ||
name: "patch team when description changes", | ||
teamName: whatev, | ||
parent: nil, | ||
config: org.Team{ | ||
TeamMetadata: org.TeamMetadata{ | ||
Description: &cur, | ||
|
@@ -1019,6 +1029,7 @@ func TestConfigureTeam(t *testing.T) { | |
{ | ||
name: "patch team when privacy changes", | ||
teamName: whatev, | ||
parent: nil, | ||
config: org.Team{ | ||
TeamMetadata: org.TeamMetadata{ | ||
Privacy: &secret, | ||
|
@@ -1035,9 +1046,48 @@ func TestConfigureTeam(t *testing.T) { | |
Privacy: string(secret), | ||
}, | ||
}, | ||
{ | ||
name: "patch team when parent changes", | ||
teamName: whatev, | ||
parent: &parent, | ||
config: org.Team{}, | ||
github: github.Team{ | ||
ID: 3, | ||
Name: whatev, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please make sure the parent ID is the only bad value. By making |
||
Parent: &github.Team{ | ||
ID: 4, | ||
}, | ||
}, | ||
expected: github.Team{ | ||
ID: 3, | ||
Name: whatev, | ||
Parent: &github.Team{ | ||
ID: 2, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "patch team when parent removed", | ||
teamName: whatev, | ||
parent: nil, | ||
config: org.Team{}, | ||
github: github.Team{ | ||
ID: 3, | ||
Name: whatev, | ||
Parent: &github.Team{ | ||
ID: 2, | ||
}, | ||
}, | ||
expected: github.Team{ | ||
ID: 3, | ||
Name: whatev, | ||
Parent: nil, | ||
}, | ||
}, | ||
{ | ||
name: "do not patch team when values are the same", | ||
teamName: fail, | ||
parent: &parent, | ||
config: org.Team{ | ||
TeamMetadata: org.TeamMetadata{ | ||
Description: &fail, | ||
|
@@ -1049,17 +1099,24 @@ func TestConfigureTeam(t *testing.T) { | |
Name: fail, | ||
Description: fail, | ||
Privacy: fail, | ||
Parent: &github.Team{ | ||
ID: 2, | ||
}, | ||
}, | ||
expected: github.Team{ | ||
ID: 4, | ||
Name: fail, | ||
Description: fail, | ||
Privacy: fail, | ||
Parent: &github.Team{ | ||
ID: 2, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "fail to patch team", | ||
teamName: "team", | ||
parent: nil, | ||
config: org.Team{ | ||
TeamMetadata: org.TeamMetadata{ | ||
Description: &fail, | ||
|
@@ -1077,7 +1134,7 @@ func TestConfigureTeam(t *testing.T) { | |
for _, tc := range cases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
fc := makeFakeTeamClient(tc.github) | ||
err := configureTeam(fc, fakeOrg, tc.teamName, tc.config, tc.github) | ||
err := configureTeam(fc, fakeOrg, tc.teamName, tc.config, tc.github, tc.parent) | ||
switch { | ||
case err != nil: | ||
if !tc.err { | ||
|
@@ -1086,7 +1143,7 @@ func TestConfigureTeam(t *testing.T) { | |
case tc.err: | ||
t.Errorf("failed to receive expected error") | ||
case !reflect.DeepEqual(fc.teams[tc.expected.ID], tc.expected): | ||
t.Errorf("actual %v != expected %v", fc.teams[tc.expected.ID], tc.expected) | ||
t.Errorf("actual %+v != expected %+v", fc.teams[tc.expected.ID], tc.expected) | ||
} | ||
}) | ||
} | ||
|
@@ -1567,7 +1624,7 @@ func TestDumpOrgConfig(t *testing.T) { | |
MembersCanCreateRepositories: yes, | ||
DefaultRepositoryPermission: string(perm), | ||
}, | ||
members: []string{"george", "jungle"}, | ||
members: []string{"george", "jungle", "banana"}, | ||
admins: []string{"james", "giant", "peach"}, | ||
teams: []github.Team{ | ||
{ | ||
|
@@ -1579,14 +1636,24 @@ func TestDumpOrgConfig(t *testing.T) { | |
ID: 6, | ||
Name: "enemies", | ||
}, | ||
{ | ||
ID: 7, | ||
Name: "archenemies", | ||
Parent: &github.Team{ | ||
ID: 6, | ||
Name: "enemies", | ||
}, | ||
}, | ||
}, | ||
teamMembers: map[int][]string{ | ||
5: {"george", "james"}, | ||
6: {"george"}, | ||
7: {}, | ||
}, | ||
maintainers: map[int][]string{ | ||
5: {}, | ||
6: {"giant", "jungle"}, | ||
7: {"banana"}, | ||
}, | ||
expected: org.Config{ | ||
Metadata: org.Metadata{ | ||
|
@@ -1609,6 +1676,7 @@ func TestDumpOrgConfig(t *testing.T) { | |
}, | ||
Members: []string{"george", "james"}, | ||
Maintainers: []string{}, | ||
Children: map[string]org.Team{}, | ||
}, | ||
"enemies": { | ||
TeamMetadata: org.TeamMetadata{ | ||
|
@@ -1617,9 +1685,20 @@ func TestDumpOrgConfig(t *testing.T) { | |
}, | ||
Members: []string{"george"}, | ||
Maintainers: []string{"giant", "jungle"}, | ||
Children: map[string]org.Team{ | ||
"archenemies": { | ||
TeamMetadata: org.TeamMetadata{ | ||
Description: &empty, | ||
Privacy: &pub, | ||
}, | ||
Members: []string{}, | ||
Maintainers: []string{"banana"}, | ||
Children: map[string]org.Team{}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
Members: []string{"george", "jungle"}, | ||
Members: []string{"george", "jungle", "banana"}, | ||
Admins: []string{"james", "giant", "peach"}, | ||
}, | ||
}, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One last change.. moved this line down here from L222