Skip to content

Commit

Permalink
feat(org): add support for org group usres (#1489)
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-savciuc authored Dec 20, 2023
1 parent e687aa8 commit 95be3a9
Show file tree
Hide file tree
Showing 9 changed files with 386 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ nav_order: 1
component, and is a combination of the `host` and `port` fields
- Add `external_postgresql` and `external_google_cloud_bigquery` service integration endpoints
- Do not return error on `aiven_account_team_member` deletion if the member does not exist
- Deprecating `aiven_organization_user` resource and update data source logic that will be used instead of the corresponding resource
- Add support for the `aiven_organization_user_group_member` resource, allowing the association of groups with the users. Please note that this resource is in the beta stage, and to use it, you would need to set the environment variable PROVIDER_AIVEN_ENABLE_BETA to a non-zero value.

## [4.9.4] - 2023-12-13

Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ lint-test: $(TERRAFMT)


lint-docs: $(TFPLUGINDOCS)
$(TFPLUGINDOCS) validate
PROVIDER_AIVEN_ENABLE_BETA=true $(TFPLUGINDOCS) validate

#################################################
# Format
Expand Down Expand Up @@ -172,7 +172,7 @@ gen-go:


docs: $(TFPLUGINDOCS)
$(TFPLUGINDOCS) generate
PROVIDER_AIVEN_ENABLE_BETA=true $(TFPLUGINDOCS) generate

#################################################
# CI
Expand Down
1 change: 1 addition & 0 deletions docs/data-sources/organization_user_group.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ The Organization User Groupe data source provides information about the existing

- `create_time` (String) Time of creation
- `description` (String) The organization user group description. This property cannot be changed, doing so forces recreation of the resource.
- `group_id` (String) The unique organization user group ID
- `id` (String) The ID of this resource.
- `update_time` (String) Time of last update
1 change: 1 addition & 0 deletions docs/resources/organization_user_group.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ The Organization User Group resource allows the creation and management of an Ai
### Read-Only

- `create_time` (String) Time of creation
- `group_id` (String) The unique organization user group ID
- `id` (String) The ID of this resource.
- `update_time` (String) Time of last update

Expand Down
40 changes: 40 additions & 0 deletions docs/resources/organization_user_group_member.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "aiven_organization_user_group_member Resource - terraform-provider-aiven"
subcategory: ""
description: |-
Creates and manages an organization user group members in Aiven. Please no that this resource is in beta and may change without notice. To use it please use the beta environment variable PROVIDERAIVENENABLE_BETA.
---

# aiven_organization_user_group_member (Resource)

Creates and manages an organization user group members in Aiven. Please no that this resource is in beta and may change without notice. To use it please use the beta environment variable PROVIDER_AIVEN_ENABLE_BETA.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `group_id` (String) Identifier of the organization user group.
- `organization_id` (String) Identifier of the organization.
- `user_id` (String) Identifier of the organization user group member.

### Optional

- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))

### Read-Only

- `last_activity_time` (String) Last activity time of the user group member.

<a id="nestedblock--timeouts"></a>
### Nested Schema for `timeouts`

Optional:

