Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIRE-838 - Support for managing organization groups #427

Merged
merged 10 commits into from
Dec 9, 2024
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
SHELL := /bin/bash

export API_TAGS ?= ExternalClusterAPI,PoliciesAPI,NodeConfigurationAPI,NodeTemplatesAPI,AuthTokenAPI,ScheduledRebalancingAPI,InventoryAPI,UsersAPI,OperationsAPI,EvictorAPI,SSOAPI,CommitmentsAPI,WorkloadOptimizationAPI
export API_TAGS ?= ExternalClusterAPI,PoliciesAPI,NodeConfigurationAPI,NodeTemplatesAPI,AuthTokenAPI,ScheduledRebalancingAPI,InventoryAPI,UsersAPI,OperationsAPI,EvictorAPI,SSOAPI,CommitmentsAPI,WorkloadOptimizationAPI,RbacServiceAPI
export SWAGGER_LOCATION ?= https://api.cast.ai/v1/spec/openapi.json

default: build
Expand Down
1 change: 1 addition & 0 deletions castai/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func Provider(version string) *schema.Provider {
"castai_organization_members": resourceOrganizationMembers(),
"castai_sso_connection": resourceSSOConnection(),
"castai_workload_scaling_policy": resourceWorkloadScalingPolicy(),
"castai_organization_group": resourceOrganizationGroup(),
},

DataSourcesMap: map[string]*schema.Resource{
Expand Down
229 changes: 229 additions & 0 deletions castai/resource_organization_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
package castai

import (
"context"
"errors"
"fmt"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/samber/lo"

"github.com/castai/terraform-provider-castai/castai/sdk"
)

const (
FieldOrganizationGroupOrganizationID = "organization_id"
FieldOrganizationGroupName = "name"
FieldOrganizationGroupDescription = "description"
FieldOrganizationGroupMembers = "members"
FieldOrganizationGroupMember = "member"
FieldOrganizationGroupMemberID = "id"
FieldOrganizationGroupMemberEmail = "email"
oskarwojciski marked this conversation as resolved.
Show resolved Hide resolved
)

func resourceOrganizationGroup() *schema.Resource {
return &schema.Resource{
ReadContext: resourceOrganizationGroupRead,
CreateContext: resourceOrganizationGroupCreate,
UpdateContext: resourceOrganizationGroupUpdate,
DeleteContext: resourceOrganizationGroupDelete,
Description: "CAST AI organization group resource to manage organization groups",
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(2 * time.Minute),
Update: schema.DefaultTimeout(2 * time.Minute),
Delete: schema.DefaultTimeout(2 * time.Minute),
},
Schema: map[string]*schema.Schema{
FieldOrganizationGroupOrganizationID: {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "CAST AI organization ID.",
},
FieldOrganizationGroupName: {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Name of the group.",
},
FieldOrganizationGroupDescription: {
Type: schema.TypeString,
Optional: true,
Description: "Description of the group.",
},
FieldOrganizationGroupMembers: {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
FieldOrganizationGroupMember: {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
FieldOrganizationGroupMemberID: {
Type: schema.TypeString,
Required: true,
},
FieldOrganizationGroupMemberEmail: {
Type: schema.TypeString,
Required: true,
},
},
},
},
},
},
},
},
}
}

func resourceOrganizationGroupCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
organizationID := data.Get(FieldOrganizationGroupOrganizationID).(string)

client := meta.(*ProviderConfig).api

members := convertMembersToSDK(data)

resp, err := client.RbacServiceAPICreateGroupWithResponse(ctx, organizationID, sdk.RbacServiceAPICreateGroupJSONRequestBody{
Definition: sdk.CastaiRbacV1beta1CreateGroupRequestGroupDefinition{
Members: &members,
},
Description: lo.ToPtr(data.Get(FieldOrganizationGroupDescription).(string)),
Name: data.Get(FieldOrganizationName).(string),
})

if err := sdk.CheckOKResponse(resp, err); err != nil {
return diag.FromErr(fmt.Errorf("create group: %w", err))
}

data.SetId(*resp.JSON200.Id)

return resourceOrganizationGroupRead(ctx, data, meta)
}

func resourceOrganizationGroupRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
organizationID := data.Get(FieldOrganizationGroupOrganizationID).(string)
if organizationID == "" {
var err error
organizationID, err = getDefaultOrganizationId(ctx, meta)
if err != nil {
return diag.FromErr(fmt.Errorf("getting default organization: %w", err))
}
}
groupID := data.Id()

client := meta.(*ProviderConfig).api

group, err := getGroup(client, ctx, organizationID, groupID)
if err != nil {
return diag.FromErr(fmt.Errorf("getting group for read: %w", err))
}

err = assignGroupData(group, data)
if err != nil {
oskarwojciski marked this conversation as resolved.
Show resolved Hide resolved
return diag.FromErr(fmt.Errorf("assigning group data for read: %w", err))
}

return nil
}

func resourceOrganizationGroupUpdate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
organizationID := data.Get(FieldOrganizationGroupOrganizationID).(string)
groupID := data.Id()

client := meta.(*ProviderConfig).api

members := convertMembersToSDK(data)

resp, err := client.RbacServiceAPIUpdateGroupWithResponse(ctx, organizationID, groupID, sdk.RbacServiceAPIUpdateGroupJSONRequestBody{
Definition: sdk.CastaiRbacV1beta1UpdateGroupRequestGroupDefinition{
Members: members,
},
Description: lo.ToPtr(data.Get(FieldOrganizationGroupDescription).(string)),
Name: data.Get(FieldOrganizationName).(string),
})
if err := sdk.CheckOKResponse(resp, err); err != nil {
return diag.FromErr(fmt.Errorf("update group: %w", err))
}

return resourceOrganizationGroupRead(ctx, data, meta)
}

func resourceOrganizationGroupDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
organizationID := data.Get(FieldOrganizationGroupOrganizationID).(string)
groupID := data.Id()

client := meta.(*ProviderConfig).api

resp, err := client.RbacServiceAPIDeleteGroupWithResponse(ctx, organizationID, groupID)
if err := sdk.CheckOKResponse(resp, err); err != nil {
return diag.FromErr(fmt.Errorf("destroy group: %w", err))
}

return nil
}

func getGroup(client *sdk.ClientWithResponses, ctx context.Context, organizationID, groupID string) (*sdk.CastaiRbacV1beta1Group, error) {
groupsResp, err := client.RbacServiceAPIGetGroupWithResponse(ctx, organizationID, groupID)
if err := sdk.CheckOKResponse(groupsResp, err); err != nil {
return nil, fmt.Errorf("retrieving group: %w", err)
}
if groupsResp.JSON200 == nil {
return nil, errors.New("group not found")
}
return groupsResp.JSON200, nil
}

func assignGroupData(group *sdk.CastaiRbacV1beta1Group, data *schema.ResourceData) error {
if err := data.Set(FieldOrganizationGroupOrganizationID, group.OrganizationId); err != nil {
return fmt.Errorf("setting organization_id: %w", err)
}
if err := data.Set(FieldOrganizationGroupDescription, group.Description); err != nil {
return fmt.Errorf("setting description: %w", err)
}
if err := data.Set(FieldOrganizationGroupName, group.Name); err != nil {
return fmt.Errorf("setting group name: %w", err)
}

if group.Definition.Members != nil {
var members []map[string]string
for _, member := range *group.Definition.Members {
members = append(members, map[string]string{
FieldOrganizationGroupMemberID: member.Id,
FieldOrganizationGroupMemberEmail: member.Email,
})
}
err := data.Set(FieldOrganizationGroupMembers, []any{
map[string]any{
FieldOrganizationGroupMember: members,
},
})
if err != nil {
return fmt.Errorf("parsing group members: %w", err)
}
}

return nil
}

func convertMembersToSDK(data *schema.ResourceData) []sdk.CastaiRbacV1beta1Member {
var members []sdk.CastaiRbacV1beta1Member

for _, dataMembersDef := range data.Get(FieldOrganizationGroupMembers).([]any) {
for _, dataMember := range dataMembersDef.(map[string]any)[FieldOrganizationGroupMember].([]any) {
members = append(members, sdk.CastaiRbacV1beta1Member{
Email: dataMember.(map[string]any)[FieldOrganizationGroupMemberEmail].(string),
Id: dataMember.(map[string]any)[FieldOrganizationGroupMemberID].(string),
})
}
}

return members
}
Loading
Loading