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: 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 +} 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" } }