Skip to content

Commit

Permalink
refactor reconciler resources => reconciler state
Browse files Browse the repository at this point in the history
  • Loading branch information
christeredvartsen committed Feb 14, 2024
1 parent 7cb0920 commit 2e85482
Show file tree
Hide file tree
Showing 15 changed files with 264 additions and 329 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/google/go-github/v50 v50.2.0
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
github.com/nais/api v0.0.0-20240209152755-005fb86efda4
github.com/nais/api v0.0.0-20240214105006-15e74222a0ff
github.com/nais/dependencytrack v0.0.0-20240212045319-10e523c017ff
github.com/prometheus/client_golang v1.18.0
github.com/sethvargo/go-envconfig v1.0.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/nais/api v0.0.0-20240209152755-005fb86efda4 h1:T6qv3GBE56cXGo1z3axRfxQsakLCY83mAfoPvjnEgvc=
github.com/nais/api v0.0.0-20240209152755-005fb86efda4/go.mod h1:Uvltp+8JGkw0EpYBUvkue24FHpttnJQSa0K+//xme8w=
github.com/nais/api v0.0.0-20240214105006-15e74222a0ff h1:MpRTN6EhV4m93Cr+967D6Wb5Ls4CGl4I1Oyah9Ussz0=
github.com/nais/api v0.0.0-20240214105006-15e74222a0ff/go.mod h1:0Sszi+reZvdVuiO2+SYKf6+q2+hFWSTUAGMwPrSFOQU=
github.com/nais/dependencytrack v0.0.0-20240212045319-10e523c017ff h1:vEBRGIUHbRw9Df/ajxggBe6ef31VdK+zetms7pE9mhA=
github.com/nais/dependencytrack v0.0.0-20240212045319-10e523c017ff/go.mod h1:tO3PgO7achftJKGgTBNAf4rARTa4ICsoxUwQOyuScmw=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
Expand Down
28 changes: 14 additions & 14 deletions internal/reconcilers/dependencytrack/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (r *reconciler) Name() string {
}

