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-839 add service account resource #430

Merged
merged 16 commits into from
Dec 12, 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,ServiceAccountsAPI
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 @@ -52,6 +52,7 @@ func Provider(version string) *schema.Provider {
"castai_commitments": resourceCommitments(),
"castai_organization_members": resourceOrganizationMembers(),
"castai_sso_connection": resourceSSOConnection(),
"castai_service_account": resourceServiceAccount(),
"castai_workload_scaling_policy": resourceWorkloadScalingPolicy(),
},

Expand Down
250 changes: 250 additions & 0 deletions castai/resource_service_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
package castai

import (
"context"
"net/http"
"time"

"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

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

const (
FieldServiceAccountOrganizationID = "organization_id"
FieldServiceAccountName = "name"
FieldServiceAccountID = "service_account_id"
FieldServiceAccountDescription = "description"
FieldServiceAccountEmail = "email"

FieldServiceAccountAuthor = "author"
FieldServiceAccountAuthorID = "id"
FieldServiceAccountAuthorEmail = "email"
FieldServiceAccountAuthorKind = "kind"
)

func resourceServiceAccount() *schema.Resource {
return &schema.Resource{
CreateContext: resourceServiceAccountCreate,
ReadContext: resourceServiceAccountRead,
UpdateContext: resourceServiceAccountUpdate,
DeleteContext: resourceServiceAccountDelete,

Description: "Service Account resource allows managing CAST AI service accounts.",
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(3 * time.Minute),
Update: schema.DefaultTimeout(3 * time.Minute),
Delete: schema.DefaultTimeout(3 * time.Minute),
},

Schema: map[string]*schema.Schema{
FieldServiceAccountOrganizationID: {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "ID of the organization.",
},
FieldServiceAccountName: {
Type: schema.TypeString,
Required: true,
Description: "Name of the service account.",
},
FieldServiceAccountDescription: {
Type: schema.TypeString,
Optional: true,
Description: "Description of the service account.",
},
FieldServiceAccountEmail: {
Type: schema.TypeString,
Computed: true,
Description: "Email of the service account.",
},
FieldServiceAccountAuthor: {
Type: schema.TypeList,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
FieldServiceAccountAuthorID: {Type: schema.TypeString, Computed: true},
FieldServiceAccountAuthorEmail: {Type: schema.TypeString, Computed: true},
FieldServiceAccountAuthorKind: {Type: schema.TypeString, Computed: true},
},
},
Computed: true,
Description: "Author of the service account.",
},
},
}
}

func resourceServiceAccountRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*ProviderConfig).api

if data.Id() == "" {
return diag.Errorf("service account ID is not set")
}

organizationID := data.Get(FieldServiceAccountOrganizationID).(string)

tflog.Info(ctx, "reading service account", map[string]interface{}{
"resource_id": data.Id(),
"organization_id": organizationID,
})

resp, err := client.ServiceAccountsAPIGetServiceAccountWithResponse(ctx, organizationID, data.Id())
if resp.StatusCode() == http.StatusNotFound {
tflog.Warn(ctx, "resource is not found, removing from state", map[string]interface{}{
"resource_id": data.Id(),
"organization_id": organizationID,
})
data.SetId("") // Mark resource as deleted
return nil
}
if err := sdk.CheckOKResponse(resp, err); err != nil {
return diag.Errorf("getting service account: %v", err)
}

tflog.Info(ctx, "found service account", map[string]interface{}{
"resource_id": data.Id(),
"organization_id": organizationID,
})
serviceAccount := resp.JSON200

if err := data.Set(FieldServiceAccountName, serviceAccount.ServiceAccount.Name); err != nil {
return diag.Errorf("setting service account name: %v", err)
}

if err := data.Set(FieldServiceAccountEmail, serviceAccount.ServiceAccount.Email); err != nil {
return diag.Errorf("setting service account email: %v", err)
}

if err := data.Set(FieldServiceAccountDescription, serviceAccount.ServiceAccount.Description); err != nil {
return diag.Errorf("setting service account description: %v", err)
}

