Skip to content

Commit

Permalink
Merge pull request #97 from jfrog/add-retry-to-project-deletion
Browse files Browse the repository at this point in the history
Add retry logic for project deletion
  • Loading branch information
alexhung authored Feb 8, 2024
2 parents 920f654 + ace76a9 commit 2410db1
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 42 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 1.3.5 (Feburary 9, 2024)

BUG FIXES:

* resource/project: Add retry logic for resource deletion. Issue: [#96](https://github.com/jfrog/terraform-provider-project/issues/96) PR: [#97](https://github.com/jfrog/terraform-provider-project/pull/97)

## 1.3.4 (October 31, 2023). Tested on Artifactory 7.71.8 and Xray 3.86.9

BUG FIXES:
Expand Down
29 changes: 17 additions & 12 deletions pkg/project/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
Expand All @@ -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),
Expand All @@ -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)
}
Expand All @@ -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 {
Expand All @@ -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}")
Expand Down
11 changes: 10 additions & 1 deletion pkg/project/resource_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)

Expand Down
83 changes: 58 additions & 25 deletions pkg/project/resource_project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,13 +271,53 @@ 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,
"repo2": repo2,
}

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 }}"
Expand All @@ -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"]
}
Expand All @@ -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,
]
}
`

Expand All @@ -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"],
Expand All @@ -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,
Expand Down
3 changes: 1 addition & 2 deletions pkg/project/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()[:]))
}
}
4 changes: 2 additions & 2 deletions terraform-registry-manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": 1,
"metadata": {
"protocol_versions": ["5.0"],
},
"protocol_versions": ["5.0"]
}
}

0 comments on commit 2410db1

Please sign in to comment.