From dc136f0818bfcfd31b86e5d86e27d2b6ac756f66 Mon Sep 17 00:00:00 2001 From: Ivo Verberk Date: Tue, 9 Apr 2024 08:25:02 +0200 Subject: [PATCH] feat: add `application_role_membership` and `organization_role_membership` resources (#12) --- docs/resources/application_role_membership.md | 58 ++++ .../resources/organization_role_membership.md | 58 ++++ .../resource.tf | 26 ++ .../resource.tf | 26 ++ .../application_role_membership_resource.go | 253 ++++++++++++++++++ ...plication_role_membership_resource_test.go | 68 +++++ .../organization_role_membership_resource.go | 253 ++++++++++++++++++ ...anization_role_membership_resource_test.go | 68 +++++ internal/provider/provider.go | 2 + 9 files changed, 812 insertions(+) create mode 100644 docs/resources/application_role_membership.md create mode 100644 docs/resources/organization_role_membership.md create mode 100644 examples/resources/sonatypeiq_application_role_membership/resource.tf create mode 100644 examples/resources/sonatypeiq_organization_role_membership/resource.tf create mode 100755 internal/provider/application_role_membership_resource.go create mode 100755 internal/provider/application_role_membership_resource_test.go create mode 100755 internal/provider/organization_role_membership_resource.go create mode 100755 internal/provider/organization_role_membership_resource_test.go diff --git a/docs/resources/application_role_membership.md b/docs/resources/application_role_membership.md new file mode 100644 index 0000000..d8fe763 --- /dev/null +++ b/docs/resources/application_role_membership.md @@ -0,0 +1,58 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "sonatypeiq_application_role_membership Resource - terraform-provider-sonatypeiq" +subcategory: "" +description: |- + +--- + +# sonatypeiq_application_role_membership (Resource) + + + +## Example Usage + +```terraform +data "sonatypeiq_application" "sandbox" { + public_id = "sandbox-application" +} + +data "sonatypeiq_role" "developer" { + name = "Developer" +} + +resource "sonatypeiq_user" "example_user" { + username = "example2" + password = "randomthing" + first_name = "Example" + last_name = "User" + email = "example@user.tld" +} + +# Create and manage application role memberships for Sonatype IQ Server +resource "sonatypeiq_application_role_membership" "application_role_membership" { + application_id = data.sonatypeiq_application.sandbox.id + role_id = data.sonatypeiq_role.developer.id + user_name = sonatypeiq_user.example_user.username + + # user_name and group_name are mutually exclusive. + # group_name = "developers" +} +``` + + +## Schema + +### Required + +- `application_id` (String) The application ID +- `role_id` (String) The role ID + +### Optional + +- `group_name` (String) The group name of the group (mutually exclusive with user_name) +- `user_name` (String) The username of the user (mutually exclusive with group_name) + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/docs/resources/organization_role_membership.md b/docs/resources/organization_role_membership.md new file mode 100644 index 0000000..bd05444 --- /dev/null +++ b/docs/resources/organization_role_membership.md @@ -0,0 +1,58 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "sonatypeiq_organization_role_membership Resource - terraform-provider-sonatypeiq" +subcategory: "" +description: |- + +--- + +# sonatypeiq_organization_role_membership (Resource) + + + +## Example Usage + +```terraform +data "sonatypeiq_organization" "sandbox" { + name = "Sandbox Organization" +} + +data "sonatypeiq_role" "developer" { + name = "Developer" +} + +resource "sonatypeiq_user" "example" { + username = "example" + password = "randomthing" + first_name = "Example" + last_name = "User" + email = "example@user.tld" +} + +# Create and manage application role memberships for Sonatype IQ Server +resource "sonatypeiq_organization_role_membership" "organization_role_membership" { + organization_id = data.sonatypeiq_organization.sandbox.id + role_id = data.sonatypeiq_role.developer.id + username = sonatypeiq_user.example.username + + # group_name can also be used but it is mutually exclusive with the user_name attribute. + # group_name = "developers" +} +``` + + +## Schema + +### Required + +- `organization_id` (String) The organization ID +- `role_id` (String) The role ID + +### Optional + +- `group_name` (String) The group name of the group (mutually exclusive with user_name) +- `user_name` (String) The username of the user (mutually exclusive with group_name) + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/examples/resources/sonatypeiq_application_role_membership/resource.tf b/examples/resources/sonatypeiq_application_role_membership/resource.tf new file mode 100644 index 0000000..4a5a332 --- /dev/null +++ b/examples/resources/sonatypeiq_application_role_membership/resource.tf @@ -0,0 +1,26 @@ +data "sonatypeiq_application" "sandbox" { + public_id = "sandbox-application" +} + +data "sonatypeiq_role" "developer" { + name = "Developer" +} + +resource "sonatypeiq_user" "example_user" { + username = "example2" + password = "randomthing" + first_name = "Example" + last_name = "User" + email = "example@user.tld" +} + +# Create and manage application role memberships for Sonatype IQ Server +resource "sonatypeiq_application_role_membership" "application_role_membership" { + application_id = data.sonatypeiq_application.sandbox.id + role_id = data.sonatypeiq_role.developer.id + user_name = sonatypeiq_user.example_user.username + + # user_name and group_name are mutually exclusive. + # group_name = "developers" +} + diff --git a/examples/resources/sonatypeiq_organization_role_membership/resource.tf b/examples/resources/sonatypeiq_organization_role_membership/resource.tf new file mode 100644 index 0000000..c65601a --- /dev/null +++ b/examples/resources/sonatypeiq_organization_role_membership/resource.tf @@ -0,0 +1,26 @@ +data "sonatypeiq_organization" "sandbox" { + name = "Sandbox Organization" +} + +data "sonatypeiq_role" "developer" { + name = "Developer" +} + +resource "sonatypeiq_user" "example" { + username = "example" + password = "randomthing" + first_name = "Example" + last_name = "User" + email = "example@user.tld" +} + +# Create and manage application role memberships for Sonatype IQ Server +resource "sonatypeiq_organization_role_membership" "organization_role_membership" { + organization_id = data.sonatypeiq_organization.sandbox.id + role_id = data.sonatypeiq_role.developer.id + username = sonatypeiq_user.example.username + + # group_name can also be used but it is mutually exclusive with the user_name attribute. + # group_name = "developers" +} + diff --git a/internal/provider/application_role_membership_resource.go b/internal/provider/application_role_membership_resource.go new file mode 100755 index 0000000..bee92f9 --- /dev/null +++ b/internal/provider/application_role_membership_resource.go @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2019-present Sonatype, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package provider + +import ( + "context" + "fmt" + "io" + "net/http" + + "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + sonatypeiq "github.com/sonatype-nexus-community/nexus-iq-api-client-go" +) + +// applicationRoleMembershipResource is the resource implementation. +type applicationRoleMembershipResource struct { + baseResource +} + +type applicationRoleMembershipModelResource struct { + ID types.String `tfsdk:"id"` + RoleId types.String `tfsdk:"role_id"` + ApplicationId types.String `tfsdk:"application_id"` + UserName types.String `tfsdk:"user_name"` + GroupName types.String `tfsdk:"group_name"` +} + +// NewApplicationRoleMembershipResource is a helper function to simplify the provider implementation. +func NewApplicationRoleMembershipResource() resource.Resource { + return &applicationRoleMembershipResource{} +} + +// Metadata returns the resource type name. +func (r *applicationRoleMembershipResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_application_role_membership" +} + +// Schema defines the schema for the resource. +func (r *applicationRoleMembershipResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "role_id": schema.StringAttribute{ + Required: true, + Description: "The role ID", + }, + "application_id": schema.StringAttribute{ + Required: true, + Description: "The application ID", + }, + "user_name": schema.StringAttribute{ + Optional: true, + Description: "The username of the user (mutually exclusive with group_name)", + }, + "group_name": schema.StringAttribute{ + Optional: true, + Description: "The group name of the group (mutually exclusive with user_name)", + }, + }, + } +} + +func (r *applicationRoleMembershipResource) ConfigValidators(ctx context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + resourcevalidator.ExactlyOneOf( + path.MatchRoot("user_name"), + path.MatchRoot("group_name"), + ), + } +} + +// Create creates the resource and sets the initial Terraform state. +func (r *applicationRoleMembershipResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data applicationRoleMembershipModelResource + + // Read Terraform plan data into the model + diags := req.Plan.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Call API to create application role membership + ctx = context.WithValue( + ctx, + sonatypeiq.ContextBasicAuth, + r.auth, + ) + + // Determine the member type, which can be any of group or user. + // The resource validator makes sure that exactly one of these is configured. + var memberType, memberName string + if !data.GroupName.IsNull() { + memberType = "group" + memberName = data.GroupName.ValueString() + } else { + memberType = "user" + memberName = data.UserName.ValueString() + } + + apiRequest := r.client.RoleMembershipsAPI.GrantRoleMembershipApplicationOrOrganization(ctx, "application", data.ApplicationId.ValueString(), data.RoleId.ValueString(), memberType, memberName) + apiResponse, err := r.client.RoleMembershipsAPI.GrantRoleMembershipApplicationOrOrganizationExecute(apiRequest) + + // Call API + if err != nil { + error_body, _ := io.ReadAll(apiResponse.Body) + resp.Diagnostics.AddError( + "Error creating application role membership", + "Could not create application role membership, unexpected error: "+apiResponse.Status+": "+string(error_body), + ) + return + } + + // Map response body to schema and populate Computed attribute values. + // Because the application role membership does not have an ID of its own, we create a synthetic one based on the provided attributes. + data.ID = types.StringValue(fmt.Sprintf("%s_%s_%s_%s", data.ApplicationId.ValueString(), data.RoleId.ValueString(), memberType, memberName)) + + // Set state to fully populated data + diags = resp.State.Set(ctx, data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read refreshes the Terraform state with the latest data. +func (r *applicationRoleMembershipResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data applicationRoleMembershipModelResource + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + ctx = context.WithValue( + ctx, + sonatypeiq.ContextBasicAuth, + r.auth, + ) + + // Get refreshed application role membership from IQ + apiRequest := r.client.RoleMembershipsAPI.GetRoleMembershipsApplicationOrOrganization(ctx, "application", data.ApplicationId.ValueString()) + roleMemberships, apiResponse, err := r.client.RoleMembershipsAPI.GetRoleMembershipsApplicationOrOrganizationExecute(apiRequest) + + // Check if we received a list of role mappings. + if err != nil { + if apiResponse.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + } else { + resp.Diagnostics.AddError( + "Error Reading IQ application role membership", + "Could not read application role membership with ID "+data.ID.ValueString()+": "+err.Error(), + ) + } + return + } + + // Determine the member type, which can be any of group or user. + // The resource validator makes sure that exactly one of these is configured. + var memberType, memberName string + if !data.GroupName.IsNull() { + memberType = "GROUP" + memberName = data.GroupName.ValueString() + } else { + memberType = "USER" + memberName = data.UserName.ValueString() + } + + // Check for application role membership existence. + var membershipFound bool + for _, roleMembership := range roleMemberships.MemberMappings { + if *roleMembership.RoleId == data.RoleId.ValueString() { + for _, member := range roleMembership.Members { + if *member.Type == memberType && *member.UserOrGroupName == memberName && *member.OwnerType == "APPLICATION" && *member.OwnerId == data.ApplicationId.ValueString() { + membershipFound = true + break + } + } + } + } + + if !membershipFound { + resp.State.RemoveResource(ctx) + return + } + + // Set refreshed state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *applicationRoleMembershipResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data applicationRoleMembershipModelResource + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Make Delete API Call + ctx = context.WithValue( + ctx, + sonatypeiq.ContextBasicAuth, + r.auth, + ) + + // Determine the member type, which can be any of group or user. + // The resource validator makes sure that exactly one of these is configured. + var memberType, memberName string + if !data.GroupName.IsNull() { + memberType = "group" + memberName = data.GroupName.ValueString() + } else { + memberType = "user" + memberName = data.UserName.ValueString() + } + + apiRequest := r.client.RoleMembershipsAPI.RevokeRoleMembershipApplicationOrOrganization(ctx, "application", data.ApplicationId.ValueString(), data.RoleId.ValueString(), memberType, memberName) + apiResponse, err := r.client.RoleMembershipsAPI.RevokeRoleMembershipApplicationOrOrganizationExecute(apiRequest) + if err != nil { + error_body, _ := io.ReadAll(apiResponse.Body) + resp.Diagnostics.AddError( + "Error deleting application role membership", + "Could not delete application role membership, unexpected error: "+apiResponse.Status+": "+string(error_body), + ) + return + } +} diff --git a/internal/provider/application_role_membership_resource_test.go b/internal/provider/application_role_membership_resource_test.go new file mode 100755 index 0000000..4c74452 --- /dev/null +++ b/internal/provider/application_role_membership_resource_test.go @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019-present Sonatype, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccApplicationRoleMembershipResource(t *testing.T) { + + userName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: fmt.Sprintf(providerConfig+` + data "sonatypeiq_application" "sandbox" { + public_id = "sandbox-application" + } + + data "sonatypeiq_role" "developer" { + name = "Developer" + } + + resource "sonatypeiq_user" "user" { + username = "%s" + password = "randomthing" + first_name = "Example" + last_name = "User" + email = "example@user.tld" + } + + resource "sonatypeiq_application_role_membership" "test" { + role_id = data.sonatypeiq_role.developer.id + application_id = data.sonatypeiq_application.sandbox.id + user_name = sonatypeiq_user.user.username + } + + `, userName), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify application role membership + resource.TestCheckResourceAttrSet("sonatypeiq_application_role_membership.test", "id"), + resource.TestCheckResourceAttr("sonatypeiq_application_role_membership.test", "user_name", userName), + ), + }, + }, + }) +} diff --git a/internal/provider/organization_role_membership_resource.go b/internal/provider/organization_role_membership_resource.go new file mode 100755 index 0000000..433625c --- /dev/null +++ b/internal/provider/organization_role_membership_resource.go @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2019-present Sonatype, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package provider + +import ( + "context" + "fmt" + "io" + "net/http" + + "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + sonatypeiq "github.com/sonatype-nexus-community/nexus-iq-api-client-go" +) + +// organizatonRoleMembershipResource is the resource implementation. +type organizationRoleMembershipResource struct { + baseResource +} + +type organizationRoleMembershipModelResource struct { + ID types.String `tfsdk:"id"` + RoleId types.String `tfsdk:"role_id"` + OrganizationId types.String `tfsdk:"organization_id"` + UserName types.String `tfsdk:"user_name"` + GroupName types.String `tfsdk:"group_name"` +} + +// NewOrganizationRoleMembershipResource is a helper function to simplify the provider implementation. +func NewOrganizationRoleMembershipResource() resource.Resource { + return &organizationRoleMembershipResource{} +} + +// Metadata returns the resource type name. +func (r *organizationRoleMembershipResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_organization_role_membership" +} + +// Schema defines the schema for the resource. +func (r *organizationRoleMembershipResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "role_id": schema.StringAttribute{ + Required: true, + Description: "The role ID", + }, + "organization_id": schema.StringAttribute{ + Required: true, + Description: "The organization ID", + }, + "user_name": schema.StringAttribute{ + Optional: true, + Description: "The username of the user (mutually exclusive with group_name)", + }, + "group_name": schema.StringAttribute{ + Optional: true, + Description: "The group name of the group (mutually exclusive with user_name)", + }, + }, + } +} + +func (r *organizationRoleMembershipResource) ConfigValidators(ctx context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + resourcevalidator.ExactlyOneOf( + path.MatchRoot("user_name"), + path.MatchRoot("group_name"), + ), + } +} + +// Create creates the resource and sets the initial Terraform state. +func (r *organizationRoleMembershipResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data organizationRoleMembershipModelResource + + // Read Terraform plan data into the model + diags := req.Plan.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Call API to create organization role membership + ctx = context.WithValue( + ctx, + sonatypeiq.ContextBasicAuth, + r.auth, + ) + + // Determine the member type, which can be any of group or user. + // The resource validator makes sure that exactly one of these is configured. + var memberType, memberName string + if !data.GroupName.IsNull() { + memberType = "group" + memberName = data.GroupName.ValueString() + } else { + memberType = "user" + memberName = data.UserName.ValueString() + } + + apiRequest := r.client.RoleMembershipsAPI.GrantRoleMembershipApplicationOrOrganization(ctx, "organization", data.OrganizationId.ValueString(), data.RoleId.ValueString(), memberType, memberName) + apiResponse, err := r.client.RoleMembershipsAPI.GrantRoleMembershipApplicationOrOrganizationExecute(apiRequest) + + // Call API + if err != nil { + error_body, _ := io.ReadAll(apiResponse.Body) + resp.Diagnostics.AddError( + "Error creating organization role membership", + "Could not create organization role membership, unexpected error: "+apiResponse.Status+": "+string(error_body), + ) + return + } + + // Map response body to schema and populate Computed attribute values. + // Because the organization role membership does not have an ID of its own, we create a synthetic one based on the provided attributes. + data.ID = types.StringValue(fmt.Sprintf("%s_%s_%s_%s", data.OrganizationId.ValueString(), data.RoleId.ValueString(), memberType, memberName)) + + // Set state to fully populated data + diags = resp.State.Set(ctx, data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read refreshes the Terraform state with the latest data. +func (r *organizationRoleMembershipResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data organizationRoleMembershipModelResource + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + ctx = context.WithValue( + ctx, + sonatypeiq.ContextBasicAuth, + r.auth, + ) + + // Get refreshed organization role membership from IQ + apiRequest := r.client.RoleMembershipsAPI.GetRoleMembershipsApplicationOrOrganization(ctx, "organization", data.OrganizationId.ValueString()) + roleMemberships, apiResponse, err := r.client.RoleMembershipsAPI.GetRoleMembershipsApplicationOrOrganizationExecute(apiRequest) + + // Check if we received a list of role mappings. + if err != nil { + if apiResponse.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + } else { + resp.Diagnostics.AddError( + "Error Reading IQ organization role membership", + "Could not read organization role membership with ID "+data.ID.ValueString()+": "+err.Error(), + ) + } + return + } + + // Determine the member type, which can be any of group or user. + // The resource validator makes sure that exactly one of these is configured. + var memberType, memberName string + if !data.GroupName.IsNull() { + memberType = "GROUP" + memberName = data.GroupName.ValueString() + } else { + memberType = "USER" + memberName = data.UserName.ValueString() + } + + // Check for organization role membership existence. + var membershipFound bool + for _, roleMembership := range roleMemberships.MemberMappings { + if *roleMembership.RoleId == data.RoleId.ValueString() { + for _, member := range roleMembership.Members { + if *member.Type == memberType && *member.UserOrGroupName == memberName && *member.OwnerType == "ORGANIZATION" && *member.OwnerId == data.OrganizationId.ValueString() { + membershipFound = true + break + } + } + } + } + + if !membershipFound { + resp.State.RemoveResource(ctx) + return + } + + // Set refreshed state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *organizationRoleMembershipResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data organizationRoleMembershipModelResource + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Make Delete API Call + ctx = context.WithValue( + ctx, + sonatypeiq.ContextBasicAuth, + r.auth, + ) + + // Determine the member type, which can be any of group or user. + // The resource validator makes sure that exactly one of these is configured. + var memberType, memberName string + if !data.GroupName.IsNull() { + memberType = "group" + memberName = data.GroupName.ValueString() + } else { + memberType = "user" + memberName = data.UserName.ValueString() + } + + apiRequest := r.client.RoleMembershipsAPI.RevokeRoleMembershipApplicationOrOrganization(ctx, "organization", data.OrganizationId.ValueString(), data.RoleId.ValueString(), memberType, memberName) + apiResponse, err := r.client.RoleMembershipsAPI.RevokeRoleMembershipApplicationOrOrganizationExecute(apiRequest) + if err != nil { + error_body, _ := io.ReadAll(apiResponse.Body) + resp.Diagnostics.AddError( + "Error deleting organization role membership", + "Could not delete organization role membership, unexpected error: "+apiResponse.Status+": "+string(error_body), + ) + return + } +} diff --git a/internal/provider/organization_role_membership_resource_test.go b/internal/provider/organization_role_membership_resource_test.go new file mode 100755 index 0000000..5c10fc0 --- /dev/null +++ b/internal/provider/organization_role_membership_resource_test.go @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019-present Sonatype, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccOrganizationRoleMembershipResource(t *testing.T) { + + userName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: fmt.Sprintf(providerConfig+` + data "sonatypeiq_organization" "sandbox" { + name = "Sandbox Organization" + } + + data "sonatypeiq_role" "developer" { + name = "Developer" + } + + resource "sonatypeiq_user" "user" { + username = "%s" + password = "randomthing" + first_name = "Example" + last_name = "User" + email = "example@user.tld" + } + + resource "sonatypeiq_organization_role_membership" "test" { + role_id = data.sonatypeiq_role.developer.id + organization_id = data.sonatypeiq_organization.sandbox.id + user_name = sonatypeiq_user.user.username + } + + `, userName), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify application role membership + resource.TestCheckResourceAttrSet("sonatypeiq_organization_role_membership.test", "id"), + resource.TestCheckResourceAttr("sonatypeiq_organization_role_membership.test", "user_name", userName), + ), + }, + }, + }) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 2a778cf..215786f 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -168,6 +168,8 @@ func (p *SonatypeIqProvider) Resources(ctx context.Context) []func() resource.Re NewOrganizationResource, NewSystemConfigResource, NewUserResource, + NewApplicationRoleMembershipResource, + NewOrganizationRoleMembershipResource, } }