diff --git a/pkg/project/repo.go b/pkg/project/repo.go index 87bbad3a..ab2ec69f 100644 --- a/pkg/project/repo.go +++ b/pkg/project/repo.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" + "github.com/go-resty/resty/v2" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/jfrog/terraform-provider-shared/util" @@ -109,8 +110,13 @@ var updateRepos = func(ctx context.Context, projectKey string, terraformRepoKeys var addRepos = func(ctx context.Context, projectKey string, repoKeys []RepoKey, m interface{}) error { tflog.Debug(ctx, fmt.Sprintf("addRepos: %s", repoKeys)) + req := m.(util.ProvderMetadata).Client.R(). + AddRetryCondition(retryOnSpecificMsgBody("A timeout occurred")). + AddRetryCondition(retryOnSpecificMsgBody("Web server is down")). + AddRetryCondition(retryOnSpecificMsgBody("Web server is returning an unknown error")) + for _, repoKey := range repoKeys { - err := addRepo(ctx, projectKey, repoKey, m) + err := addRepo(ctx, projectKey, repoKey, req) if err != nil { return fmt.Errorf("failed to add repo %s: %s", repoKey, err) } @@ -119,13 +125,10 @@ var addRepos = func(ctx context.Context, projectKey string, repoKeys []RepoKey, return nil } -var addRepo = func(ctx context.Context, projectKey string, repoKey RepoKey, m interface{}) error { +var addRepo = func(ctx context.Context, projectKey string, repoKey RepoKey, req *resty.Request) error { tflog.Debug(ctx, fmt.Sprintf("addRepo: %s", repoKey)) - _, err := m.(util.ProvderMetadata).Client.R(). - AddRetryCondition(retryOnSpecificMsgBody("A timeout occurred")). - AddRetryCondition(retryOnSpecificMsgBody("Web server is down")). - AddRetryCondition(retryOnSpecificMsgBody("Web server is returning an unknown error")). + _, err := req. SetPathParams(map[string]string{ "projectKey": projectKey, "repoKey": string(repoKey), @@ -139,8 +142,13 @@ var addRepo = func(ctx context.Context, projectKey string, repoKey RepoKey, m in var deleteRepos = func(ctx context.Context, projectKey string, repoKeys []RepoKey, m interface{}) error { tflog.Debug(ctx, fmt.Sprintf("deleteRepos: %s", repoKeys)) + req := m.(util.ProvderMetadata).Client.R(). + AddRetryCondition(retryOnSpecificMsgBody("A timeout occurred")). + AddRetryCondition(retryOnSpecificMsgBody("Web server is down")). + AddRetryCondition(retryOnSpecificMsgBody("Web server is returning an unknown error")) + for _, repoKey := range repoKeys { - err := deleteRepo(ctx, projectKey, repoKey, m) + err := deleteRepo(ctx, projectKey, repoKey, req) if err != nil { return fmt.Errorf("failed to delete repo %s: %s", repoKey, err) } @@ -149,7 +157,7 @@ var deleteRepos = func(ctx context.Context, projectKey string, repoKeys []RepoKe return nil } -var deleteRepo = func(ctx context.Context, projectKey string, repoKey RepoKey, m interface{}) error { +var deleteRepo = func(ctx context.Context, projectKey string, repoKey RepoKey, req *resty.Request) error { tflog.Debug(ctx, fmt.Sprintf("deleteRepo: %s", repoKey)) type Error struct { @@ -163,10 +171,7 @@ var deleteRepo = func(ctx context.Context, projectKey string, repoKey RepoKey, m var errorResp ErrorResponse - resp, err := m.(util.ProvderMetadata).Client.R(). - AddRetryCondition(retryOnSpecificMsgBody("A timeout occurred")). - AddRetryCondition(retryOnSpecificMsgBody("Web server is down")). - AddRetryCondition(retryOnSpecificMsgBody("Web server is returning an unknown error")). + resp, err := req. SetPathParam("repoKey", string(repoKey)). SetError(&errorResp). Delete(projectsUrl + "/_/attach/repositories/{repoKey}") diff --git a/pkg/project/resource_project.go b/pkg/project/resource_project.go index a44cc654..aafb7226 100644 --- a/pkg/project/resource_project.go +++ b/pkg/project/resource_project.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/go-resty/resty/v2" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -489,7 +490,15 @@ func projectResource() *schema.Resource { return diag.FromErr(fmt.Errorf("failed to delete repos for project: %s", deleteErr)) } - resp, err := m.(util.ProvderMetadata).Client.R(). + req := m.(util.ProvderMetadata).Client.R() + req.AddRetryCondition( + func(r *resty.Response, _ error) bool { + return r.StatusCode() == http.StatusBadRequest && + strings.Contains(r.String(), "project containing resources can't be removed") + }, + ) + + resp, err := req. SetPathParam("projectKey", data.Id()). Delete(projectUrl) diff --git a/pkg/project/resource_project_test.go b/pkg/project/resource_project_test.go index 06f7a3d4..a92626e1 100644 --- a/pkg/project/resource_project_test.go +++ b/pkg/project/resource_project_test.go @@ -271,6 +271,8 @@ func TestAccProject_full(t *testing.T) { "project_key": strings.ToLower(randSeq(6)), "username1": username1, "username2": username2, + "email1": email1, + "email2": email2, "group1": group1, "group2": group2, "repo1": repo1, @@ -278,6 +280,44 @@ func TestAccProject_full(t *testing.T) { } template := ` + resource "artifactory_managed_user" "{{ .username1 }}" { + name = "{{ .username1 }}" + email = "{{ .email1 }}" + password = "Password1!" + admin = false + } + + resource "artifactory_managed_user" "{{ .username2 }}" { + name = "{{ .username2 }}" + email = "{{ .email2 }}" + password = "Password1!" + admin = false + } + + resource "artifactory_group" "{{ .group1 }}" { + name = "{{ .group1 }}" + } + + resource "artifactory_group" "{{ .group2 }}" { + name = "{{ .group2 }}" + } + + resource "artifactory_local_generic_repository" "{{ .repo1 }}" { + key = "{{ .repo1 }}" + + lifecycle { + ignore_changes = ["project_key"] + } + } + + resource "artifactory_local_generic_repository" "{{ .repo2 }}" { + key = "{{ .repo2 }}" + + lifecycle { + ignore_changes = ["project_key"] + } + } + resource "project" "{{ .name }}" { key = "{{ .project_key }}" display_name = "{{ .name }}" @@ -292,22 +332,22 @@ func TestAccProject_full(t *testing.T) { email_notification = {{ .email_notification }} member { - name = "{{ .username1 }}" + name = artifactory_managed_user.{{ .username1 }}.name roles = ["Developer","Project Admin"] } member { - name = "{{ .username2 }}" + name = artifactory_managed_user.{{ .username2 }}.name roles = ["Developer"] } group { - name = "{{ .group1 }}" + name = artifactory_group.{{ .group1 }}.name roles = ["qa"] } group { - name = "{{ .group2 }}" + name = artifactory_group.{{ .group2 }}.name roles = ["Release Manager"] } @@ -327,7 +367,10 @@ func TestAccProject_full(t *testing.T) { actions = ["READ_REPOSITORY", "ANNOTATE_REPOSITORY", "DEPLOY_CACHE_REPOSITORY", "DELETE_OVERWRITE_REPOSITORY", "TRIGGER_PIPELINE", "READ_INTEGRATIONS_PIPELINE", "READ_POOLS_PIPELINE", "MANAGE_INTEGRATIONS_PIPELINE", "MANAGE_SOURCES_PIPELINE", "MANAGE_POOLS_PIPELINE", "READ_BUILD", "ANNOTATE_BUILD", "DEPLOY_BUILD", "DELETE_BUILD",] } - repos = ["{{ .repo1 }}", "{{ .repo2 }}"] + repos = [ + artifactory_local_generic_repository.{{ .repo1 }}.key, + artifactory_local_generic_repository.{{ .repo2 }}.key, + ] } ` @@ -344,6 +387,8 @@ func TestAccProject_full(t *testing.T) { "project_key": params["project_key"], "username1": params["username1"], "username2": params["username2"], + "email1": params["email1"], + "email2": params["email2"], "group1": params["group1"], "group2": params["group2"], "repo1": params["repo1"], @@ -352,27 +397,15 @@ func TestAccProject_full(t *testing.T) { projectUpdated := test.ExecuteTemplate("TestAccProjects", template, updateParams) resource.Test(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - createTestUser(t, username1, email1) - createTestUser(t, username2, email2) - createTestGroup(t, group1) - createTestGroup(t, group2) - createTestRepo(t, repo1) - createTestRepo(t, repo2) - }, - CheckDestroy: verifyDeleted(resourceName, func(id string, request *resty.Request) (*resty.Response, error) { - deleteTestUser(t, username1) - deleteTestUser(t, username2) - deleteTestGroup(t, group1) - deleteTestGroup(t, group2) - deleteTestRepo(t, repo1) - deleteTestRepo(t, repo2) - resp, err := verifyProject(id, request) - - return resp, err - }), + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: verifyDeleted(resourceName, verifyProject), ProviderFactories: testAccProviders(), + ExternalProviders: map[string]resource.ExternalProvider{ + "artifactory": { + Source: "jfrog/artifactory", + VersionConstraint: "10.1.3", + }, + }, Steps: []resource.TestStep{ { Config: project, diff --git a/pkg/project/util.go b/pkg/project/util.go index 31ee3c76..711f0b14 100644 --- a/pkg/project/util.go +++ b/pkg/project/util.go @@ -31,7 +31,6 @@ type Equatable interface { func retryOnSpecificMsgBody(matchString string) func(response *resty.Response, err error) bool { return func(response *resty.Response, err error) bool { - var responseBodyRegex = regexp.MustCompile(matchString) - return responseBodyRegex.MatchString(string(response.Body()[:])) + return regexp.MustCompile(matchString).MatchString(string(response.Body()[:])) } } diff --git a/terraform-registry-manifest.json b/terraform-registry-manifest.json index e4a68d89..ef650d4b 100644 --- a/terraform-registry-manifest.json +++ b/terraform-registry-manifest.json @@ -1,6 +1,6 @@ { "version": 1, "metadata": { - "protocol_versions": ["5.0"], - }, + "protocol_versions": ["5.0"] + } }