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 GitHub Nested Teams 🐣 #8486

Merged
merged 1 commit into from
Jun 30, 2018

Conversation

cblecker
Copy link
Member

@cblecker cblecker commented Jun 27, 2018

Add support for Github Nested Teams to peribolos.
https://blog.github.com/2017-06-13-nested-teams-add-depth-to-your-team-structure/

example config:

teams:
  sig-foo:
    description: foo enthusiasts
    members:
    - george
    - jane
    privacy: closed
    teams:
      sig-foo-leads:
      members:
      - george
      sig-foo-pr-reviews:
      members:
      - jane

@cblecker cblecker added the area/prow Issues or PRs related to prow label Jun 27, 2018
@k8s-ci-robot k8s-ci-robot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. size/M Denotes a PR that changes 30-99 lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Jun 27, 2018
@cblecker cblecker force-pushed the nested-teams branch 2 times, most recently from ecaeb72 to 26f555f Compare June 27, 2018 22:20
@k8s-ci-robot k8s-ci-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Jun 27, 2018
Privacy *Privacy `json:"privacy,omitempty"`
Description *string `json:"description,omitempty"`
Privacy *Privacy `json:"privacy,omitempty"`
ParentTeamID *int `json:"parent_team_id,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this and add a Children map[string]Team field in Team (or maybe Teams)

We would want the config to be something like:

teams:
  sig-foo:
    description: foo enthusiasts
    teams:
      sig-foo-leads:
      members:
      - george
      sig-foo-pr-reviews:
      members:
      - jane

@@ -579,6 +579,16 @@ type Team struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Privacy string `json:"privacy,omitempty"`
Parent *Team `json:"parent,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ParentTeamID here, get rid of the unnecessary NewTeam struct

@@ -651,7 +657,7 @@ func configureOrg(opt options, client *github.Client, orgName string, orgConfig
}

type editTeamClient interface {
EditTeam(team github.Team) (*github.Team, error)
EditTeam(team github.NewTeam) (*github.Team, error)
}

// configureTeam patches the team name/description/privacy when values differ
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change configureTeam() to receive the parent int and do something like:

func configureTeam(client editTeamClient, orgName, teamName string, team org.Team, gt github.Team, parent int) error {
// ...
if parent > 0 && gt.ParentID != parent {
  gt.ParentID = parent
  patch = true
}
// ...
}

Then in configureOrg() the for name, team := range orgConfig.Teams { ... } probably needs to be in its own recursive function:

func configureOrg(...) {
// ...
for name, team := range orgConfig.Teams {
  // move what's currently here into the configureTeamAndMembers()
  configureTeamAndMembers(client, githubTeams, name, team, 0) // top level teams have no parent
}
...
}

func configureTeamAndMembers(client, githubTeams map[string]github.Team, name string, team org.Team, parent int) error {
	gt, ok := githubTeams[name]
	err := configureTeam(client, orgName, name, team, parent)
	err := configureTeamMembers(client, gt.ID, team)
	for childName, childTeam := range team.Teams {
		err := configureTeamAndMembers(client, githubTeams, childName, childTeam, gt.ID)
	}
}

@@ -231,6 +231,9 @@ func dumpOrgConfig(client dumpClient, orgName string) (*org.Config, error) {
Maintainers: []string{},
Members: []string{},
}
if t.Parent.ID != 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should construct the recursive tree.

@k8s-ci-robot k8s-ci-robot added size/M Denotes a PR that changes 30-99 lines, ignoring generated files. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Jun 27, 2018
Copy link
Contributor

@fejta fejta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a skeleton for how we can convert the github response into the tree we want for --dump

}
for _, m := range teamMembers {
nt.Members = append(nt.Members, m.Login)
out.Teams = make(map[string]org.Team)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the general goal here is:

a) Convert github.Team metadata into org.Team metadata
b) Figure out what children a given parent has
c) Create the tree of children, starting at ancestors.

So then we wind up with something like this:

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

// first create all the org.Team items
for _, t := range teams {
  nt := org.Team{
      TeamMetadata: org.TeamMetadata{
         Description: t.Description
      },
      Maintainer: client.ListTeamMembers(t.ID)
  }
  names[t.ID] = t.Name
  idMap[t.ID] = nt
  if t.ParentID == 0 { // top level team
    tops = append(tops, t.ID)
  } else { // add this id to the list of the parent's children
    children[t.ParentID] = append(children[t.ParentID], t.ID)
  }
}

makeChild := func(id int) org.Team {
  t := idMap[id]
  for _, cid := range children[id] {
    child := makeChild(cid)
    t.Children = append(t.Children, child)
  }
  return t
} 

for _, id := range tops {
  out.Teams[names[id]] = makeChild(id)
}

