From 97ea997debc75a15c7426b6a28590fed6b3b42e9 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 14 Mar 2024 11:58:41 -0700 Subject: [PATCH 1/3] Fix HTTP response error handling Previous version of Resty will set error to non-nil even for HTTP response error. Now it no longer does (matching Golang Http client). Thus we need to add code to handle http response error. Update project resource doc for import --- docs/resources/project.md | 8 ++++ examples/resources/project/import.sh | 1 + go.mod | 1 + go.sum | 2 + pkg/project/membership.go | 25 +++++++++-- pkg/project/repo.go | 47 ++++++++++++--------- pkg/project/resource_project.go | 40 +++++++++++++++--- pkg/project/resource_project_environment.go | 34 +++++++++++++-- pkg/project/resource_project_group.go | 26 ++++++++++-- pkg/project/resource_project_repository.go | 27 +++++++++--- pkg/project/resource_project_role.go | 33 ++++++++++++--- pkg/project/resource_project_user.go | 29 ++++++++++--- pkg/project/role.go | 39 ++++++++++++++--- pkg/project/util.go | 26 ++++++++++++ 14 files changed, 277 insertions(+), 61 deletions(-) create mode 100644 examples/resources/project/import.sh diff --git a/docs/resources/project.md b/docs/resources/project.md index 7d3d3be5..218a3ebb 100644 --- a/docs/resources/project.md +++ b/docs/resources/project.md @@ -141,3 +141,11 @@ Required: Optional: - `description` (String) + +## Import + +Import is supported using the following syntax: + +```shell +terraform import project.myproject myproj +``` diff --git a/examples/resources/project/import.sh b/examples/resources/project/import.sh new file mode 100644 index 00000000..a36c2597 --- /dev/null +++ b/examples/resources/project/import.sh @@ -0,0 +1 @@ +terraform import project.myproject myproj \ No newline at end of file diff --git a/go.mod b/go.mod index bcf2763e..cd926942 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 github.com/hashicorp/terraform-plugin-testing v1.7.0 github.com/jfrog/terraform-provider-shared v1.22.0 + github.com/samber/lo v1.39.0 golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 ) diff --git a/go.sum b/go.sum index c8f7a857..7182b5a4 100644 --- a/go.sum +++ b/go.sum @@ -177,6 +177,8 @@ github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBO github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= +github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= +github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= diff --git a/pkg/project/membership.go b/pkg/project/membership.go index c4ee60ee..a2f87933 100644 --- a/pkg/project/membership.go +++ b/pkg/project/membership.go @@ -3,6 +3,7 @@ package project import ( "context" "fmt" + "net/http" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -101,16 +102,21 @@ var readMembers = func(ctx context.Context, projectKey string, membershipType st membership := Membership{} - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParams(map[string]string{ "projectKey": projectKey, "membershipType": membershipType, }). SetResult(&membership). + SetError(&projectError). Get(projectMembershipsUrl) if err != nil { return nil, err } + if resp.IsError() { + return nil, fmt.Errorf("%s", projectError.String()) + } tflog.Trace(ctx, fmt.Sprintf("readMembers: %+v\n", membership)) @@ -163,14 +169,22 @@ var updateMember = func(ctx context.Context, projectKey string, membershipType s return fmt.Errorf("invalid membershipType: %s", membershipType) } - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParams(map[string]string{ "projectKey": projectKey, "membershipType": membershipType, "memberName": member.Name, }). SetBody(member). + SetError(&projectError). Put(projectMembershipUrl) + if err != nil { + return err + } + if resp.IsError() { + return fmt.Errorf("%s", projectError.String()) + } return err } @@ -196,16 +210,21 @@ var deleteMember = func(ctx context.Context, projectKey string, membershipType s return fmt.Errorf("invalid membershipType: %s", membershipType) } - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParams(map[string]string{ "projectKey": projectKey, "membershipType": membershipType, "memberName": member.Name, }). + SetError(&projectError). Delete(projectMembershipUrl) if err != nil { return err } + if resp.IsError() && resp.StatusCode() != http.StatusNotFound { + return fmt.Errorf("%s", projectError.String()) + } return nil } diff --git a/pkg/project/repo.go b/pkg/project/repo.go index cfba0e4f..bd882b51 100644 --- a/pkg/project/repo.go +++ b/pkg/project/repo.go @@ -54,14 +54,19 @@ var readRepos = func(ctx context.Context, projectKey string, m interface{}) ([]R artifactoryRepos := []ArtifactoryRepo{} - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParam("projectKey", projectKey). SetResult(&artifactoryRepos). + SetError(&projectError). Get("/artifactory/api/repositories?project={projectKey}") if err != nil { return nil, err } + if resp.IsError() { + return nil, fmt.Errorf("%s", projectError.String()) + } tflog.Trace(ctx, fmt.Sprintf("artifactoryRepos: %+v\n", artifactoryRepos)) @@ -127,13 +132,21 @@ var addRepos = func(ctx context.Context, projectKey string, repoKeys []RepoKey, var addRepo = func(ctx context.Context, projectKey string, repoKey RepoKey, req *resty.Request) error { tflog.Debug(ctx, fmt.Sprintf("addRepo: %s", repoKey)) - _, err := req. + var projectError ProjectErrorsResponse + resp, err := req. SetPathParams(map[string]string{ "projectKey": projectKey, "repoKey": string(repoKey), }). SetQueryParam("force", "true"). + SetError(&projectError). Put(projectsUrl + "/_/attach/repositories/{repoKey}/{projectKey}") + if err != nil { + return err + } + if resp.IsError() { + return fmt.Errorf("%s", projectError.String()) + } return err } @@ -159,32 +172,28 @@ var deleteRepos = func(ctx context.Context, repoKeys []RepoKey, m interface{}) e var deleteRepo = func(ctx context.Context, repoKey RepoKey, req *resty.Request) error { tflog.Debug(ctx, fmt.Sprintf("deleteRepo: %s", repoKey)) - type Error struct { - Code string `json:"code"` - Message string `json:"message"` - } - - type ErrorResponse struct { - Errors []Error `json:"errors"` - } - - var errorResp ErrorResponse - + var projectError ProjectErrorsResponse resp, err := req. SetPathParam("repoKey", string(repoKey)). - SetError(&errorResp). + SetError(&projectError). Delete(projectsUrl + "/_/attach/repositories/{repoKey}") + if err != nil { + return err + } + // Ignore 404 NOT_FOUND error when unassigning repo from project // Possible that repo was deleted out-of-band from TF - if resp.StatusCode() == http.StatusNotFound && len(errorResp.Errors) > 0 { - for _, error := range errorResp.Errors { - if error.Code == "NOT_FOUND" { - tflog.Warn(ctx, fmt.Sprintf("failed to unassign repo: %s", error.Message)) + if resp.StatusCode() == http.StatusNotFound && len(projectError.Errors) > 0 { + for _, e := range projectError.Errors { + if e.Code == "NOT_FOUND" { + tflog.Warn(ctx, fmt.Sprintf("failed to unassign repo: %s", e.Message)) return nil } } + } else if resp.IsError() { + return fmt.Errorf("%s", projectError.String()) } - return err + return nil } diff --git a/pkg/project/resource_project.go b/pkg/project/resource_project.go index 4cf563d8..7ac8a7c5 100644 --- a/pkg/project/resource_project.go +++ b/pkg/project/resource_project.go @@ -437,13 +437,23 @@ func projectResource() *schema.Resource { var readProject = func(ctx context.Context, data *schema.ResourceData, m interface{}) diag.Diagnostics { project := Project{} - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParam("projectKey", data.Id()). SetResult(&project). + SetError(&projectError). Get(projectUrl) + if err != nil { return diag.FromErr(err) } + if resp.StatusCode() == http.StatusNotFound { + data.SetId("") + return nil + } + if resp.IsError() { + return diag.Errorf("%s", projectError.String()) + } users := []Member{} useProjectUserResource := data.Get("use_project_user_resource").(bool) @@ -493,12 +503,17 @@ func projectResource() *schema.Resource { return diag.FromErr(err) } - _, err = m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetBody(project). + SetError(&projectError). Post(projectsUrl) if err != nil { return diag.FromErr(err) } + if resp.IsError() { + return diag.Errorf("%s", projectError.String()) + } data.SetId(project.Id()) @@ -547,13 +562,18 @@ func projectResource() *schema.Resource { return diag.FromErr(err) } - _, err = m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParam("projectKey", data.Id()). SetBody(project). + SetError(&projectError). Put(projectUrl) if err != nil { return diag.FromErr(err) } + if resp.IsError() { + return diag.Errorf("%s", projectError.String()) + } data.SetId(project.Id()) @@ -615,16 +635,22 @@ func projectResource() *schema.Resource { }, ) - resp, err := req. + var projectError ProjectErrorsResponse + res, err := req. SetPathParam("projectKey", data.Id()). + SetError(&projectError). Delete(projectUrl) if err != nil { - if resp.StatusCode() == http.StatusNotFound { - data.SetId("") - } return diag.FromErr(err) } + if res.StatusCode() == http.StatusNotFound { + data.SetId("") + return nil + } + if res.IsError() { + return diag.Errorf("%s", projectError.String()) + } return nil } diff --git a/pkg/project/resource_project_environment.go b/pkg/project/resource_project_environment.go index 96858b31..c7921160 100644 --- a/pkg/project/resource_project_environment.go +++ b/pkg/project/resource_project_environment.go @@ -3,6 +3,7 @@ package project import ( "context" "fmt" + "net/http" "regexp" "strings" @@ -57,13 +58,22 @@ func projectEnvironmentResource() *schema.Resource { projectKey := data.Get("project_key").(string) var envs []ProjectEnvironment - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParam("projectKey", projectKey). SetResult(&envs). + SetError(&projectError). Get(projectEnvironmentUrl) if err != nil { return diag.FromErr(err) } + if resp.StatusCode() == http.StatusNotFound { + data.SetId("") + return nil + } + if resp.IsError() { + return diag.Errorf("%s", projectError.String()) + } var matchedEnv *ProjectEnvironment for _, env := range envs { @@ -91,13 +101,18 @@ func projectEnvironmentResource() *schema.Resource { Name: fmt.Sprintf("%s-%s", projectKey, data.Get("name").(string)), } - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParam("projectKey", projectKey). SetBody(projectEnvironment). + SetError(&projectError). Post(projectEnvironmentUrl) if err != nil { return diag.FromErr(err) } + if resp.IsError() { + return diag.Errorf("%s", projectError.String()) + } data.SetId(projectEnvironment.Id()) @@ -112,16 +127,21 @@ func projectEnvironmentResource() *schema.Resource { NewName: fmt.Sprintf("%s-%s", projectKey, newName), } - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParams(map[string]string{ "projectKey": projectKey, "environmentName": fmt.Sprintf("%s-%s", projectKey, oldName), }). SetBody(projectEnvironmentUpdate). + SetError(&projectError). Post(projectEnvironmentUrl + "/{environmentName}/rename") if err != nil { return diag.FromErr(err) } + if resp.IsError() { + return diag.Errorf("%s", projectError.String()) + } data.SetId(projectEnvironmentUpdate.Id()) data.Set("name", newName) @@ -131,15 +151,21 @@ func projectEnvironmentResource() *schema.Resource { var deleteProjectEnvironment = func(ctx context.Context, data *schema.ResourceData, m interface{}) diag.Diagnostics { projectKey := data.Get("project_key").(string) - _, err := m.(util.ProvderMetadata).Client.R(). + + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParams(map[string]string{ "projectKey": projectKey, "environmentName": fmt.Sprintf("%s-%s", projectKey, data.Get("name")), }). + SetError(&projectError). Delete(projectEnvironmentUrl + "/{environmentName}") if err != nil { return diag.FromErr(err) } + if resp.IsError() && resp.StatusCode() != http.StatusNotFound { + return diag.Errorf("%s", projectError.String()) + } data.SetId("") diff --git a/pkg/project/resource_project_group.go b/pkg/project/resource_project_group.go index ec625bff..f225d636 100644 --- a/pkg/project/resource_project_group.go +++ b/pkg/project/resource_project_group.go @@ -3,6 +3,7 @@ package project import ( "context" "fmt" + "net/http" "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -75,17 +76,26 @@ func projectGroupResource() *schema.Resource { projectGroup := unpackProjectGroup(data) var loadedProjectGroup ProjectGroup - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParams(map[string]string{ "projectKey": projectGroup.ProjectKey, "name": projectGroup.Name, }). SetResult(&loadedProjectGroup). + SetError(&projectError). Get(projectGroupsUrl) if err != nil { return diag.FromErr(err) } + if resp.StatusCode() == http.StatusNotFound { + data.SetId("") + return nil + } + if resp.IsError() { + return diag.Errorf("%s", projectError.String()) + } loadedProjectGroup.ProjectKey = projectGroup.ProjectKey @@ -95,17 +105,22 @@ func projectGroupResource() *schema.Resource { var upsertProjectGroup = func(ctx context.Context, data *schema.ResourceData, m interface{}) diag.Diagnostics { projectGroup := unpackProjectGroup(data) - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParams(map[string]string{ "projectKey": projectGroup.ProjectKey, "name": projectGroup.Name, }). SetBody(&projectGroup). + SetError(&projectError). Put(projectGroupsUrl) if err != nil { return diag.FromErr(err) } + if resp.IsError() { + return diag.Errorf("%s", projectError.String()) + } data.SetId(projectGroup.Id()) @@ -115,16 +130,21 @@ func projectGroupResource() *schema.Resource { var deleteProjectGroup = func(ctx context.Context, data *schema.ResourceData, m interface{}) diag.Diagnostics { projectGroup := unpackProjectGroup(data) - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParams(map[string]string{ "projectKey": projectGroup.ProjectKey, "name": projectGroup.Name, }). + SetError(&projectError). Delete(projectGroupsUrl) if err != nil { return diag.FromErr(err) } + if resp.IsError() && resp.StatusCode() != http.StatusNotFound { + return diag.Errorf("%s", projectError.String()) + } data.SetId("") diff --git a/pkg/project/resource_project_repository.go b/pkg/project/resource_project_repository.go index f4a8d732..4ca6999c 100644 --- a/pkg/project/resource_project_repository.go +++ b/pkg/project/resource_project_repository.go @@ -42,18 +42,23 @@ func projectRepositoryResource() *schema.Resource { var repo Repository + var projectError ProjectErrorsResponse resp, err := m.(util.ProvderMetadata).Client.R(). SetResult(&repo). SetPathParam("key", repoKey). + SetError(&projectError). Get("/artifactory/api/repositories/{key}") if err != nil { - if resp != nil && (resp.StatusCode() == http.StatusBadRequest || resp.StatusCode() == http.StatusNotFound) { - data.SetId("") - return nil - } return diag.FromErr(err) } + if resp.StatusCode() == http.StatusBadRequest || resp.StatusCode() == http.StatusNotFound { + data.SetId("") + return nil + } + if resp.IsError() { + return diag.Errorf("%s", projectError.String()) + } if repo.ProjectKey == "" { tflog.Info(ctx, "no project_key for repo", map[string]any{"repoKey": repoKey}) @@ -77,16 +82,21 @@ func projectRepositoryResource() *schema.Resource { projectKey := data.Get("project_key").(string) repoKey := data.Get("key").(string) - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParams(map[string]string{ "projectKey": projectKey, "repoKey": repoKey, }). + SetError(&projectError). Put("/access/api/v1/projects/_/attach/repositories/{repoKey}/{projectKey}?force=true") if err != nil { return diag.FromErr(err) } + if resp.IsError() { + return diag.Errorf("%s", projectError.String()) + } data.SetId(fmt.Sprintf("%s-%s", projectKey, repoKey)) @@ -96,13 +106,18 @@ func projectRepositoryResource() *schema.Resource { var deleteProjectRepository = func(ctx context.Context, data *schema.ResourceData, m interface{}) diag.Diagnostics { repoKey := data.Get("key").(string) - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParam("repoKey", repoKey). + SetError(&projectError). Delete("/access/api/v1/projects/_/attach/repositories/{repoKey}") if err != nil { return diag.FromErr(err) } + if resp.IsError() && resp.StatusCode() != http.StatusNotFound { + return diag.Errorf("%s", projectError.String()) + } data.SetId("") diff --git a/pkg/project/resource_project_role.go b/pkg/project/resource_project_role.go index c95bdac0..f5014a45 100644 --- a/pkg/project/resource_project_role.go +++ b/pkg/project/resource_project_role.go @@ -132,17 +132,26 @@ func projectRoleResource() *schema.Resource { var role Role projectKey := data.Get("project_key").(string) - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParams(map[string]string{ "projectKey": projectKey, "roleName": data.Id(), }). SetResult(&role). + SetError(&projectError). Get(projectRoleUrl) if err != nil { return diag.FromErr(err) } + if resp.StatusCode() == http.StatusNotFound { + data.SetId("") + return nil + } + if resp.IsError() { + return diag.Errorf("%s", projectError.String()) + } return packRole(ctx, data, role, projectKey) } @@ -162,14 +171,19 @@ func projectRoleResource() *schema.Resource { projectKey := data.Get("project_key").(string) role := unpackRole(data) - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParam("projectKey", projectKey). SetBody(role). + SetError(&projectError). Post(projectRolesUrl) if err != nil { return diag.FromErr(err) } + if resp.IsError() { + return diag.Errorf("%s", projectError.String()) + } data.SetId(role.Id()) @@ -180,17 +194,22 @@ func projectRoleResource() *schema.Resource { projectKey := data.Get("project_key").(string) role := unpackRole(data) - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParams(map[string]string{ "projectKey": projectKey, "roleName": role.Name, }). + SetError(&projectError). SetBody(role). Put(projectRoleUrl) if err != nil { return diag.FromErr(err) } + if resp.IsError() { + return diag.Errorf("%s", projectError.String()) + } data.SetId(role.Id()) @@ -198,19 +217,21 @@ func projectRoleResource() *schema.Resource { } var deleteProjectRole = func(ctx context.Context, data *schema.ResourceData, m interface{}) diag.Diagnostics { + var projectError ProjectErrorsResponse resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParams(map[string]string{ "roleName": data.Id(), "projectKey": data.Get("project_key").(string), }). + SetError(&projectError). Delete(projectRoleUrl) if err != nil { - if resp.StatusCode() == http.StatusNotFound { - data.SetId("") - } return diag.FromErr(err) } + if resp.IsError() && resp.StatusCode() != http.StatusNotFound { + return diag.Errorf("%s", projectError.String()) + } return nil } diff --git a/pkg/project/resource_project_user.go b/pkg/project/resource_project_user.go index 9ab3d187..cb7880b5 100644 --- a/pkg/project/resource_project_user.go +++ b/pkg/project/resource_project_user.go @@ -85,19 +85,24 @@ func projectUserResource() *schema.Resource { projectUser := unpackProjectUser(data) var loadedProjectUser ProjectUser + var projectError ProjectErrorsResponse resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParams(map[string]string{ "projectKey": projectUser.ProjectKey, "name": projectUser.Name, }). + SetError(&projectError). SetResult(&loadedProjectUser). Get(projectUsersUrl) - if resp != nil && resp.StatusCode() == http.StatusNotFound && projectUser.IgnoreMissingUser { + if err != nil { + return diag.FromErr(err) + } + if resp.StatusCode() == http.StatusNotFound && projectUser.IgnoreMissingUser { // ignore missing user, reuse local info for state loadedProjectUser = projectUser - } else if err != nil { - return diag.FromErr(err) + } else if resp.IsError() { + return diag.Errorf("%s", projectError.String()) } loadedProjectUser.ProjectKey = projectUser.ProjectKey @@ -109,18 +114,23 @@ func projectUserResource() *schema.Resource { var upsertProjectUser = func(ctx context.Context, data *schema.ResourceData, m interface{}) diag.Diagnostics { projectUser := unpackProjectUser(data) + var projectError ProjectErrorsResponse resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParams(map[string]string{ "projectKey": projectUser.ProjectKey, "name": projectUser.Name, }). SetBody(&projectUser). + SetError(&projectError). Put(projectUsersUrl) // allow missing user? -> report warning and ignore error diagnostics := diag.Diagnostics{} - if resp != nil && resp.StatusCode() == http.StatusNotFound { + if err != nil { + return diag.FromErr(err) + } + if resp.StatusCode() == http.StatusNotFound { if projectUser.IgnoreMissingUser { diagnostics = append(diagnostics, diag.Diagnostic{ Severity: diag.Warning, @@ -129,8 +139,8 @@ func projectUserResource() *schema.Resource { } else { return diag.Errorf("user '%s' not found, project membership not created", projectUser.Name) } - } else if err != nil { - return diag.FromErr(err) + } else if resp.IsError() { + return diag.Errorf("%s", projectError.String()) } data.SetId(projectUser.Id()) @@ -147,16 +157,21 @@ func projectUserResource() *schema.Resource { var deleteProjectUser = func(ctx context.Context, data *schema.ResourceData, m interface{}) diag.Diagnostics { projectUser := unpackProjectUser(data) - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParams(map[string]string{ "projectKey": projectUser.ProjectKey, "name": projectUser.Name, }). + SetError(&projectError). Delete(projectUsersUrl) if err != nil { return diag.FromErr(err) } + if resp.IsError() && resp.StatusCode() != http.StatusNotFound { + return diag.Errorf("%s", projectError.String()) + } data.SetId("") diff --git a/pkg/project/role.go b/pkg/project/role.go index e88fccc3..db7c6a3d 100644 --- a/pkg/project/role.go +++ b/pkg/project/role.go @@ -3,6 +3,7 @@ package project import ( "context" "fmt" + "net/http" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -79,14 +80,19 @@ var readRoles = func(ctx context.Context, projectKey string, m interface{}) ([]R roles := []Role{} - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParam("projectKey", projectKey). SetResult(&roles). + SetError(&projectError). Get(projectRolesUrl) if err != nil { return nil, err } + if resp.IsError() { + return nil, fmt.Errorf("%s", projectError.String()) + } tflog.Trace(ctx, fmt.Sprintf("roles: %+v\n", roles)) @@ -145,26 +151,42 @@ var updateRoles = func(ctx context.Context, projectKey string, terraformRoles [] var addRole = func(ctx context.Context, projectKey string, role Role, m interface{}) error { tflog.Debug(ctx, "addRole") - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParam("projectKey", projectKey). SetBody(role). + SetError(&projectError). Post(projectRolesUrl) + if err != nil { + return err + } + if resp.IsError() { + return fmt.Errorf("%s", projectError.String()) + } - return err + return nil } var updateRole = func(ctx context.Context, projectKey string, role Role, m interface{}) error { tflog.Debug(ctx, "updateRole") - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParams(map[string]string{ "projectKey": projectKey, "roleName": role.Name, }). SetBody(role). + SetError(&projectError). Put(projectRoleUrl) + if err != nil { + return err + } + if resp.IsError() { + return fmt.Errorf("%s", projectError.String()) + } - return err + return nil } var deleteRoles = func(ctx context.Context, projectKey string, roles []Role, m interface{}) error { @@ -184,17 +206,22 @@ var deleteRole = func(ctx context.Context, projectKey string, role Role, m inter tflog.Debug(ctx, "deleteRole") tflog.Trace(ctx, fmt.Sprintf("%+v\n", role)) - _, err := m.(util.ProvderMetadata).Client.R(). + var projectError ProjectErrorsResponse + resp, err := m.(util.ProvderMetadata).Client.R(). SetPathParams(map[string]string{ "projectKey": projectKey, "roleName": role.Name, }). SetBody(role). + SetError(&projectError). Delete(projectRoleUrl) if err != nil { return err } + if resp.IsError() && resp.StatusCode() != http.StatusNotFound { + return fmt.Errorf("%s", projectError.String()) + } return nil } diff --git a/pkg/project/util.go b/pkg/project/util.go index 2a83eff8..eb90ad6c 100644 --- a/pkg/project/util.go +++ b/pkg/project/util.go @@ -1,11 +1,13 @@ package project import ( + "fmt" "math" "regexp" "github.com/go-resty/resty/v2" "github.com/jfrog/terraform-provider-shared/util/sdk" + "github.com/samber/lo" ) func BytesToGibibytes(bytes int64) int { @@ -34,3 +36,27 @@ func retryOnSpecificMsgBody(matchString string) func(response *resty.Response, e return regexp.MustCompile(matchString).MatchString(string(response.Body()[:])) } } + +type ProjectError struct { + Code string `json:"code"` + Message string `json:"message"` +} + +func (e ProjectError) String() string { + return fmt.Sprintf("%s - %s", e.Code, e.Message) +} + +type ProjectErrorsResponse struct { + Errors []ProjectError `json:"errors"` +} + +func (r ProjectErrorsResponse) String() string { + errs := lo.Reduce(r.Errors, func(err string, item ProjectError, _ int) string { + if err == "" { + return item.String() + } else { + return fmt.Sprintf("%s, %s", err, item.String()) + } + }, "") + return errs +} From 40b2b10a05fe432106ab80402a8ad34c4516e6dd Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 14 Mar 2024 11:59:42 -0700 Subject: [PATCH 2/3] Update sample.tf --- sample.tf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sample.tf b/sample.tf index 86509e80..4fc17040 100644 --- a/sample.tf +++ b/sample.tf @@ -1,8 +1,7 @@ -# Required for Terraform 0.13 and up (https://www.terraform.io/upgrade-guides/0-13.html) terraform { required_providers { project = { - source = "registry.terraform.io/jfrog/project" + source = "jfrog/project" version = "1.5.0" } } From 13b00fea8e8b38865b4a5eb21a53cfe37280a108 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 14 Mar 2024 12:02:39 -0700 Subject: [PATCH 3/3] Update CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47e6e119..1cff1c5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.5.1 (March 14, 2024) + +BUG FIXES: + +* Fix HTTP response error handling due to change of behavior (for better consistency) from Resty client. PR: [#106](https://github.com/jfrog/terraform-provider-project/pull/106) + ## 1.5.0 (March 13, 2024) FEATURES: