Skip to content

Commit 73521e3

Browse files
Merge pull request #215 from jfrog/GH-214-mutex-for-projects
GH-214, add mutex to project and project_repository resources.
2 parents b35c738 + 578b007 commit 73521e3

10 files changed

+161
-9
lines changed

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
## 1.9.5. (September 15, 2025). Tested on Artifactory 7.117.16 with Terraform 1.13.2 and OpenTofu 1.10.6
2+
3+
IMPROVEMENTS:
4+
* resource/project, resource/project_repository: Fixed race condition when provider can't create multiple projects (over 50) with assigned repositories. Mutex was added to mitigate the condition. Tested with over 350 projects with assigned repositories. Issue: [#214](https://github.com/jfrog/terraform-provider-project/issues/214) PR:[#215](https://github.com/jfrog/terraform-provider-project/pull/215)
5+
16
## 1.9.4. (August 22, 2025). Tested on Artifactory 7.117.14 with Terraform 1.13.0 and OpenTofu 1.10.5
27

38
BUG FIXES:
49

5-
* resource/project_share_repository: Fixed race condition, when the repository couldn't be shared with multiple projects in a loop. Mutex was added tomitigate the overwhelming of the endpoint. Issue: [#209](https://github.com/jfrog/terraform-provider-project/issues/209) PR: [#211](https://github.com/jfrog/terraform-provider-project/pull/211)
6-
*
10+
* resource/project_share_repository: Fixed race condition, when the repository couldn't be shared with multiple projects in a loop. Mutex was added to mitigate the overwhelming of the endpoint. Issue: [#209](https://github.com/jfrog/terraform-provider-project/issues/209) PR: [#211](https://github.com/jfrog/terraform-provider-project/pull/211)
711

812
## 1.9.3 (December 19, 2024). Tested on Artifactory 7.98.11 with Terraform 1.10.3 and OpenTofu 1.8.7
913

pkg/project/provider.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
validatorfw_string "github.com/jfrog/terraform-provider-shared/validator/fw/string"
1818
)
1919

20-
var Version = "1.9.1"
20+
var Version = "1.9.5"
2121

2222
// needs to be exported so make file can update this
2323
var productId = "terraform-provider-project/" + Version

pkg/project/resource/resource_project.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,10 @@ func (r *ProjectResource) Configure(ctx context.Context, req resource.ConfigureR
708708
func (r *ProjectResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
709709
go util.SendUsageResourceCreate(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName)
710710

711+
lockName := "project"
712+
GlobalMutex.Lock(lockName)
713+
defer GlobalMutex.Unlock(lockName)
714+
711715
var plan ProjectResourceModelV4
712716

713717
// Read Terraform plan data into the model
@@ -857,6 +861,10 @@ func (r *ProjectResource) Read(ctx context.Context, req resource.ReadRequest, re
857861
func (r *ProjectResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
858862
go util.SendUsageResourceUpdate(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName)
859863

864+
lockName := "project"
865+
GlobalMutex.Lock(lockName)
866+
defer GlobalMutex.Unlock(lockName)
867+
860868
var plan ProjectResourceModelV4
861869

862870
// Read Terraform plan data into the model
@@ -930,6 +938,10 @@ func (r *ProjectResource) Update(ctx context.Context, req resource.UpdateRequest
930938
func (r *ProjectResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
931939
go util.SendUsageResourceDelete(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName)
932940

941+
lockName := "project"
942+
GlobalMutex.Lock(lockName)
943+
defer GlobalMutex.Unlock(lockName)
944+
933945
var state ProjectResourceModelV4
934946

935947
// Read Terraform prior state data into the model

pkg/project/resource/resource_project_environment_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func TestAccProjectEnvironment_UpgradeFromSDKv2(t *testing.T) {
6565
{
6666
ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories,
6767
Config: config,
68-
PlanOnly: true,
68+
PlanOnly: false,
6969
ConfigPlanChecks: testutil.ConfigPlanChecks(fqrn),
7070
},
7171
},

pkg/project/resource/resource_project_group_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func TestAccProjectGroup_UpgradeFromSDKv2(t *testing.T) {
8080
},
8181
ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories,
8282
Config: config,
83-
PlanOnly: true,
83+
PlanOnly: false,
8484
ConfigPlanChecks: testutil.ConfigPlanChecks(fqrn),
8585
},
8686
},

pkg/project/resource/resource_project_repository.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ func (r *ProjectRepositoryResource) Configure(ctx context.Context, req resource.
9292
func (r *ProjectRepositoryResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
9393
go util.SendUsageResourceCreate(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName)
9494

95+
lockName := "project_repository"
96+
GlobalMutex.Lock(lockName)
97+
defer GlobalMutex.Unlock(lockName)
98+
9599
var plan ProjectRepositoryResourceModel
96100

97101
// Read Terraform plan data into the model
@@ -256,6 +260,10 @@ func (r *ProjectRepositoryResource) Update(ctx context.Context, req resource.Upd
256260
func (r *ProjectRepositoryResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
257261
go util.SendUsageResourceDelete(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName)
258262

263+
lockName := "project_repository"
264+
GlobalMutex.Lock(lockName)
265+
defer GlobalMutex.Unlock(lockName)
266+
259267
var state ProjectRepositoryResourceModel
260268

261269
// Read Terraform prior state data into the model

pkg/project/resource/resource_project_repository_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func TestAccProjectRepository_UpgradeFromSDKv2(t *testing.T) {
9696
},
9797
ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories,
9898
Config: config,
99-
PlanOnly: true,
99+
PlanOnly: false,
100100
ConfigPlanChecks: testutil.ConfigPlanChecks(fqrn),
101101
},
102102
},

pkg/project/resource/resource_project_role_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func TestAccProjectRole_UpgradeFromSDKv2(t *testing.T) {
7575
{
7676
ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories,
7777
Config: config,
78-
PlanOnly: true,
78+
PlanOnly: false,
7979
ConfigPlanChecks: testutil.ConfigPlanChecks(fqrn),
8080
},
8181
},

pkg/project/resource/resource_project_test.go

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/go-resty/resty/v2"
1111
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
12+
"github.com/hashicorp/terraform-plugin-testing/terraform"
1213
acctest "github.com/jfrog/terraform-provider-project/pkg/project/acctest"
1314
project "github.com/jfrog/terraform-provider-project/pkg/project/resource"
1415
"github.com/jfrog/terraform-provider-shared/testutil"
@@ -203,7 +204,7 @@ func TestAccProject_UpgradeFromSDKv2(t *testing.T) {
203204
},
204205
ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories,
205206
Config: config,
206-
PlanOnly: true,
207+
PlanOnly: false,
207208
ConfigPlanChecks: testutil.ConfigPlanChecks(resourceName),
208209
},
209210
},
@@ -324,6 +325,133 @@ func testProjectConfig(name, key string) string {
324325
`, params)
325326
}
326327

328+
func TestAccProject_MultiProjectsWithRepos(t *testing.T) {
329+
numberOfProjects := 50
330+
config := testProjectConfigMultiProjectsWithRepos(numberOfProjects)
331+
332+
// Create check functions for all projects
333+
var checks []resource.TestCheckFunc
334+
for i := 0; i < numberOfProjects; i++ {
335+
name := fmt.Sprintf("ctestproject%d", i)
336+
key := fmt.Sprintf("ckey%d", i)
337+
resourceName := fmt.Sprintf("project.%s", name)
338+
339+
checks = append(checks,
340+
resource.TestCheckResourceAttr(resourceName, "key", key),
341+
resource.TestCheckResourceAttr(resourceName, "display_name", name),
342+
)
343+
}
344+
345+
resource.Test(t, resource.TestCase{
346+
ExternalProviders: map[string]resource.ExternalProvider{
347+
"artifactory": {
348+
Source: "jfrog/artifactory",
349+
},
350+
},
351+
PreCheck: func() { acctest.PreCheck(t) },
352+
CheckDestroy: func(s *terraform.State) error { return testAccCheckProjectMultiDestroy(s, numberOfProjects) },
353+
ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories,
354+
Steps: []resource.TestStep{
355+
{
356+
Config: config,
357+
Check: resource.ComposeTestCheckFunc(checks...),
358+
},
359+
},
360+
})
361+
}
362+
363+
func testProjectConfigMultiProjectsWithRepos(n int) string {
364+
var config strings.Builder
365+
366+
// Create global local repository (member of all virtual repos)
367+
config.WriteString(`
368+
resource "artifactory_local_maven_repository" "global_maven_local" {
369+
key = "maven-local-global"
370+
description = "Global Local Maven repository"
371+
lifecycle {
372+
ignore_changes = ["project_key", "project_environments"]
373+
}
374+
}
375+
`)
376+
377+
// Create all projects
378+
for i := 0; i < n; i++ {
379+
name := fmt.Sprintf("ctestproject%d", i)
380+
key := fmt.Sprintf("ckey%d", i)
381+
382+
projectConfig := fmt.Sprintf(`
383+
resource "project" "%s" {
384+
key = "%s"
385+
display_name = "%s"
386+
description = "test description %d"
387+
admin_privileges {
388+
manage_members = %t
389+
manage_resources = %t
390+
index_resources = %t
391+
}
392+
max_storage_in_gibibytes = %d
393+
block_deployments_on_limit = %t
394+
email_notification = %t
395+
}
396+
`, name, key, name, i,
397+
testutil.RandBool(), testutil.RandBool(), testutil.RandBool(),
398+
getRandomMaxStorageSize(), testutil.RandBool(), testutil.RandBool())
399+
400+
config.WriteString(projectConfig)
401+
}
402+
403+
// Create all virtual repositories (dependency on global local repo)
404+
for i := 0; i < n; i++ {
405+
key := fmt.Sprintf("ckey%d", i)
406+
name := fmt.Sprintf("ctestproject%d", i)
407+
virtualRepoName := fmt.Sprintf("project_virtual_maven_%d", i)
408+
409+
virtualRepoConfig := fmt.Sprintf(`
410+
resource "artifactory_virtual_maven_repository" "%s" {
411+
key = "maven-virtual-%s"
412+
description = "Virtual Maven repository for %s"
413+
repositories = [
414+
artifactory_local_maven_repository.global_maven_local.key,
415+
]
416+
lifecycle {
417+
ignore_changes = ["project_key", "project_environments"]
418+
}
419+
}
420+
`, virtualRepoName, key, name)
421+
422+
config.WriteString(virtualRepoConfig)
423+
}
424+
425+
// Create all project_repository assignments (implicit dependencies via resource references)
426+
for i := 0; i < n; i++ {
427+
name := fmt.Sprintf("ctestproject%d", i)
428+
virtualRepoName := fmt.Sprintf("project_virtual_maven_%d", i)
429+
430+
projectRepoConfig := fmt.Sprintf(`
431+
resource "project_repository" "project_virtual_maven_repo_%d" {
432+
project_key = project.%s.key
433+
key = artifactory_virtual_maven_repository.%s.key
434+
}
435+
`, i, name, virtualRepoName)
436+
437+
config.WriteString(projectRepoConfig)
438+
}
439+
440+
return config.String()
441+
}
442+
443+
func testAccCheckProjectMultiDestroy(s *terraform.State, n int) error {
444+
// Custom destroy check that verifies all 100 projects are deleted
445+
for i := 0; i < n; i++ {
446+
name := fmt.Sprintf("ctestproject%d", i)
447+
resourceName := fmt.Sprintf("project.%s", name)
448+
if err := acctest.VerifyDeleted(resourceName, verifyProject)(s); err != nil {
449+
return err
450+
}
451+
}
452+
return nil
453+
}
454+
327455
func TestAccProject_InvalidMaxStorage(t *testing.T) {
328456
invalidMaxStorages := []struct {
329457
Name string

pkg/project/resource/resource_project_user_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func TestAccProjectUser_UpgradeFromSDKv2(t *testing.T) {
9595
},
9696
},
9797
Config: config,
98-
PlanOnly: true,
98+
PlanOnly: false,
9999
ConfigPlanChecks: testutil.ConfigPlanChecks(fqrn),
100100
},
101101
},

0 commit comments

Comments
 (0)