@cblecker cblecker force-pushed the nested-teams branch 3 times, most recently from 1941437 to f3330aa Compare June 29, 2018 21:44
@cblecker
Copy link
Member Author

@fejta I think this is ready for another review. Got the dump working (thanks for the help!). Example from kubernetes-sigs:

  test-parent-team:
    description: ""
    maintainers:
    - cblecker
    privacy: closed
    teams:
      test-child-team:
        description: ""
        maintainers:
        - cblecker
        privacy: closed

Copy link
Contributor

@fejta fejta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🍾 very cool

A couple comments

Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Privacy string `json:"privacy,omitempty"`
Parent *Team `json:"parent,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe note with a comment that this is only relevant for responses

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -1077,7 +1077,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, 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will you s/0/tc.parentID and ensure cover this in "do not patch team when values are the same" and have a "patch team when parent changes"

@@ -675,6 +721,11 @@ func configureTeam(client editTeamClient, orgName, teamName string, team org.Tea
} else {
gt.Privacy = ""
}
if parent > 0 && gt.ParentTeamID != &parent {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I make a child team into a top-level team by later setting its parentID to 0? I think so, in which case we probably don't need the parent > 0 check?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no.. you actually need to send parent_team_id: null for that to work. removed the omitempty so it will handle this case.

@cblecker
Copy link
Member Author

@fejta Addressed comments, but having some issues with the tests.. would you mind having a look at 9a6de59 ? there might be something dumb and obvious I'm missing.. both the test cases you call out are failing:

--- FAIL: TestConfigureTeam (0.00s)
    --- FAIL: TestConfigureTeam/patch_team_when_parent_changes (0.00s)
        main_test.go:1130: actual {3 whatever   0xc420129680 <nil>} != expected {3 whatever   <nil> 0xc42023b0b0}
    --- FAIL: TestConfigureTeam/do_not_patch_team_when_values_are_the_same (0.00s)
        main_test.go:1125: unexpected error: failed to edit random-org team 4(fail): injected name failure

@@ -655,7 +701,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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand the motivation behind / value of changing this to parent *int instead of int.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 is technically a valid parent team ID. We need to be able to tell the difference between 0 and nil.

Copy link
Contributor

Choose a reason for hiding this comment

The 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?

@@ -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 {
team.Parent = &github.Team{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong lvalue

Copy link
Member Author

@cblecker cblecker Jun 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused.. I don't think it is. The goal here in the fake client is to take ParentTeamID, and set the Parent struct to contain a fake team with that matching ID.

EDIT: I get it now.

cases := []struct {
name string
err bool
teamName string
parent *int
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where do you use this value?

config: org.Team{},
github: github.Team{
ID: 3,
Name: whatev,
Copy link
Contributor

Choose a reason for hiding this comment

The 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 config.Name != github.Name also differ it is no longer clear that ParentTeamID change causes an update (might just be luck due to the name diff).

Name: whatev,
Parent: &github.Team{
ID: 4,
Name: "sup",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically Name is unnecessary here. Consider removing for clarity

@cblecker
Copy link
Member Author

@fejta oooookay. I think I got this figured out, and the tests working. PTAL, and I'll squash after.. thanks again for the pointers! :)

@fejta
Copy link
Contributor

fejta commented Jun 30, 2018

commit includes an emoji, auto
/approve

@k8s-ci-robot k8s-ci-robot added approved Indicates a PR has been approved by an approver from all required OWNERS files. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. labels Jun 30, 2018
@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Jun 30, 2018
Copy link
Contributor

@fejta fejta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm
/hold

@k8s-ci-robot k8s-ci-robot removed the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Jun 30, 2018
@cblecker cblecker changed the title [WIP] Add support for GitHub Nested Teams Add support for GitHub Nested Teams 🐣 Jun 30, 2018
@k8s-ci-robot k8s-ci-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jun 30, 2018
@cblecker
Copy link
Member Author

Squashified! :shipit:

@BenTheElder
Copy link
Member

/test pull-test-infra-bazel-canary

@BenTheElder
Copy link
Member

BenTheElder commented Jun 30, 2018 via email

return t
}

out.Teams = make(map[string]org.Team, len(tops))
Copy link
Member Author

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

@BenTheElder
Copy link
Member

/test pull-test-infra-bazel-canary

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Jun 30, 2018
Copy link
Contributor

@fejta fejta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: cblecker, fejta

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@cblecker
Copy link
Member Author

/hold cancel

@k8s-ci-robot k8s-ci-robot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jun 30, 2018
@fejta
Copy link
Contributor

fejta commented Jun 30, 2018

/retest

@k8s-ci-robot k8s-ci-robot merged commit f47f634 into kubernetes:master Jun 30, 2018
@cblecker cblecker deleted the nested-teams branch July 1, 2018 00:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. area/prow Issues or PRs related to prow cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants