Skip to content

Commit 7b6b90d

Browse files
authoredMar 18, 2025··
Clean up duplicate apitokens (#529)
1 parent 790e92f commit 7b6b90d

File tree

2 files changed

+51
-13
lines changed

2 files changed

+51
-13
lines changed
 

‎controllers/apitoken_controller.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package controllers
33
import (
44
"context"
55
"fmt"
6+
"sort"
67
"time"
78

89
"github.com/go-logr/logr"
@@ -250,11 +251,23 @@ func (r *ApiTokenReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
250251

251252
var apiToken *unleashclient.ApiToken
252253
log.WithValues("tokens", len(apiTokens.Tokens)).Info("Fetched token from Unleash for ApiToken")
254+
// @TODO this is not entirely correct with regards to the environment
253255
apiTokenExistingTokens.WithLabelValues(token.Namespace, token.Name, token.Spec.Environment).Set(float64(len(apiTokens.Tokens)))
254256

257+
// Sort tokens by created_at descending
258+
// This is needed since Unleash can return tokens in any order
259+
sort.Slice(apiTokens.Tokens, func(i, j int) bool {
260+
ti, _ := time.Parse(time.RFC3339, apiTokens.Tokens[i].CreatedAt)
261+
tj, _ := time.Parse(time.RFC3339, apiTokens.Tokens[j].CreatedAt)
262+
return ti.After(tj)
263+
})
264+
255265
// Delete outdated tokens in Unleash
256266
for _, t := range apiTokens.Tokens {
257-
if token.IsEqual(t) {
267+
// Check if the token is up to date and that we have not already found an up to date token
268+
if token.IsEqual(t) && apiToken == nil {
269+
// This is the token we are looking for
270+
// Continue to the next token
258271
apiToken = &t
259272
continue
260273
}
@@ -264,17 +277,20 @@ func (r *ApiTokenReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
264277
apiTokenDeletedCounter.WithLabelValues(token.Namespace, token.Name).Inc()
265278
span.AddEvent(fmt.Sprintf("Deleting old token for %s created at %s in Unleash", t.TokenName, t.CreatedAt))
266279

280+
// If ApiTokenUpdateEnabled is false, prevent deletion of outdated tokens and subsequent creation of new tokens
281+
// This also prevents deletion of duplicate tokens.
267282
if !r.ApiTokenUpdateEnabled {
268283
log.WithValues("token", t.TokenName, "created_at", t.CreatedAt).Info("Token update is disabled in operator config")
269284

270-
// Set ApiToken so we don't create a new token if update is disabled
285+
// Prevent creation of new tokens further down
271286
if apiToken == nil {
272287
apiToken = &t
273288
}
274289

275290
continue
276291
}
277292

293+
// At this point we know that the token is outdated our duplicate and it is safe to delete it
278294
log.WithValues("token", t.TokenName, "created_at", t.CreatedAt).Info("Deleting token in Unleash for ApiToken")
279295
err = apiClient.DeleteApiToken(ctx, t.Secret)
280296
if err != nil {

‎controllers/apitoken_controller_test.go

+33-11
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ var _ = Describe("ApiToken controller", Ordered, func() {
8181

8282
existingTokens.Tokens = append(existingTokens.Tokens, unleashclient.ApiToken{
8383
Secret: ApiTokenSecret,
84-
Username: tokenRequest.Username,
84+
TokenName: tokenRequest.Username,
8585
Type: strings.ToLower(tokenRequest.Type),
8686
Environment: tokenRequest.Environment,
8787
Project: tokenRequest.Projects[0],
@@ -219,7 +219,7 @@ var _ = Describe("ApiToken controller", Ordered, func() {
219219

220220
Expect(httpmock.GetCallCountInfo()[fmt.Sprintf("POST %s", unleashclient.ApiTokensEndpoint)]).Should(Equal(1))
221221
Expect(existingTokens.Tokens).Should(HaveLen(1))
222-
Expect(existingTokens.Tokens[0].Username).Should(Equal(apiTokenCreated.ApiTokenName("unleasherator")))
222+
Expect(existingTokens.Tokens[0].TokenName).Should(Equal(apiTokenCreated.ApiTokenName("unleasherator")))
223223
Expect(existingTokens.Tokens[0].Type).Should(Equal("client"))
224224
Expect(existingTokens.Tokens[0].Environment).Should(Equal("development"))
225225
Expect(existingTokens.Tokens[0].Projects).Should(Equal([]string{"default"}))
@@ -309,7 +309,7 @@ var _ = Describe("ApiToken controller", Ordered, func() {
309309
existingTokens.Tokens = []unleashclient.ApiToken{
310310
{
311311
Secret: ApiTokenSecret,
312-
Username: apiTokenCreated.ApiTokenName("unleasherator"),
312+
TokenName: apiTokenCreated.ApiTokenName("unleasherator"),
313313
Type: "CLIENT",
314314
Environment: "development",
315315
Project: "default",
@@ -386,7 +386,7 @@ var _ = Describe("ApiToken controller", Ordered, func() {
386386
})
387387

388388
Context("When dealing with duplicate Unleash tokens", func() {
389-
It("Should count duplicate tokens", func() {
389+
It("Should delete duplicate tokens", func() {
390390
ctx := context.Background()
391391

392392
apiTokenName := "test-apitoken-duplicate"
@@ -401,23 +401,32 @@ var _ = Describe("ApiToken controller", Ordered, func() {
401401

402402
existingTokens.Tokens = []unleashclient.ApiToken{
403403
{
404-
Secret: ApiTokenSecret,
405-
Username: "test-apitoken-duplicate-unleasherator",
404+
Secret: ApiTokenSecret + "-1",
405+
TokenName: "test-apitoken-duplicate-unleasherator",
406406
Type: "CLIENT",
407407
Environment: "development",
408408
Project: "default",
409409
Projects: []string{"default"},
410-
CreatedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC).Format(time.RFC3339),
410+
CreatedAt: time.Date(2021, 1, 1, 2, 0, 0, 0, time.UTC).Format(time.RFC3339),
411411
},
412412
{
413-
Secret: ApiTokenSecret,
414-
Username: "test-apitoken-duplicate-unleasherator",
413+
Secret: ApiTokenSecret + "-2",
414+
TokenName: "test-apitoken-duplicate-unleasherator",
415415
Type: "CLIENT",
416-
Environment: "development",
416+
Environment: "production",
417417
Project: "default",
418418
Projects: []string{"default"},
419419
CreatedAt: time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC).Format(time.RFC3339),
420420
},
421+
{
422+
Secret: ApiTokenSecret + "-3",
423+
TokenName: "test-apitoken-duplicate-unleasherator",
424+
Type: "CLIENT",
425+
Environment: "development",
426+
Project: "default",
427+
Projects: []string{"default"},
428+
CreatedAt: time.Date(2021, 1, 1, 3, 0, 0, 0, time.UTC).Format(time.RFC3339),
429+
},
421430
}
422431

423432
By("By creating a new ApiToken")
@@ -427,7 +436,20 @@ var _ = Describe("ApiToken controller", Ordered, func() {
427436

428437
fmt.Printf("existingTokens=%+v\n", existingTokens)
429438

430-
Expect(promGaugeVecVal(apiTokenExistingTokens, ApiTokenNamespace, apiTokenName, "development")).Should(Equal(2.0))
439+
Expect(promGaugeVecVal(apiTokenExistingTokens, ApiTokenNamespace, apiTokenName, "development")).Should(Equal(3.0))
440+
Expect(promCounterVecVal(apiTokenDeletedCounter, ApiTokenNamespace, apiTokenName)).Should(Equal(2.0))
441+
442+
Expect(httpmock.GetCallCountInfo()[fmt.Sprintf("DELETE %s", fmt.Sprintf("=~%s/.*", unleashclient.ApiTokensEndpoint))]).Should(Equal(2))
443+
444+
// Verify that the older tokens were deleted
445+
for _, token := range existingTokens.Tokens {
446+
Expect(token.Secret).ShouldNot(Equal(ApiTokenSecret + "-1"))
447+
Expect(token.Secret).ShouldNot(Equal(ApiTokenSecret + "-2"))
448+
}
449+
450+
// Verify that we kept the newest duplicate token
451+
Expect(existingTokens.Tokens).Should(HaveLen(1))
452+
Expect(existingTokens.Tokens[0].Secret).Should(Equal(ApiTokenSecret + "-3"))
431453
})
432454
})
433455
})

0 commit comments

Comments
 (0)
Please sign in to comment.