Skip to content

Commit b9e97db

Browse files
authored
Bug fixes to argocd_project_token + addition of renew_after (#245)
1 parent d291579 commit b9e97db

File tree

6 files changed

+184
-113
lines changed

6 files changed

+184
-113
lines changed

argocd/provider.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ import (
2323
_ "k8s.io/client-go/plugin/pkg/client/auth"
2424
)
2525

26-
var apiClientConnOpts apiclient.ClientOptions
27-
2826
// Used to handle concurrent access to ArgoCD common configuration
2927
var tokenMutexConfiguration = &sync.RWMutex{}
3028

@@ -336,9 +334,6 @@ func initApiClient(ctx context.Context, d *schema.ResourceData) (apiClient apicl
336334
}
337335
}
338336

339-
// Export provider API client connections options for use in other spawned api clients
340-
apiClientConnOpts = opts
341-
342337
authToken, authTokenOk := d.GetOk("auth_token")
343338
switch authTokenOk {
344339
case true:

argocd/resource_argocd_project_token.go

Lines changed: 80 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,77 @@ func resourceArgoCDProjectToken() *schema.Resource {
2222
ReadContext: resourceArgoCDProjectTokenRead,
2323
UpdateContext: resourceArgoCDProjectTokenUpdate,
2424
DeleteContext: resourceArgoCDProjectTokenDelete,
25+
CustomizeDiff: func(ctx context.Context, d *schema.ResourceDiff, m interface{}) error {
26+
ia := d.Get("issued_at").(string)
27+
if ia == "" {
28+
// Blank issued_at indicates a new token - nothing to do here
29+
return nil
30+
}
31+
32+
issuedAt, err := convertStringToInt64(ia)
33+
if err != nil {
34+
return fmt.Errorf("invalid issued_at: %w", err)
35+
}
36+
37+
if ra, ok := d.GetOk("renew_after"); ok {
38+
renewAfterDuration, err := time.ParseDuration(ra.(string))
39+
if err != nil {
40+
return fmt.Errorf("invalid renew_after: %w", err)
41+
}
42+
43+
if time.Now().Unix()-issuedAt > int64(renewAfterDuration.Seconds()) {
44+
// Token is older than renewAfterDuration - force recreation
45+
if err := d.SetNewComputed("issued_at"); err != nil {
46+
return fmt.Errorf("failed to force new resource on field %q: %w", "issued_at", err)
47+
}
48+
49+
return nil
50+
}
51+
}
52+
53+
ea, ok := d.GetOk("expires_at")
54+
if !ok {
55+
return nil
56+
}
57+
58+
expiresAt, err := convertStringToInt64(ea.(string))
59+
if err != nil {
60+
return fmt.Errorf("invalid expires_at: %w", err)
61+
}
62+
63+
if expiresAt == 0 {
64+
// Token not set to expire - no need to check anything else
65+
return nil
66+
}
67+
68+
if expiresAt < time.Now().Unix() {
69+
// Token has expired - force recreation
70+
if err := d.SetNewComputed("expires_at"); err != nil {
71+
return fmt.Errorf("failed to force new resource on field %q: %w", "expires_at", err)
72+
}
73+
74+
return nil
75+
}
76+
77+
rb, ok := d.GetOk("renew_before")
78+
if !ok {
79+
return nil
80+
}
81+
82+
renewBeforeDuration, err := time.ParseDuration(rb.(string))
83+
if err != nil {
84+
return fmt.Errorf("invalid renew_before: %w", err)
85+
}
86+
87+
if expiresAt-time.Now().Unix() < int64(renewBeforeDuration.Seconds()) {
88+
// Token will expire within renewBeforeDuration - force recreation
89+
if err := d.SetNewComputed("issued_at"); err != nil {
90+
return fmt.Errorf("failed to force new resource on field %q: %w", "issued_at", err)
91+
}
92+
}
93+
94+
return nil
95+
},
2596

2697
Schema: map[string]*schema.Schema{
2798
"project": {
@@ -43,9 +114,15 @@ func resourceArgoCDProjectToken() *schema.Resource {
43114
ForceNew: true,
44115
ValidateFunc: validateDuration,
45116
},
117+
"renew_after": {
118+
Type: schema.TypeString,
119+
Description: "Duration to control token silent regeneration based on token age. Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. If set, then the token will be regenerated if it is older than `renew_after`. I.e. if `currentDate - issued_at > renew_after`.",
120+
Optional: true,
121+
ValidateFunc: validateDuration,
122+
},
46123
"renew_before": {
47124
Type: schema.TypeString,
48-
Description: "Duration to control token silent regeneration, valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. If `expires_in` is set, Terraform will regenerate the token if `expires_in - renew_before < currentDate`.",
125+
Description: "Duration to control token silent regeneration based on remaining token lifetime. If `expires_in` is set, Terraform will regenerate the token if `expires_at - currentDate < renew_before`. Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`.",
49126
Optional: true,
50127
ValidateFunc: validateDuration,
51128
RequiredWith: []string{"expires_in"},
@@ -138,6 +215,7 @@ func resourceArgoCDProjectTokenCreate(ctx context.Context, d *schema.ResourceDat
138215
}
139216

140217
renewBefore := int64(renewBeforeDuration.Seconds())
218+
141219
if renewBefore > expiresIn {
142220
return []diag.Diagnostic{
143221
{
@@ -146,16 +224,6 @@ func resourceArgoCDProjectTokenCreate(ctx context.Context, d *schema.ResourceDat
146224
},
147225
}
148226
}
149-
150-
// Arbitrary protection against misconfiguration
151-
if 300 > expiresIn-renewBefore {
152-
return []diag.Diagnostic{
153-
{
154-
Severity: diag.Error,
155-
Summary: "token will expire within 5 minutes, check your settings",
156-
},
157-
}
158-
}
159227
}
160228

161229
featureTokenIDSupported, err := server.isFeatureSupported(featureTokenIDs)
@@ -376,18 +444,6 @@ func resourceArgoCDProjectTokenRead(ctx context.Context, d *schema.ResourceData,
376444
return nil
377445
}
378446

379-
var expiresIn int64
380-
381-
var renewBefore int64
382-
383-
// TODO: Bug identified during refactoring/linting - is this really correct?
384-
// Change introduced in https://github.com/oboukili/terraform-provider-argocd/pull/12/files
385-
computedExpiresIn := expiresIn - renewBefore
386-
if err = isValidToken(token, computedExpiresIn); err != nil {
387-
d.SetId("")
388-
return nil
389-
}
390-
391447
if err = d.Set("issued_at", convertInt64ToString(token.IssuedAt)); err != nil {
392448
return []diag.Diagnostic{
393449
{
@@ -457,7 +513,7 @@ func resourceArgoCDProjectTokenUpdate(ctx context.Context, d *schema.ResourceDat
457513
}
458514
}
459515

460-
return resourceArgoCDProjectRead(ctx, d, meta)
516+
return resourceArgoCDProjectTokenRead(ctx, d, meta)
461517
}
462518

463519
func resourceArgoCDProjectTokenDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {

argocd/resource_argocd_project_token_test.go

Lines changed: 87 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package argocd
22

33
import (
44
"fmt"
5-
"math"
65
"math/rand"
76
"regexp"
87
"testing"
@@ -23,9 +22,6 @@ func TestAccArgoCDProjectToken(t *testing.T) {
2322

2423
count := 3 + rand.Intn(7)
2524
expIn1 := expiresInDurationFunc(rand.Intn(100000))
26-
expIn2 := expiresInDurationFunc(rand.Intn(100000))
27-
expIn3 := expiresInDurationFunc(rand.Intn(100000))
28-
expIn4 := expiresInDurationFunc(rand.Intn(100000))
2925

3026
resource.ParallelTest(t, resource.TestCase{
3127
PreCheck: func() { testAccPreCheck(t) },
@@ -50,27 +46,6 @@ func TestAccArgoCDProjectToken(t *testing.T) {
5046
int64(expIn1.Seconds()),
5147
),
5248
},
53-
{
54-
Config: testAccArgoCDProjectTokenMisconfiguration(expIn2),
55-
ExpectError: regexp.MustCompile("token will expire within 5 minutes, check your settings"),
56-
},
57-
{
58-
Config: testAccArgoCDProjectTokenRenewBeforeSuccess(expIn3),
59-
Check: resource.ComposeTestCheckFunc(
60-
testCheckTokenExpiresAt(
61-
"argocd_project_token.renew",
62-
int64(expIn3.Seconds()),
63-
),
64-
resource.TestCheckResourceAttrSet(
65-
"argocd_project_token.renew",
66-
"renew_before",
67-
),
68-
),
69-
},
70-
{
71-
Config: testAccArgoCDProjectTokenRenewBeforeFailure(expIn4),
72-
ExpectError: regexp.MustCompile("renew_before .* cannot be greater than expires_in .*"),
73-
},
7449
{
7550
Config: testAccArgoCDProjectTokenMultiple(count),
7651
Check: resource.ComposeTestCheckFunc(
@@ -96,6 +71,70 @@ func TestAccArgoCDProjectToken(t *testing.T) {
9671
})
9772
}
9873

74+
func TestAccArgoCDProjectToken_RenewBefore(t *testing.T) {
75+
resourceName := "argocd_project_token.renew_before"
76+
77+
expiresInSeconds := 30
78+
expiresIn := fmt.Sprintf("%ds", expiresInSeconds)
79+
expiresInDuration, _ := time.ParseDuration(expiresIn)
80+
81+
renewBeforeSeconds := expiresInSeconds - 1
82+
83+
resource.ParallelTest(t, resource.TestCase{
84+
PreCheck: func() { testAccPreCheck(t) },
85+
ProviderFactories: testAccProviders,
86+
Steps: []resource.TestStep{
87+
{
88+
Config: testAccArgoCDProjectTokenRenewBeforeSuccess(expiresIn, "20s"),
89+
Check: resource.ComposeTestCheckFunc(
90+
testCheckTokenExpiresAt(resourceName, int64(expiresInDuration.Seconds())),
91+
resource.TestCheckResourceAttr(resourceName, "renew_before", "20s"),
92+
),
93+
},
94+
{
95+
Config: testAccArgoCDProjectTokenRenewBeforeSuccess(expiresIn, fmt.Sprintf("%ds", renewBeforeSeconds)),
96+
Check: resource.ComposeTestCheckFunc(
97+
resource.TestCheckResourceAttr(resourceName, "renew_before", fmt.Sprintf("%ds", renewBeforeSeconds)),
98+
testDelay(renewBeforeSeconds+1),
99+
),
100+
ExpectNonEmptyPlan: true, // token should be recreated when refreshed at end of step due to delay above
101+
},
102+
{
103+
Config: testAccArgoCDProjectTokenRenewBeforeFailure(expiresInDuration),
104+
ExpectError: regexp.MustCompile("renew_before .* cannot be greater than expires_in .*"),
105+
},
106+
},
107+
})
108+
}
109+
110+
func TestAccArgoCDProjectToken_RenewAfter(t *testing.T) {
111+
resourceName := "argocd_project_token.renew_after"
112+
113+
renewAfterSeconds := 1
114+
115+
resource.ParallelTest(t, resource.TestCase{
116+
PreCheck: func() { testAccPreCheck(t) },
117+
ProviderFactories: testAccProviders,
118+
Steps: []resource.TestStep{
119+
{
120+
Config: testAccArgoCDProjectTokenRenewAfter(renewAfterSeconds),
121+
Check: resource.ComposeTestCheckFunc(
122+
resource.TestCheckResourceAttr(resourceName, "renew_after", fmt.Sprintf("%ds", renewAfterSeconds)),
123+
testDelay(renewAfterSeconds+1),
124+
),
125+
ExpectNonEmptyPlan: true,
126+
},
127+
{
128+
Config: testAccArgoCDProjectTokenRenewAfter(renewAfterSeconds),
129+
Check: resource.ComposeTestCheckFunc(
130+
testDelay(renewAfterSeconds),
131+
),
132+
ExpectNonEmptyPlan: true, // token should be recreated when refreshed at end of step due to delay above
133+
},
134+
},
135+
})
136+
}
137+
99138
func testAccArgoCDProjectTokenSimple() string {
100139
return `
101140
resource "argocd_project_token" "simple" {
@@ -144,29 +183,23 @@ resource "argocd_project_token" "multiple2b" {
144183
`, count, count, count, count)
145184
}
146185

147-
func testAccArgoCDProjectTokenMisconfiguration(expiresInDuration time.Duration) string {
148-
expiresIn := int64(expiresInDuration.Seconds())
149-
renewBefore := expiresIn
150-
186+
func testAccArgoCDProjectTokenRenewBeforeSuccess(expiresIn, renewBefore string) string {
151187
return fmt.Sprintf(`
152-
resource "argocd_project_token" "renew" {
188+
resource "argocd_project_token" "renew_before" {
153189
project = "myproject1"
154190
role = "test-role1234"
155-
expires_in = "%ds"
156-
renew_before = "%ds"
191+
expires_in = "%s"
192+
renew_before = "%s"
157193
}
158194
`, expiresIn, renewBefore)
159195
}
160196

161-
func testAccArgoCDProjectTokenRenewBeforeSuccess(expiresInDuration time.Duration) string {
197+
func testAccArgoCDProjectTokenRenewBeforeFailure(expiresInDuration time.Duration) string {
162198
expiresIn := int64(expiresInDuration.Seconds())
163-
renewBefore := int64(math.Min(
164-
expiresInDuration.Seconds()-1,
165-
expiresInDuration.Seconds()-(rand.Float64()*expiresInDuration.Seconds()),
166-
)) % int64(expiresInDuration.Seconds())
199+
renewBefore := int64(expiresInDuration.Seconds() + 1.0)
167200

168201
return fmt.Sprintf(`
169-
resource "argocd_project_token" "renew" {
202+
resource "argocd_project_token" "renew_before" {
170203
project = "myproject1"
171204
role = "test-role1234"
172205
expires_in = "%ds"
@@ -175,21 +208,15 @@ resource "argocd_project_token" "renew" {
175208
`, expiresIn, renewBefore)
176209
}
177210

178-
func testAccArgoCDProjectTokenRenewBeforeFailure(expiresInDuration time.Duration) string {
179-
expiresIn := int64(expiresInDuration.Seconds())
180-
renewBefore := int64(math.Max(
181-
expiresInDuration.Seconds()+1.0,
182-
expiresInDuration.Seconds()+(rand.Float64()*expiresInDuration.Seconds()),
183-
))
184-
211+
func testAccArgoCDProjectTokenRenewAfter(renewAfter int) string {
185212
return fmt.Sprintf(`
186-
resource "argocd_project_token" "renew" {
187-
project = "myproject1"
188-
role = "test-role1234"
189-
expires_in = "%ds"
190-
renew_before = "%ds"
213+
resource "argocd_project_token" "renew_after" {
214+
project = "myproject1"
215+
description = "auto-renewing long-lived token"
216+
role = "test-role1234"
217+
renew_after = "%ds"
191218
}
192-
`, expiresIn, renewBefore)
219+
`, renewAfter)
193220
}
194221

195222
func testCheckTokenIssuedAt(resourceName string) resource.TestCheckFunc {
@@ -205,12 +232,12 @@ func testCheckTokenIssuedAt(resourceName string) resource.TestCheckFunc {
205232

206233
_issuedAt, ok := rs.Primary.Attributes["issued_at"]
207234
if !ok {
208-
return fmt.Errorf("testCheckTokenExpiresAt: issued_at is not set")
235+
return fmt.Errorf("testCheckTokenIssuedAt: issued_at is not set")
209236
}
210237

211238
_, err := convertStringToInt64(_issuedAt)
212239
if err != nil {
213-
return fmt.Errorf("testCheckTokenExpiresAt: string attribute 'issued_at' stored in state cannot be converted to int64: %s", err)
240+
return fmt.Errorf("testCheckTokenIssuedAt: string attribute 'issued_at' stored in state cannot be converted to int64: %s", err)
214241
}
215242

216243
return nil
@@ -282,3 +309,10 @@ func testTokenIssuedAtSet(name string, count int) resource.TestCheckFunc {
282309
return nil
283310
}
284311
}
312+
313+
func testDelay(seconds int) resource.TestCheckFunc {
314+
return func(s *terraform.State) error {
315+
time.Sleep(time.Duration(seconds) * time.Second)
316+
return nil
317+
}
318+
}

0 commit comments

Comments
 (0)