Skip to content

Commit 2b17103

Browse files
authored
Fix locking on resources during Terraform actions (#263)
1 parent 33775f3 commit 2b17103

File tree

4 files changed

+76
-47
lines changed

4 files changed

+76
-47
lines changed

argocd/resource_argocd_project.go

Lines changed: 60 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -68,37 +68,6 @@ func resourceArgoCDProjectCreate(ctx context.Context, d *schema.ResourceData, me
6868

6969
projectName := objectMeta.Name
7070

71-
if _, ok := tokenMutexProjectMap[projectName]; !ok {
72-
tokenMutexProjectMap[projectName] = &sync.RWMutex{}
73-
}
74-
75-
tokenMutexProjectMap[projectName].RLock()
76-
p, err := si.ProjectClient.Get(ctx, &projectClient.ProjectQuery{
77-
Name: projectName,
78-
})
79-
tokenMutexProjectMap[projectName].RUnlock()
80-
81-
if err != nil {
82-
if !strings.Contains(err.Error(), "NotFound") {
83-
return []diag.Diagnostic{
84-
{
85-
Severity: diag.Error,
86-
Summary: fmt.Sprintf("Project %s could not be created", projectName),
87-
Detail: err.Error(),
88-
},
89-
}
90-
}
91-
}
92-
93-
if p != nil {
94-
switch p.DeletionTimestamp {
95-
case nil:
96-
default:
97-
// Pre-existing project is still in Kubernetes soft deletion queue
98-
time.Sleep(time.Duration(*p.DeletionGracePeriodSeconds))
99-
}
100-
}
101-
10271
featureProjectSourceNamespacesSupported, err := si.isFeatureSupported(featureProjectSourceNamespaces)
10372
if err != nil {
10473
return []diag.Diagnostic{
@@ -122,7 +91,34 @@ func resourceArgoCDProjectCreate(ctx context.Context, d *schema.ResourceData, me
12291
}
12392
}
12493

94+
if _, ok := tokenMutexProjectMap[projectName]; !ok {
95+
tokenMutexProjectMap[projectName] = &sync.RWMutex{}
96+
}
97+
12598
tokenMutexProjectMap[projectName].Lock()
99+
100+
p, err := si.ProjectClient.Get(ctx, &projectClient.ProjectQuery{
101+
Name: projectName,
102+
})
103+
if err != nil && !strings.Contains(err.Error(), "NotFound") {
104+
tokenMutexProjectMap[projectName].Unlock()
105+
106+
return []diag.Diagnostic{
107+
{
108+
Severity: diag.Error,
109+
Summary: fmt.Sprintf("failed to get project %s", projectName),
110+
Detail: err.Error(),
111+
},
112+
}
113+
} else if p != nil {
114+
switch p.DeletionTimestamp {
115+
case nil:
116+
default:
117+
// Pre-existing project is still in Kubernetes soft deletion queue
118+
time.Sleep(time.Duration(*p.DeletionGracePeriodSeconds))
119+
}
120+
}
121+
126122
p, err = si.ProjectClient.Create(ctx, &projectClient.ProjectCreateRequest{
127123
Project: &application.AppProject{
128124
ObjectMeta: objectMeta,
@@ -132,6 +128,7 @@ func resourceArgoCDProjectCreate(ctx context.Context, d *schema.ResourceData, me
132128
// TODO: make that a resource flag with proper acceptance tests
133129
Upsert: false,
134130
})
131+
135132
tokenMutexProjectMap[projectName].Unlock()
136133

137134
if err != nil {
@@ -238,6 +235,29 @@ func resourceArgoCDProjectUpdate(ctx context.Context, d *schema.ResourceData, me
238235

239236
projectName := objectMeta.Name
240237

238+
featureProjectSourceNamespacesSupported, err := si.isFeatureSupported(featureProjectSourceNamespaces)
239+
if err != nil {
240+
return []diag.Diagnostic{
241+
{
242+
Severity: diag.Error,
243+
Summary: "feature not supported",
244+
Detail: err.Error(),
245+
},
246+
}
247+
} else if !featureProjectSourceNamespacesSupported {
248+
_, sourceNamespacesOk := d.GetOk("spec.0.source_namespaces")
249+
if sourceNamespacesOk {
250+
return []diag.Diagnostic{
251+
{
252+
Severity: diag.Error,
253+
Summary: fmt.Sprintf(
254+
"project source_namespaces is only supported from ArgoCD %s onwards",
255+
featureVersionConstraintsMap[featureProjectSourceNamespaces].String()),
256+
},
257+
}
258+
}
259+
}
260+
241261
if _, ok := tokenMutexProjectMap[projectName]; !ok {
242262
tokenMutexProjectMap[projectName] = &sync.RWMutex{}
243263
}
@@ -249,23 +269,22 @@ func resourceArgoCDProjectUpdate(ctx context.Context, d *schema.ResourceData, me
249269
},
250270
}
251271

252-
tokenMutexProjectMap[projectName].RLock()
272+
tokenMutexProjectMap[projectName].Lock()
273+
253274
p, err := si.ProjectClient.Get(ctx, &projectClient.ProjectQuery{
254275
Name: d.Id(),
255276
})
256-
tokenMutexProjectMap[projectName].RUnlock()
257-
258277
if err != nil {
278+
tokenMutexProjectMap[projectName].Unlock()
279+
259280
return []diag.Diagnostic{
260281
{
261282
Severity: diag.Error,
262-
Summary: "failed to get project",
283+
Summary: fmt.Sprintf("failed to get existing project %s", projectName),
263284
Detail: err.Error(),
264285
},
265286
}
266-
}
267-
268-
if p != nil {
287+
} else if p != nil {
269288
// Kubernetes API requires providing the up-to-date correct ResourceVersion for updates
270289
projectRequest.Project.ResourceVersion = p.ResourceVersion
271290

@@ -282,6 +301,8 @@ func resourceArgoCDProjectUpdate(ctx context.Context, d *schema.ResourceData, me
282301
// i == -1 means the role does not exist
283302
// and was recently added within Terraform tf files
284303
if i != -1 {
304+
tokenMutexProjectMap[projectName].Unlock()
305+
285306
return []diag.Diagnostic{
286307
{
287308
Severity: diag.Error,
@@ -296,8 +317,8 @@ func resourceArgoCDProjectUpdate(ctx context.Context, d *schema.ResourceData, me
296317
}
297318
}
298319

299-
tokenMutexProjectMap[projectName].Lock()
300320
_, err = si.ProjectClient.Update(ctx, projectRequest)
321+
301322
tokenMutexProjectMap[projectName].Unlock()
302323

303324
if err != nil {

argocd/resource_argocd_project_token.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,12 @@ func resourceArgoCDProjectTokenDelete(ctx context.Context, d *schema.ResourceDat
564564
}
565565

566566
tokenMutexProjectMap[projectName].Lock()
567-
if _, err := si.ProjectClient.DeleteToken(ctx, opts); err != nil {
567+
568+
_, err = si.ProjectClient.DeleteToken(ctx, opts)
569+
570+
tokenMutexProjectMap[projectName].Unlock()
571+
572+
if err != nil {
568573
return []diag.Diagnostic{
569574
{
570575
Severity: diag.Error,
@@ -573,7 +578,6 @@ func resourceArgoCDProjectTokenDelete(ctx context.Context, d *schema.ResourceDat
573578
},
574579
}
575580
}
576-
tokenMutexProjectMap[projectName].Unlock()
577581

578582
d.SetId("")
579583

argocd/resource_argocd_project_token_test.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func TestAccArgoCDProjectToken_RenewBefore(t *testing.T) {
110110
func TestAccArgoCDProjectToken_RenewAfter(t *testing.T) {
111111
resourceName := "argocd_project_token.renew_after"
112112

113-
renewAfterSeconds := 1
113+
renewAfterSeconds := 2
114114

115115
resource.ParallelTest(t, resource.TestCase{
116116
PreCheck: func() { testAccPreCheck(t) },
@@ -120,17 +120,21 @@ func TestAccArgoCDProjectToken_RenewAfter(t *testing.T) {
120120
Config: testAccArgoCDProjectTokenRenewAfter(renewAfterSeconds),
121121
Check: resource.ComposeTestCheckFunc(
122122
resource.TestCheckResourceAttr(resourceName, "renew_after", fmt.Sprintf("%ds", renewAfterSeconds)),
123-
testDelay(renewAfterSeconds+1),
124123
),
125-
ExpectNonEmptyPlan: true,
126124
},
127125
{
128126
Config: testAccArgoCDProjectTokenRenewAfter(renewAfterSeconds),
129127
Check: resource.ComposeTestCheckFunc(
130-
testDelay(renewAfterSeconds),
128+
testDelay(renewAfterSeconds + 1),
131129
),
132130
ExpectNonEmptyPlan: true, // token should be recreated when refreshed at end of step due to delay above
133131
},
132+
{
133+
Config: testAccArgoCDProjectTokenRenewAfter(renewAfterSeconds),
134+
Check: resource.ComposeTestCheckFunc(
135+
resource.TestCheckResourceAttr(resourceName, "renew_after", fmt.Sprintf("%ds", renewAfterSeconds)),
136+
),
137+
},
134138
},
135139
})
136140
}

argocd/resource_argocd_repository.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func resourceArgoCDRepositoryCreate(ctx context.Context, d *schema.ResourceData,
9191
return resource.RetryableError(fmt.Errorf("handshake failed for repository %s, retrying in case a repository certificate has been set recently", repo.Repo))
9292
}
9393

94-
return resource.NonRetryableError(fmt.Errorf("repository %s not found: %s", repo.Repo, err))
94+
return resource.NonRetryableError(fmt.Errorf("failed to create repository %s : %w", repo.Repo, err))
9595
} else if r == nil {
9696
return resource.NonRetryableError(fmt.Errorf("ArgoCD did not return an error or a repository result: %s", err))
9797
} else if r.ConnectionState.Status == application.ConnectionStatusFailed {
@@ -139,7 +139,7 @@ func resourceArgoCDRepositoryRead(ctx context.Context, d *schema.ResourceData, m
139139
}
140140
}
141141

142-
r := &application.Repository{}
142+
var r *application.Repository
143143

144144
if featureRepositoryGetSupported {
145145
tokenMutexConfiguration.RLock()

0 commit comments

Comments
 (0)