authorData := flattenServiceAccountAuthor(serviceAccount.ServiceAccount.Author)
if err := data.Set(FieldServiceAccountAuthor, authorData); err != nil {
return diag.Errorf("setting service account author: %v", err)
}
return nil
}

func resourceServiceAccountCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*ProviderConfig).api

organizationID := data.Get(FieldServiceAccountOrganizationID).(string)
radekska marked this conversation as resolved.
Show resolved Hide resolved
name := data.Get(FieldServiceAccountName).(string)
description := data.Get(FieldServiceAccountDescription).(string)

tflog.Info(ctx, "creating service account", map[string]interface{}{
"name": name,
"description": description,
"organization_id": organizationID,
})

resp, err := client.ServiceAccountsAPICreateServiceAccountWithResponse(ctx, organizationID, sdk.CastaiServiceaccountsV1beta1CreateServiceAccountRequestServiceAccount{
Name: name,
Description: &description,
},
)

if err := sdk.CheckResponseCreated(resp, err); err != nil {
return diag.Errorf("creating service account: %v", err)
}

tflog.Info(ctx, "created service account", map[string]interface{}{
"resource_id": *resp.JSON201.Id,
"organization_id": organizationID,
})
data.SetId(*resp.JSON201.Id)

return resourceServiceAccountRead(ctx, data, meta)
}

func resourceServiceAccountUpdate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*ProviderConfig).api

serviceAccountID := data.Id()
organizationID := data.Get(FieldServiceAccountOrganizationID).(string)
name := data.Get(FieldServiceAccountName).(string)
description := data.Get(FieldServiceAccountDescription).(string)

tflog.Info(ctx, "updating service account", map[string]interface{}{
"resource_id": serviceAccountID,
"name": name,
"description": description,
"organization_id": organizationID,
})

resp, err := client.ServiceAccountsAPIUpdateServiceAccountWithResponse(
ctx,
organizationID,
serviceAccountID,
sdk.ServiceAccountsAPIUpdateServiceAccountRequest{
ServiceAccount: sdk.CastaiServiceaccountsV1beta1UpdateServiceAccountRequestServiceAccount{
Name: name,
Description: &description,
},
},
)

if err := sdk.CheckOKResponse(resp, err); err != nil {
return diag.Errorf("updating service account: %v", err)
}

tflog.Info(ctx, "created service account", map[string]interface{}{
"resource_id": serviceAccountID,
"organization_id": organizationID,
"name": name,
"description": description,
})

return resourceServiceAccountRead(ctx, data, meta)
}

func resourceServiceAccountDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*ProviderConfig).api
organizationID := data.Get(FieldServiceAccountOrganizationID).(string)
serviceAccountID := data.Id()

tflog.Info(ctx, "deleting service account", map[string]interface{}{
"resource_id": serviceAccountID,
"organization_id": organizationID,
})

resp, err := client.ServiceAccountsAPIDeleteServiceAccount(ctx, organizationID, serviceAccountID)
if err != nil {
return diag.Errorf("deleting service account: %v", err)
}
if resp.StatusCode != http.StatusNoContent {
return diag.Errorf("deleteting service account: expected status: [204], received status: [%d]", resp.StatusCode)
}

tflog.Info(ctx, "deleted service account", map[string]interface{}{
"resource_id": serviceAccountID,
"organization_id": organizationID,
})

return nil
}

func flattenServiceAccountAuthor(author *sdk.CastaiServiceaccountsV1beta1ServiceAccountAuthor) []map[string]interface{} {
if author == nil {
return []map[string]interface{}{}
}

return []map[string]interface{}{
{
FieldServiceAccountAuthorID: stringValue(author.Id),
FieldServiceAccountAuthorEmail: stringValue(author.Email),
FieldServiceAccountAuthorKind: stringValue(author.Kind),
},
}
}

func stringValue(value *string) string {
if value == nil {
return ""
}
return *value
}
Loading
Loading