Skip to content

Commit 391ce96

Browse files
authored
Handle concurrent mass modification of repository and repository_credentials resources (#26)
1 parent 8f099f5 commit 391ce96

11 files changed

+218
-58
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,19 @@ provider "argocd" {
6565
insecure = false # env ARGOCD_INSECURE
6666
}
6767
68-
resource "argocd_repository" "nginx_helm" {
68+
resource "argocd_repository_credentials" "private" {
69+
70+
username = "git"
71+
ssh_private_key = "-----BEGIN OPENSSH PRIVATE KEY-----\nfoo\nbar\n-----END OPENSSH PRIVATE KEY-----"
72+
}
73+
74+
// Uses previously defined repository credentials
75+
resource "argocd_repository" "private" {
76+
repo = "[email protected]:somerepo.git"
77+
// insecure = true
78+
}
79+
80+
resource "argocd_repository" "public_nginx_helm" {
6981
repo = "https://helm.nginx.com/stable"
7082
name = "nginx-stable"
7183
type = "helm"

argocd/provider.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,17 @@ import (
1414
"github.com/golang/protobuf/ptypes/empty"
1515
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
1616
"github.com/hashicorp/terraform-plugin-sdk/terraform"
17+
"sync"
1718
)
1819

1920
var apiClientConnOpts apiclient.ClientOptions
2021

22+
// Used to handle concurrent access to ArgoCD common configuration
23+
var tokenMutexConfiguration = &sync.RWMutex{}
24+
25+
// Used to handle concurrent access to each ArgoCD project
26+
var tokenMutexProjectMap = make(map[string]*sync.RWMutex, 0)
27+
2128
func Provider() terraform.ResourceProvider {
2229
return &schema.Provider{
2330
Schema: map[string]*schema.Schema{

argocd/resource_argocd_project.go

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
application "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
88
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
99
"strings"
10+
"sync"
1011
"time"
1112
)
1213

@@ -34,9 +35,17 @@ func resourceArgoCDProjectCreate(d *schema.ResourceData, meta interface{}) error
3435
}
3536
server := meta.(ServerInterface)
3637
c := *server.ProjectClient
38+
projectName := objectMeta.Name
39+
if _, ok := tokenMutexProjectMap[projectName]; !ok {
40+
tokenMutexProjectMap[projectName] = &sync.RWMutex{}
41+
}
42+
43+
tokenMutexProjectMap[projectName].RLock()
3744
p, err := c.Get(context.Background(), &projectClient.ProjectQuery{
38-
Name: objectMeta.Name,
45+
Name: projectName,
3946
})
47+
tokenMutexProjectMap[projectName].RUnlock()
48+
4049
if err != nil {
4150
switch strings.Contains(err.Error(), "NotFound") {
4251
case true:
@@ -52,6 +61,8 @@ func resourceArgoCDProjectCreate(d *schema.ResourceData, meta interface{}) error
5261
time.Sleep(time.Duration(*p.DeletionGracePeriodSeconds))
5362
}
5463
}
64+
65+
tokenMutexProjectMap[projectName].Lock()
5566
p, err = c.Create(context.Background(), &projectClient.ProjectCreateRequest{
5667
Project: &application.AppProject{
5768
ObjectMeta: objectMeta,
@@ -61,6 +72,8 @@ func resourceArgoCDProjectCreate(d *schema.ResourceData, meta interface{}) error
6172
// TODO: make that a resource flag with proper acceptance tests
6273
Upsert: false,
6374
})
75+
tokenMutexProjectMap[projectName].Unlock()
76+
6477
if err != nil {
6578
return err
6679
}
@@ -74,9 +87,17 @@ func resourceArgoCDProjectCreate(d *schema.ResourceData, meta interface{}) error
7487
func resourceArgoCDProjectRead(d *schema.ResourceData, meta interface{}) error {
7588
server := meta.(ServerInterface)
7689
c := *server.ProjectClient
90+
projectName := d.Id()
91+
if _, ok := tokenMutexProjectMap[projectName]; !ok {
92+
tokenMutexProjectMap[projectName] = &sync.RWMutex{}
93+
}
94+
95+
tokenMutexProjectMap[projectName].RLock()
7796
p, err := c.Get(context.Background(), &projectClient.ProjectQuery{
78-
Name: d.Id(),
97+
Name: projectName,
7998
})
99+
tokenMutexProjectMap[projectName].RUnlock()
100+
80101
if err != nil {
81102
switch strings.Contains(err.Error(), "NotFound") {
82103
case true:
@@ -98,14 +119,23 @@ func resourceArgoCDProjectUpdate(d *schema.ResourceData, meta interface{}) error
98119
}
99120
server := meta.(ServerInterface)
100121
c := *server.ProjectClient
122+
projectName := objectMeta.Name
123+
if _, ok := tokenMutexProjectMap[projectName]; !ok {
124+
tokenMutexProjectMap[projectName] = &sync.RWMutex{}
125+
}
101126
projectRequest := &projectClient.ProjectUpdateRequest{
102127
Project: &application.AppProject{
103128
ObjectMeta: objectMeta,
104129
Spec: spec,
105-
}}
130+
},
131+
}
132+
133+
tokenMutexProjectMap[projectName].RLock()
106134
p, err := c.Get(context.Background(), &projectClient.ProjectQuery{
107135
Name: d.Id(),
108136
})
137+
tokenMutexProjectMap[projectName].RUnlock()
138+
109139
if p != nil {
110140
// Kubernetes API requires providing the up-to-date correct ResourceVersion for updates
111141
projectRequest.Project.ResourceVersion = p.ResourceVersion
@@ -123,7 +153,11 @@ func resourceArgoCDProjectUpdate(d *schema.ResourceData, meta interface{}) error
123153
projectRequest.Project.Spec.Roles[i].JWTTokens = pr.JWTTokens
124154
}
125155
}
156+
157+
tokenMutexProjectMap[projectName].Lock()
126158
_, err = c.Update(context.Background(), projectRequest)
159+
tokenMutexProjectMap[projectName].Unlock()
160+
127161
if err != nil {
128162
return err
129163
}
@@ -134,7 +168,12 @@ func resourceArgoCDProjectUpdate(d *schema.ResourceData, meta interface{}) error
134168
func resourceArgoCDProjectDelete(d *schema.ResourceData, meta interface{}) error {
135169
server := meta.(ServerInterface)
136170
c := *server.ProjectClient
137-
_, err := c.Delete(context.Background(), &projectClient.ProjectQuery{Name: d.Id()})
171+
projectName := d.Id()
172+
173+
tokenMutexProjectMap[projectName].Lock()
174+
_, err := c.Delete(context.Background(), &projectClient.ProjectQuery{Name: projectName})
175+
tokenMutexProjectMap[projectName].Unlock()
176+
138177
if err != nil {
139178
return err
140179
}

argocd/resource_argocd_project_token.go

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ import (
1313
"time"
1414
)
1515

16-
// For each project, implement a sync.RWMutex
17-
var tokenMutexProjectMap = make(map[string]*sync.RWMutex, 0)
18-
1916
func resourceArgoCDProjectToken() *schema.Resource {
2017
return &schema.Resource{
2118
Create: resourceArgoCDProjectTokenCreate,
@@ -76,15 +73,15 @@ func resourceArgoCDProjectTokenCreate(d *schema.ResourceData, meta interface{})
7673
var claims jwt.StandardClaims
7774
var expiresIn int64
7875

79-
p := d.Get("project").(string)
76+
projectName := d.Get("project").(string)
8077
role := d.Get("role").(string)
8178
opts := &project.ProjectTokenCreateRequest{
82-
Project: p,
79+
Project: projectName,
8380
Role: role,
8481
}
8582

86-
if _, ok := tokenMutexProjectMap[p]; !ok {
87-
tokenMutexProjectMap[p] = &sync.RWMutex{}
83+
if _, ok := tokenMutexProjectMap[projectName]; !ok {
84+
tokenMutexProjectMap[projectName] = &sync.RWMutex{}
8885
}
8986
if d, ok := d.GetOk("description"); ok {
9087
opts.Description = d.(string)
@@ -120,14 +117,14 @@ func resourceArgoCDProjectTokenCreate(d *schema.ResourceData, meta interface{})
120117
return err
121118
}
122119

123-
tokenMutexProjectMap[p].Lock()
120+
tokenMutexProjectMap[projectName].Lock()
124121
resp, err := c.CreateToken(context.Background(), opts)
125122
// ensure issuedAt is unique upon multiple simultaneous resource creation invocations
126123
// as this is the unique ID for old tokens
127124
if !featureTokenIDSupported {
128125
time.Sleep(1 * time.Second)
129126
}
130-
tokenMutexProjectMap[p].Unlock()
127+
tokenMutexProjectMap[projectName].Unlock()
131128
if err != nil {
132129
return err
133130
}
@@ -164,7 +161,7 @@ func resourceArgoCDProjectTokenCreate(d *schema.ResourceData, meta interface{})
164161
}
165162
d.SetId(claims.ID)
166163
} else {
167-
d.SetId(fmt.Sprintf("%s-%s-%d", p, role, claims.IssuedAt.Unix()))
164+
d.SetId(fmt.Sprintf("%s-%s-%d", projectName, role, claims.IssuedAt.Unix()))
168165
}
169166
return resourceArgoCDProjectTokenRead(d, meta)
170167
}
@@ -178,11 +175,18 @@ func resourceArgoCDProjectTokenRead(d *schema.ResourceData, meta interface{}) er
178175

179176
server := meta.(ServerInterface)
180177
c := *server.ProjectClient
178+
projectName := d.Get("project").(string)
179+
if _, ok := tokenMutexProjectMap[projectName]; !ok {
180+
tokenMutexProjectMap[projectName] = &sync.RWMutex{}
181+
}
181182

182183
// Delete token from state if project has been deleted in an out-of-band fashion
184+
tokenMutexProjectMap[projectName].RLock()
183185
p, err := c.Get(context.Background(), &project.ProjectQuery{
184-
Name: d.Get("project").(string),
186+
Name: projectName,
185187
})
188+
tokenMutexProjectMap[projectName].RUnlock()
189+
186190
if err != nil {
187191
switch strings.Contains(err.Error(), "NotFound") {
188192
case true:
@@ -192,9 +196,7 @@ func resourceArgoCDProjectTokenRead(d *schema.ResourceData, meta interface{}) er
192196
return err
193197
}
194198
}
195-
if _, ok := tokenMutexProjectMap[p.Name]; !ok {
196-
tokenMutexProjectMap[p.Name] = &sync.RWMutex{}
197-
}
199+
198200
featureTokenIDSupported, err := server.isFeatureSupported(featureTokenIDs)
199201
if err != nil {
200202
return err
@@ -214,13 +216,13 @@ func resourceArgoCDProjectTokenRead(d *schema.ResourceData, meta interface{}) er
214216
}
215217
}
216218

217-
tokenMutexProjectMap[p.Name].RLock()
219+
tokenMutexProjectMap[projectName].RLock()
218220
token, _, err = p.GetJWTToken(
219221
d.Get("role").(string),
220222
requestTokenIAT,
221223
requestTokenID,
222224
)
223-
tokenMutexProjectMap[p.Name].RUnlock()
225+
tokenMutexProjectMap[projectName].RUnlock()
224226
if err != nil {
225227
// Token has been deleted in an out-of-band fashion
226228
d.SetId("")

argocd/resource_argocd_repository.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ func resourceArgoCDRepositoryCreate(d *schema.ResourceData, meta interface{}) er
2727
server := meta.(ServerInterface)
2828
c := *server.RepositoryClient
2929
repo := expandRepository(d)
30+
31+
tokenMutexConfiguration.Lock()
3032
r, err := c.CreateRepository(
3133
context.Background(),
3234
&repository.RepoCreateRequest{
@@ -35,6 +37,8 @@ func resourceArgoCDRepositoryCreate(d *schema.ResourceData, meta interface{}) er
3537
CredsOnly: false,
3638
},
3739
)
40+
tokenMutexConfiguration.Unlock()
41+
3842
if err != nil {
3943
return err
4044
}
@@ -64,10 +68,13 @@ func resourceArgoCDRepositoryRead(d *schema.ResourceData, meta interface{}) erro
6468

6569
switch featureRepositoryGetSupported {
6670
case true:
71+
tokenMutexConfiguration.RLock()
6772
r, err = c.Get(context.Background(), &repository.RepoQuery{
6873
Repo: d.Id(),
6974
ForceRefresh: false,
7075
})
76+
tokenMutexConfiguration.RUnlock()
77+
7178
if err != nil {
7279
switch strings.Contains(err.Error(), "NotFound") {
7380
// Repository has already been deleted in an out-of-band fashion
@@ -79,10 +86,13 @@ func resourceArgoCDRepositoryRead(d *schema.ResourceData, meta interface{}) erro
7986
}
8087
}
8188
case false:
89+
tokenMutexConfiguration.RLock()
8290
rl, err := c.ListRepositories(context.Background(), &repository.RepoQuery{
8391
Repo: d.Id(),
8492
ForceRefresh: false,
8593
})
94+
tokenMutexConfiguration.RUnlock()
95+
8696
if err != nil {
8797
// TODO: check for NotFound condition?
8898
return err
@@ -111,10 +121,14 @@ func resourceArgoCDRepositoryUpdate(d *schema.ResourceData, meta interface{}) er
111121
server := meta.(ServerInterface)
112122
c := *server.RepositoryClient
113123
repo := expandRepository(d)
124+
125+
tokenMutexConfiguration.Lock()
114126
r, err := c.UpdateRepository(
115127
context.Background(),
116128
&repository.RepoUpdateRequest{Repo: repo},
117129
)
130+
tokenMutexConfiguration.Unlock()
131+
118132
if err != nil {
119133
switch strings.Contains(err.Error(), "NotFound") {
120134
// Repository has already been deleted in an out-of-band fashion
@@ -142,10 +156,14 @@ func resourceArgoCDRepositoryUpdate(d *schema.ResourceData, meta interface{}) er
142156
func resourceArgoCDRepositoryDelete(d *schema.ResourceData, meta interface{}) error {
143157
server := meta.(ServerInterface)
144158
c := *server.RepositoryClient
159+
160+
tokenMutexConfiguration.Lock()
145161
_, err := c.DeleteRepository(
146162
context.Background(),
147163
&repository.RepoQuery{Repo: d.Id()},
148164
)
165+
tokenMutexConfiguration.Unlock()
166+
149167
if err != nil {
150168
return err
151169
}

argocd/resource_argocd_repository_credentials.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,17 @@ func resourceArgoCDRepositoryCredentialsCreate(d *schema.ResourceData, meta inte
2626
server := meta.(ServerInterface)
2727
c := *server.RepoCredsClient
2828
repoCreds := expandRepositoryCredentials(d)
29+
30+
tokenMutexConfiguration.Lock()
2931
rc, err := c.CreateRepositoryCredentials(
3032
context.Background(),
3133
&repocreds.RepoCredsCreateRequest{
3234
Creds: repoCreds,
3335
Upsert: false,
3436
},
3537
)
38+
tokenMutexConfiguration.Unlock()
39+
3640
if err != nil {
3741
return err
3842
}
@@ -44,9 +48,13 @@ func resourceArgoCDRepositoryCredentialsRead(d *schema.ResourceData, meta interf
4448
server := meta.(ServerInterface)
4549
c := *server.RepoCredsClient
4650
rc := application.RepoCreds{}
51+
52+
tokenMutexConfiguration.RLock()
4753
rcl, err := c.ListRepositoryCredentials(context.Background(), &repocreds.RepoCredsQuery{
4854
Url: d.Id(),
4955
})
56+
tokenMutexConfiguration.RUnlock()
57+
5058
if err != nil {
5159
// TODO: check for NotFound condition?
5260
return err
@@ -74,11 +82,15 @@ func resourceArgoCDRepositoryCredentialsUpdate(d *schema.ResourceData, meta inte
7482
server := meta.(ServerInterface)
7583
c := *server.RepoCredsClient
7684
repoCreds := expandRepositoryCredentials(d)
85+
86+
tokenMutexConfiguration.Lock()
7787
r, err := c.UpdateRepositoryCredentials(
7888
context.Background(),
7989
&repocreds.RepoCredsUpdateRequest{
8090
Creds: repoCreds},
8191
)
92+
tokenMutexConfiguration.Unlock()
93+
8294
if err != nil {
8395
switch strings.Contains(err.Error(), "NotFound") {
8496
// Repository credentials have already been deleted in an out-of-band fashion
@@ -96,10 +108,14 @@ func resourceArgoCDRepositoryCredentialsUpdate(d *schema.ResourceData, meta inte
96108
func resourceArgoCDRepositoryCredentialsDelete(d *schema.ResourceData, meta interface{}) error {
97109
server := meta.(ServerInterface)
98110
c := *server.RepoCredsClient
111+
112+
tokenMutexConfiguration.Lock()
99113
_, err := c.DeleteRepositoryCredentials(
100114
context.Background(),
101115
&repocreds.RepoCredsDeleteRequest{Url: d.Id()},
102116
)
117+
tokenMutexConfiguration.Unlock()
118+
103119
if err != nil {
104120
return err
105121
}

0 commit comments

Comments
 (0)