func (r *reconciler) Reconcile(ctx context.Context, client *apiclient.APIClient, naisTeam *protoapi.Team, log logrus.FieldLogger) error {
state, err := r.loadState(ctx, client, naisTeam.Slug)
st, err := r.loadState(ctx, client, naisTeam.Slug)
if err != nil {
return err
}
Expand All @@ -75,14 +75,14 @@ func (r *reconciler) Reconcile(ctx context.Context, client *apiclient.APIClient,
return err
}

teamId, err := r.syncTeamAndUsers(ctx, client, naisTeam.Slug, teamMembers, state, log)
teamId, err := r.syncTeamAndUsers(ctx, client, naisTeam.Slug, teamMembers, st, log)
if err != nil {
return err
}

updatedState := &dependencyTrackState{
teamID: teamId,
members: func(members []*protoapi.TeamMember) []string {
updatedState := &DependencyTrackState{
TeamID: teamId,
Members: func(members []*protoapi.TeamMember) []string {
emails := make([]string, len(members))
for i, member := range members {
emails[i] = member.User.Email
Expand All @@ -99,12 +99,12 @@ func (r *reconciler) Reconcile(ctx context.Context, client *apiclient.APIClient,
}

func (r *reconciler) Delete(ctx context.Context, client *apiclient.APIClient, naisTeam *protoapi.Team, log logrus.FieldLogger) error {
state, err := r.loadState(ctx, client, naisTeam.Slug)
s, err := r.loadState(ctx, client, naisTeam.Slug)
if err != nil {
return err
}

if err := r.client.DeleteTeam(ctx, state.teamID); err != nil {
if err := r.client.DeleteTeam(ctx, s.TeamID); err != nil {
return err
}

Expand All @@ -115,19 +115,19 @@ func (r *reconciler) Delete(ctx context.Context, client *apiclient.APIClient, na
return nil
}

func (r *reconciler) syncTeamAndUsers(ctx context.Context, client *apiclient.APIClient, teamSlug string, naisTeamMembers []*protoapi.TeamMember, state *dependencyTrackState, log logrus.FieldLogger) (string, error) {
if state != nil && state.teamID != "" {
func (r *reconciler) syncTeamAndUsers(ctx context.Context, client *apiclient.APIClient, teamSlug string, naisTeamMembers []*protoapi.TeamMember, st *DependencyTrackState, log logrus.FieldLogger) (string, error) {
if st != nil && st.TeamID != "" {
log.Debugf("team has existing state")
for _, member := range naisTeamMembers {
if !slices.Contains(state.members, member.User.Email) {
if !slices.Contains(st.Members, member.User.Email) {
log := log.WithField("email", member.User.Email)
log.Debugf("creating user in DependencyTrack")
if err := r.client.CreateOidcUser(ctx, member.User.Email); err != nil {
return "", err
}

log.Debugf("adding user to team in DependencyTrack")
if err := r.client.AddToTeam(ctx, member.User.Email, state.teamID); err != nil {
if err := r.client.AddToTeam(ctx, member.User.Email, st.TeamID); err != nil {
return "", err
}

Expand All @@ -143,10 +143,10 @@ func (r *reconciler) syncTeamAndUsers(ctx context.Context, client *apiclient.API
}
}

for _, email := range state.members {
for _, email := range st.Members {
if !inputMembersContains(naisTeamMembers, email) {
log.WithField("email", email).Debugf("removing user from team in DependencyTrack")
if err := r.client.DeleteUserMembership(ctx, state.teamID, email); err != nil {
if err := r.client.DeleteUserMembership(ctx, st.TeamID, email); err != nil {
return "", err
}

Expand All @@ -162,7 +162,7 @@ func (r *reconciler) syncTeamAndUsers(ctx context.Context, client *apiclient.API
}
}

return state.teamID, nil
return st.TeamID, nil
}

log.Debugf("team does not yet exist in DependencyTrack, creating")
Expand Down
150 changes: 66 additions & 84 deletions internal/reconcilers/dependencytrack/reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package dependencytrack_reconciler_test

import (
"context"
"encoding/json"
"fmt"
"testing"

"github.com/google/uuid"
Expand Down Expand Up @@ -61,8 +63,8 @@ func TestDependencytrackReconciler_Reconcile(t *testing.T) {

apiClient, grpcServers := apiclient.NewMockClient(t)
grpcServers.Reconcilers.EXPECT().
Resources(mock.Anything, &protoapi.ListReconcilerResourcesRequest{ReconcilerName: "nais:dependencytrack", TeamSlug: teamSlug}).
Return(&protoapi.ListReconcilerResourcesResponse{}, nil).
State(mock.Anything, &protoapi.GetReconcilerStateRequest{ReconcilerName: "nais:dependencytrack", TeamSlug: teamSlug}).
Return(&protoapi.GetReconcilerStateResponse{}, nil).
Once()
grpcServers.Teams.EXPECT().
Members(mock.Anything, &protoapi.ListTeamMembersRequest{Slug: teamSlug, Limit: 100, Offset: 0}).
Expand All @@ -75,17 +77,17 @@ func TestDependencytrackReconciler_Reconcile(t *testing.T) {
}}, nil).
Once()
grpcServers.Reconcilers.EXPECT().
SaveResources(mock.Anything, &protoapi.SaveReconcilerResourceRequest{ReconcilerName: "nais:dependencytrack", TeamSlug: teamSlug, Resources: []*protoapi.NewReconcilerResource{
{
Name: "team_id",
Value: []byte(teamID),
},
{
Name: "members",
Value: []byte(user),
},
}}).
Return(&protoapi.SaveReconcilerResourceResponse{}, nil).
SaveState(mock.Anything, mock.MatchedBy(func(req *protoapi.SaveReconcilerStateRequest) bool {
st := dependencytrack_reconciler.DependencyTrackState{}
_ = json.Unmarshal(req.Value, &st)

return req.ReconcilerName == "nais:dependencytrack" &&
req.TeamSlug == teamSlug &&
st.TeamID == teamID &&
len(st.Members) == 1 &&
st.Members[0] == user
})).
Return(&protoapi.SaveReconcilerStateResponse{}, nil).
Once()
grpcServers.AuditLogs.EXPECT().
Create(mock.Anything, mock.MatchedBy(func(r *protoapi.CreateAuditLogsRequest) bool {
Expand Down Expand Up @@ -124,13 +126,10 @@ func TestDependencytrackReconciler_Reconcile(t *testing.T) {

apiClient, grpcServers := apiclient.NewMockClient(t)
grpcServers.Reconcilers.EXPECT().
Resources(mock.Anything, &protoapi.ListReconcilerResourcesRequest{ReconcilerName: "nais:dependencytrack", TeamSlug: teamSlug}).
Return(&protoapi.ListReconcilerResourcesResponse{
Nodes: []*protoapi.ReconcilerResource{
{
Name: "team_id",
Value: []byte(teamID),
},
State(mock.Anything, &protoapi.GetReconcilerStateRequest{ReconcilerName: "nais:dependencytrack", TeamSlug: teamSlug}).
Return(&protoapi.GetReconcilerStateResponse{
State: &protoapi.ReconcilerState{
Value: []byte(fmt.Sprintf(`{"teamId": %q}`, teamID)),
},
}, nil).
Once()
Expand All @@ -145,17 +144,17 @@ func TestDependencytrackReconciler_Reconcile(t *testing.T) {
}}, nil).
Once()
grpcServers.Reconcilers.EXPECT().
SaveResources(mock.Anything, &protoapi.SaveReconcilerResourceRequest{ReconcilerName: "nais:dependencytrack", TeamSlug: teamSlug, Resources: []*protoapi.NewReconcilerResource{
{
Name: "team_id",
Value: []byte(teamID),
},
{
Name: "members",
Value: []byte(user),
},
}}).
Return(&protoapi.SaveReconcilerResourceResponse{}, nil).
SaveState(mock.Anything, mock.MatchedBy(func(req *protoapi.SaveReconcilerStateRequest) bool {
st := dependencytrack_reconciler.DependencyTrackState{}
_ = json.Unmarshal(req.Value, &st)

return req.ReconcilerName == "nais:dependencytrack" &&
req.TeamSlug == teamSlug &&
st.TeamID == teamID &&
len(st.Members) == 1 &&
st.Members[0] == user
})).
Return(&protoapi.SaveReconcilerStateResponse{}, nil).
Once()
grpcServers.AuditLogs.EXPECT().
Create(mock.Anything, mock.MatchedBy(func(r *protoapi.CreateAuditLogsRequest) bool {
Expand All @@ -180,17 +179,10 @@ func TestDependencytrackReconciler_Reconcile(t *testing.T) {

apiClient, grpcServers := apiclient.NewMockClient(t)
grpcServers.Reconcilers.EXPECT().
Resources(mock.Anything, &protoapi.ListReconcilerResourcesRequest{ReconcilerName: "nais:dependencytrack", TeamSlug: teamSlug}).
Return(&protoapi.ListReconcilerResourcesResponse{
Nodes: []*protoapi.ReconcilerResource{
{
Name: "team_id",
Value: []byte(teamID),
},
{
Name: "members",
Value: []byte(user),
},
State(mock.Anything, &protoapi.GetReconcilerStateRequest{ReconcilerName: "nais:dependencytrack", TeamSlug: teamSlug}).
Return(&protoapi.GetReconcilerStateResponse{
State: &protoapi.ReconcilerState{
Value: []byte(fmt.Sprintf(`{"teamId": %q, "members": [%q]}`, teamID, user)),
},
}, nil).
Once()
Expand All @@ -205,17 +197,17 @@ func TestDependencytrackReconciler_Reconcile(t *testing.T) {
}}, nil).
Once()
grpcServers.Reconcilers.EXPECT().
SaveResources(mock.Anything, &protoapi.SaveReconcilerResourceRequest{ReconcilerName: "nais:dependencytrack", TeamSlug: teamSlug, Resources: []*protoapi.NewReconcilerResource{
{
Name: "team_id",
Value: []byte(teamID),
},
{
Name: "members",
Value: []byte(user),
},
}}).
Return(&protoapi.SaveReconcilerResourceResponse{}, nil).
SaveState(mock.Anything, mock.MatchedBy(func(req *protoapi.SaveReconcilerStateRequest) bool {
st := dependencytrack_reconciler.DependencyTrackState{}
_ = json.Unmarshal(req.Value, &st)

return req.ReconcilerName == "nais:dependencytrack" &&
req.TeamSlug == teamSlug &&
st.TeamID == teamID &&
len(st.Members) == 1 &&
st.Members[0] == user
})).
Return(&protoapi.SaveReconcilerStateResponse{}, nil).
Once()

reconciler, err := dependencytrack_reconciler.New("", "", "", dependencytrack_reconciler.WithDependencyTrackClient(dpClient))
Expand Down Expand Up @@ -248,17 +240,10 @@ func TestDependencytrackReconciler_Reconcile(t *testing.T) {

apiClient, grpcServers := apiclient.NewMockClient(t)
grpcServers.Reconcilers.EXPECT().
Resources(mock.Anything, &protoapi.ListReconcilerResourcesRequest{ReconcilerName: "nais:dependencytrack", TeamSlug: teamSlug}).
Return(&protoapi.ListReconcilerResourcesResponse{
Nodes: []*protoapi.ReconcilerResource{
{
Name: "team_id",
Value: []byte(teamID),
},
{
Name: "members",
Value: []byte(unknownMember),
},
State(mock.Anything, &protoapi.GetReconcilerStateRequest{ReconcilerName: "nais:dependencytrack", TeamSlug: teamSlug}).
Return(&protoapi.GetReconcilerStateResponse{
State: &protoapi.ReconcilerState{
Value: []byte(fmt.Sprintf(`{"teamId": %q, "members": [%q]}`, teamID, unknownMember)),
},
}, nil).
Once()
Expand All @@ -273,17 +258,17 @@ func TestDependencytrackReconciler_Reconcile(t *testing.T) {
}}, nil).
Once()
grpcServers.Reconcilers.EXPECT().
SaveResources(mock.Anything, &protoapi.SaveReconcilerResourceRequest{ReconcilerName: "nais:dependencytrack", TeamSlug: teamSlug, Resources: []*protoapi.NewReconcilerResource{
{
Name: "team_id",
Value: []byte(teamID),
},
{
Name: "members",
Value: []byte(user),
},
}}).
Return(&protoapi.SaveReconcilerResourceResponse{}, nil).
SaveState(mock.Anything, mock.MatchedBy(func(req *protoapi.SaveReconcilerStateRequest) bool {
st := dependencytrack_reconciler.DependencyTrackState{}
_ = json.Unmarshal(req.Value, &st)

return req.ReconcilerName == "nais:dependencytrack" &&
req.TeamSlug == teamSlug &&
st.TeamID == teamID &&
len(st.Members) == 1 &&
st.Members[0] == user
})).
Return(&protoapi.SaveReconcilerStateResponse{}, nil).
Once()
grpcServers.AuditLogs.EXPECT().
Create(mock.Anything, mock.MatchedBy(func(r *protoapi.CreateAuditLogsRequest) bool {
Expand Down Expand Up @@ -326,19 +311,16 @@ func TestDependencytrackReconciler_Delete(t *testing.T) {

apiClient, grpcServers := apiclient.NewMockClient(t)
grpcServers.Reconcilers.EXPECT().
Resources(mock.Anything, &protoapi.ListReconcilerResourcesRequest{ReconcilerName: "nais:dependencytrack", TeamSlug: teamSlug}).
Return(&protoapi.ListReconcilerResourcesResponse{
Nodes: []*protoapi.ReconcilerResource{
{
Name: "team_id",
Value: []byte(teamID),
},
State(mock.Anything, &protoapi.GetReconcilerStateRequest{ReconcilerName: "nais:dependencytrack", TeamSlug: teamSlug}).
Return(&protoapi.GetReconcilerStateResponse{
State: &protoapi.ReconcilerState{
Value: []byte(fmt.Sprintf(`{"teamId": %q}`, teamID)),
},
}, nil).
Once()
grpcServers.Reconcilers.EXPECT().
DeleteResources(mock.Anything, &protoapi.DeleteReconcilerResourcesRequest{ReconcilerName: "nais:dependencytrack", TeamSlug: teamSlug}).
Return(&protoapi.DeleteReconcilerResourcesResponse{}, nil).
DeleteState(mock.Anything, &protoapi.DeleteReconcilerStateRequest{ReconcilerName: "nais:dependencytrack", TeamSlug: teamSlug}).
Return(&protoapi.DeleteReconcilerStateResponse{}, nil).
Once()

reconciler, err := dependencytrack_reconciler.New("", "", "", dependencytrack_reconciler.WithDependencyTrackClient(dpClient))
Expand Down
Loading

0 comments on commit 2e85482

Please sign in to comment.