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

feat(teams): add members to team status #794

Merged
merged 11 commits into from
Dec 17, 2024
24 changes: 24 additions & 0 deletions charts/manager/crds/greenhouse.sap_teams.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,29 @@ spec:
status:
description: TeamStatus defines the observed state of Team
properties:
members:
items:
description: User specifies a human person.
properties:
email:
description: Email of the user.
type: string
firstName:
description: FirstName of the user.
type: string
id:
description: ID is the unique identifier of the user.
type: string
lastName:
description: LastName of the user.
type: string
required:
- email
- firstName
- id
- lastName
type: object
type: array
statusConditions:
description: |-
A StatusConditions contains a list of conditions.
Expand Down Expand Up @@ -105,6 +128,7 @@ spec:
x-kubernetes-list-type: map
type: object
required:
- members
- statusConditions
type: object
type: object
Expand Down
8 changes: 4 additions & 4 deletions charts/manager/templates/manager-role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ rules:
- ""
resources:
- events
- namespaces
- secrets
- serviceaccounts
verbs:
- create
- delete
- get
- list
- patch
Expand All @@ -20,10 +20,10 @@ rules:
- apiGroups:
- ""
resources:
- secrets
- serviceaccounts
- namespaces
verbs:
- create
- delete
- get
- list
- patch
Expand Down
15 changes: 14 additions & 1 deletion docs/reference/api/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3456,6 +3456,18 @@ <h3 id="greenhouse.sap/v1alpha1.TeamStatus">TeamStatus
<td>
</td>
</tr>
<tr>
<td>
<code>members</code><br>
<em>
<a href="#greenhouse.sap/v1alpha1.User">
[]User
</a>
</em>
</td>
<td>
</td>
</tr>
</tbody>
</table>
</div>
Expand Down Expand Up @@ -3520,7 +3532,8 @@ <h3 id="greenhouse.sap/v1alpha1.User">User
</h3>
<p>
(<em>Appears on:</em>
<a href="#greenhouse.sap/v1alpha1.TeamMembershipSpec">TeamMembershipSpec</a>)
<a href="#greenhouse.sap/v1alpha1.TeamMembershipSpec">TeamMembershipSpec</a>,
<a href="#greenhouse.sap/v1alpha1.TeamStatus">TeamStatus</a>)
</p>
<p>User specifies a human person.</p>
<div class="md-typeset__scrollwrap">
Expand Down
24 changes: 24 additions & 0 deletions docs/reference/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,29 @@ components:
status:
description: TeamStatus defines the observed state of Team
properties:
members:
items:
description: User specifies a human person.
properties:
email:
description: Email of the user.
type: string
firstName:
description: FirstName of the user.
type: string
id:
description: ID is the unique identifier of the user.
type: string
lastName:
description: LastName of the user.
type: string
required:
- email
- firstName
- id
- lastName
type: object
type: array
statusConditions:
description: A StatusConditions contains a list of conditions.\nOnly one condition of a given type may exist in the list.
properties:
Expand Down Expand Up @@ -1141,6 +1164,7 @@ components:
x-kubernetes-list-type: map
type: object
required:
- members
- statusConditions
type: object
type: object
Expand Down
1 change: 1 addition & 0 deletions pkg/apis/greenhouse/v1alpha1/team_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type TeamSpec struct {
// TeamStatus defines the observed state of Team
type TeamStatus struct {
StatusConditions StatusConditions `json:"statusConditions"`
Members []User `json:"members"`
}

//+kubebuilder:object:root=true
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/greenhouse/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func listTeamsAsReconcileRequests(ctx context.Context, c client.Client, listOpts
}

func (r *TeamMembershipUpdaterController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
return lifecycle.Reconcile(ctx, r.Client, req.NamespacedName, &greenhousev1alpha1.Team{}, r, nil) // status function is nil because it updates the other entity inside.
return lifecycle.Reconcile(ctx, r.Client, req.NamespacedName, &greenhousev1alpha1.Team{}, r, nil)
}

func (r *TeamMembershipUpdaterController) EnsureDeleted(_ context.Context, _ lifecycle.RuntimeObject) (ctrl.Result, lifecycle.ReconcileResult, error) {
Expand Down Expand Up @@ -187,6 +187,7 @@ func (r *TeamMembershipUpdaterController) EnsureCreated(ctx context.Context, obj
return ctrl.Result{}, lifecycle.Failed, err
}

team.Status.Members = users
teamMembershipStatus.SetConditions(membersValidCondition, greenhousev1alpha1.TrueCondition(greenhousev1alpha1.SCIMAccessReadyCondition, "", ""))

membersCountMetric.With(prometheus.Labels{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,20 @@ var _ = Describe("TeammembershipUpdaterController", Ordered, func() {
g.Expect(readyCondition.Type).To(Equal(greenhousev1alpha1.ReadyCondition))
g.Expect(readyCondition.Status).To(Equal(metav1.ConditionTrue))
}).Should(Succeed(), "TeamMembership should be reconciled")

Eventually(func(g Gomega) {
err := setup.Get(test.Ctx, client.ObjectKeyFromObject(team), team)
g.Expect(err).ShouldNot(HaveOccurred(), "unexpected error getting Team")
g.Expect(team.Status.Members).To(HaveLen(2), "the Team should have exactly two Members")
}).Should(Succeed(), "Team should have the team members")
})

It("should update existing TM without users", func() {
By("creating a test TeamMembership")
createTeamMembershipForFirstTeam(nil)

By("creating a test Team")
createFirstTeam(validIdpGroupName)
firstTeam := createFirstTeam(validIdpGroupName)

By("ensuring the TeamMembership has been reconciled")
Eventually(func(g Gomega) {
Expand All @@ -96,6 +102,13 @@ var _ = Describe("TeammembershipUpdaterController", Ordered, func() {
g.Expect(readyCondition.Type).To(Equal(greenhousev1alpha1.ReadyCondition))
g.Expect(readyCondition.Status).To(Equal(metav1.ConditionTrue))
}).Should(Succeed(), "the TeamMembership should be reconciled")

By("ensuring that the Team has been updated")
Eventually(func(g Gomega) {
err := setup.Get(test.Ctx, client.ObjectKeyFromObject(firstTeam), firstTeam)
g.Expect(err).ShouldNot(HaveOccurred(), "unexpected error getting Team")
g.Expect(firstTeam.Status.Members).To(HaveLen(2), "the TeamMembership should have exactly two Members")
}).Should(Succeed(), "Team should have the team members")
})

It("should update existing TM with users", func() {
Expand All @@ -110,7 +123,7 @@ var _ = Describe("TeammembershipUpdaterController", Ordered, func() {
})

By("creating a test Team")
createFirstTeam(validIdpGroupName)
firstTeam := createFirstTeam(validIdpGroupName)

By("ensuring the TeamMembership has been reconciled")
Eventually(func(g Gomega) {
Expand All @@ -121,6 +134,13 @@ var _ = Describe("TeammembershipUpdaterController", Ordered, func() {
g.Expect(teamMemberships.Items[0].Spec.Members).To(HaveLen(2), "the TeamMembership should have exactly two Members")
g.Expect(teamMemberships.Items[0].Status.LastChangedTime).ToNot(BeNil(), "TeamMembership status should have updated LastChangedTime")
}).Should(Succeed(), "the TeamMembership should be reconciled")

By("ensuring that the Team has been updated")
Eventually(func(g Gomega) {
err := setup.Get(test.Ctx, client.ObjectKeyFromObject(firstTeam), firstTeam)
g.Expect(err).ShouldNot(HaveOccurred(), "unexpected error getting Team")
g.Expect(firstTeam.Status.Members).To(HaveLen(2), "the TeamMembership should have exactly two Members")
}).Should(Succeed(), "Team should have the team members")
})

It("should update multiple TMs", func() {
Expand Down Expand Up @@ -172,6 +192,20 @@ var _ = Describe("TeammembershipUpdaterController", Ordered, func() {
g.Expect(teamMemberships.Items[0].OwnerReferences).To(ContainElement(firstOwnerRef), "first TeamMembership should have set first team as owner reference")
g.Expect(teamMemberships.Items[1].OwnerReferences).To(ContainElement(secondOwnerRef), "second TeamMembership should have set second team as owner reference")
}).Should(Succeed(), "both TeamMemberships should be reconciled")

By("ensuring that the first Team has been updated")
Eventually(func(g Gomega) {
err := setup.Get(test.Ctx, client.ObjectKeyFromObject(firstTeam), firstTeam)
g.Expect(err).ShouldNot(HaveOccurred(), "unexpected error getting Team")
g.Expect(firstTeam.Status.Members).To(HaveLen(2), "the first team should have exactly two Members")
}).Should(Succeed(), "First team should have the team members")

By("ensuring that the second Team has been updated")
Eventually(func(g Gomega) {
err := setup.Get(test.Ctx, client.ObjectKeyFromObject(secondTeam), secondTeam)
g.Expect(err).ShouldNot(HaveOccurred(), "unexpected error getting Team")
g.Expect(secondTeam.Status.Members).To(HaveLen(3), "the second team should have exactly three Members")
}).Should(Succeed(), "Second team should have the team members")
})

It("should do nothing if Team has no mappedIdpGroup", func() {
Expand Down Expand Up @@ -367,7 +401,7 @@ var _ = Describe("TeammembershipUpdaterController", Ordered, func() {
createTeamMembershipForFirstTeam(originalUsers)

By("creating test Team with valid idp group")
createFirstTeam(validIdpGroupName)
firstTeam := createFirstTeam(validIdpGroupName)

expectedUser1 := greenhousev1alpha1.User{
ID: "I12345",
Expand All @@ -393,6 +427,13 @@ var _ = Describe("TeammembershipUpdaterController", Ordered, func() {
g.Expect(teamMembership.Spec.Members).To(ContainElement(expectedUser1), "TeamMembership users should contain first expected user")
g.Expect(teamMembership.Spec.Members).To(ContainElement(expectedUser2), "TeamMembership users should contain second expected user")
}).Should(Succeed(), "TeamMembership should have been reconciled")

Eventually(func(g Gomega) {
err := setup.Get(test.Ctx, client.ObjectKeyFromObject(firstTeam), firstTeam)
g.Expect(err).ShouldNot(HaveOccurred(), "unexpected error getting firstTeam")
g.Expect(firstTeam.Status.Members).To(HaveLen(2), "firstTeam should have two users")
g.Expect(firstTeam.Status.Members).To(BeEquivalentTo([]greenhousev1alpha1.User{expectedUser1, expectedUser2}), "firstTeam should contains users")
}).Should(Succeed(), "the team status should be updated")
})

It("should delete the team after all", func() {
Expand Down
1 change: 1 addition & 0 deletions test/e2e/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var knownControllers = map[string]func(controllerName string, mgr ctrl.Manager)
"organizationTeamRoleSeeder": (&organizationcontrollers.TeamRoleSeederReconciler{}).SetupWithManager,

// Team controllers.
"teamController": (&teamcontrollers.TeamReconciler{}).SetupWithManager,
"teamPropagation": (&teamcontrollers.TeamPropagationReconciler{}).SetupWithManager,

// TeamMembership controllers.
Expand Down
Loading