- `create` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).
- `delete` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Setting a timeout for a Delete operation is only applicable if changes are saved into state before the destroy operation occurs.
- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Read operations occur during any refresh or planning operation when refresh is enabled.
- `update` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).
12 changes: 11 additions & 1 deletion internal/plugin/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,19 @@ func (p *AivenProvider) Configure(

// Resources returns the resources supported by this provider.
func (p *AivenProvider) Resources(context.Context) []func() resource.Resource {
return []func() resource.Resource{
isBeta := os.Getenv("PROVIDER_AIVEN_ENABLE_BETA") != ""

// List of resources that are currently available in the provider.
resources := []func() resource.Resource{
organization.NewOrganizationResource,
}

// Add to a list of resources that are currently in beta.
if isBeta {
resources = append(resources, organization.NewOrganizationUserGroupMembersResource)
}

return resources
}

// DataSources returns the data sources supported by this provider.
Expand Down
268 changes: 268 additions & 0 deletions internal/plugin/service/organization/organization_user_group_member.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
package organization

import (
"context"
"fmt"

"github.com/aiven/aiven-go-client/v2"
"github.com/aiven/terraform-provider-aiven/internal/plugin/util"
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)

var (
_ resource.Resource = &organizationUserGroupMembersResource{}
_ resource.ResourceWithConfigure = &organizationUserGroupMembersResource{}
_ resource.ResourceWithImportState = &organizationUserGroupMembersResource{}
)

// NewOrganizationUserGroupMembersResource is a constructor for the organization resource.
func NewOrganizationUserGroupMembersResource() resource.Resource {
return &organizationUserGroupMembersResource{}
}

// organizationUserGroupMembersResource is the organization resource implementation.
type organizationUserGroupMembersResource struct {
// client is the instance of the Aiven client to use.
client *aiven.Client

// typeName is the name of the resource type.
typeName string
}

// organizationUserGroupMembersResourceModel is the model for the organization resource.
type organizationUserGroupMembersResourceModel struct {
// ID is the identifier of the organization.
OrganizationID types.String `tfsdk:"organization_id"`

// ID is the identifier of the organization user group.
OrganizationGroupID types.String `tfsdk:"group_id"`

// ID is the identifier of the organization user group member.
OrganizationUserID types.String `tfsdk:"user_id"`

// Last activity time of the user group member.
LastActivityTime types.String `tfsdk:"last_activity_time"`

// Timeouts is the configuration for resource-specific timeouts.
Timeouts timeouts.Value `tfsdk:"timeouts"`
}

// Metadata returns the metadata for the organization resource.
func (r *organizationUserGroupMembersResource) Metadata(
_ context.Context,
req resource.MetadataRequest,
resp *resource.MetadataResponse,
) {
resp.TypeName = req.ProviderTypeName + "_organization_user_group_member"

r.typeName = resp.TypeName
}

func (r *organizationUserGroupMembersResource) TypeName() string {
return r.typeName
}

// Schema returns the schema for the organization resource.
func (r *organizationUserGroupMembersResource) Schema(
ctx context.Context,
_ resource.SchemaRequest,
resp *resource.SchemaResponse,
) {
resp.Schema = util.GeneralizeSchema(ctx, schema.Schema{
Description: "Creates and manages an organization user group members in Aiven. " +
"Please no that this resource is " + " in beta and may change without notice. " +
"To use it please use the beta environment variable PROVIDER_AIVEN_ENABLE_BETA.",
Attributes: map[string]schema.Attribute{
"organization_id": schema.StringAttribute{
Description: "Identifier of the organization.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"user_id": schema.StringAttribute{
Description: "Identifier of the organization user group member.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"group_id": schema.StringAttribute{
Description: "Identifier of the organization user group.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"last_activity_time": schema.StringAttribute{
Description: "Last activity time of the user group member.",
Computed: true,
},
},
})
}

// Configure configures the organization resource.
func (r *organizationUserGroupMembersResource) Configure(
_ context.Context,
req resource.ConfigureRequest,
resp *resource.ConfigureResponse,
) {
if req.ProviderData == nil {
return
}

client, ok := req.ProviderData.(*aiven.Client)
if !ok {
resp.Diagnostics = util.DiagErrorUnexpectedProviderDataType(resp.Diagnostics, req.ProviderData)

return
}

r.client = client
}

// TimeoutSchema returns the schema for resource-specific timeouts.
func (r *organizationUserGroupMembersResource) fillModel(
ctx context.Context,
model *organizationUserGroupMembersResourceModel,
) error {
list, err := r.client.OrganizationUserGroupMembers.List(
ctx,
model.OrganizationID.ValueString(),
model.OrganizationGroupID.ValueString())
if err != nil {
return err
}

if len(list.Members) == 0 {
return nil
}

member := list.Members[0]
model.LastActivityTime = types.StringValue(member.LastActivityTime.String())

return nil
}

// Create creates an organization resource.
func (r *organizationUserGroupMembersResource) Create(
ctx context.Context,
req resource.CreateRequest,
resp *resource.CreateResponse,
) {
var plan organizationUserGroupMembersResourceModel

if !util.PlanStateToModel(ctx, &req.Plan, &plan, &resp.Diagnostics) {
return
}

err := r.client.OrganizationUserGroupMembers.Modify(
ctx,
plan.OrganizationID.ValueString(),
plan.OrganizationGroupID.ValueString(),
aiven.OrganizationUserGroupMemberRequest{
Operation: "add_members",
MemberIDs: []string{
plan.OrganizationUserID.ValueString(),
}})
if err != nil {
resp.Diagnostics = util.DiagErrorCreatingResource(resp.Diagnostics, r, err)

return
}

err = r.fillModel(ctx, &plan)
if err != nil {
resp.Diagnostics = util.DiagErrorCreatingResource(resp.Diagnostics, r, err)

return
}

if !util.ModelToPlanState(ctx, plan, &resp.State, &resp.Diagnostics) {
return
}
}

// Delete deletes an organization resource.
func (r *organizationUserGroupMembersResource) Read(
ctx context.Context,
req resource.ReadRequest,
resp *resource.ReadResponse,
) {
var state organizationUserGroupMembersResourceModel

if !util.PlanStateToModel(ctx, &req.State, &state, &resp.Diagnostics) {
return
}

err := r.fillModel(ctx, &state)
if err != nil {
resp.Diagnostics = util.DiagErrorReadingResource(resp.Diagnostics, r, err)

return
}

if !util.ModelToPlanState(ctx, state, &resp.State, &resp.Diagnostics) {
return
}
}

// Delete deletes an organization resource.
func (r *organizationUserGroupMembersResource) Delete(
ctx context.Context,
req resource.DeleteRequest,
resp *resource.DeleteResponse,
) {
var plan organizationUserGroupMembersResourceModel

if !util.PlanStateToModel(ctx, &req.State, &plan, &resp.Diagnostics) {
return
}

err := r.client.OrganizationUserGroupMembers.Modify(
ctx,
plan.OrganizationID.ValueString(),
plan.OrganizationGroupID.ValueString(),
aiven.OrganizationUserGroupMemberRequest{
Operation: "remove_members",
MemberIDs: []string{
plan.OrganizationGroupID.ValueString(),
}})
if err != nil {
resp.Diagnostics = util.DiagErrorDeletingResource(resp.Diagnostics, r, err)

return
}
}

// Update updates an organization resource.
func (r *organizationUserGroupMembersResource) Update(
_ context.Context,
_ resource.UpdateRequest,
resp *resource.UpdateResponse,
) {
util.DiagErrorUpdatingResource(
resp.Diagnostics,
r,
fmt.Errorf("cannot update %s resource", r.TypeName()),
)
}

// ImportStatePassthroughID is a helper function to set the import
func (r *organizationUserGroupMembersResource) ImportState(
_ context.Context,
_ resource.ImportStateRequest,
resp *resource.ImportStateResponse,
) {
util.DiagErrorUpdatingResource(
resp.Diagnostics,
r,
fmt.Errorf("cannot import %s resource", r.TypeName()),
)
}
Loading

0 comments on commit 95be3a9

Please sign in to comment.