diff --git a/castai/provider.go b/castai/provider.go
index 66933cf7..9fe3c29e 100644
--- a/castai/provider.go
+++ b/castai/provider.go
@@ -53,6 +53,7 @@ func Provider(version string) *schema.Provider {
"castai_organization_members": resourceOrganizationMembers(),
"castai_sso_connection": resourceSSOConnection(),
"castai_service_account": resourceServiceAccount(),
+ "castai_service_account_key": resourceServiceAccountKey(),
"castai_workload_scaling_policy": resourceWorkloadScalingPolicy(),
"castai_organization_group": resourceOrganizationGroup(),
"castai_role_bindings": resourceRoleBindings(),
diff --git a/castai/resource_service_account.go b/castai/resource_service_account.go
index 6928d6bb..b248ecb6 100644
--- a/castai/resource_service_account.go
+++ b/castai/resource_service_account.go
@@ -9,6 +9,7 @@ import (
"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/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/castai/terraform-provider-castai/castai/sdk"
)
@@ -24,6 +25,7 @@ const (
FieldServiceAccountAuthorID = "id"
FieldServiceAccountAuthorEmail = "email"
FieldServiceAccountAuthorKind = "kind"
+
)
func resourceServiceAccount() *schema.Resource {
@@ -33,24 +35,27 @@ func resourceServiceAccount() *schema.Resource {
UpdateContext: resourceServiceAccountUpdate,
DeleteContext: resourceServiceAccountDelete,
- Description: "Service Account resource allows managing CAST AI service accounts.",
+ Description: "Service account resource allows managing CAST AI service accounts.",
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(3 * time.Minute),
+ Read: 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.",
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "ID of the organization.",
+ ValidateDiagFunc: validation.ToDiagFunc(validation.IsUUID),
},
FieldServiceAccountName: {
- Type: schema.TypeString,
- Required: true,
- Description: "Name of the service account.",
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Name of the service account.",
+ ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace),
},
FieldServiceAccountDescription: {
Type: schema.TypeString,
@@ -96,6 +101,10 @@ func resourceServiceAccountRead(ctx context.Context, data *schema.ResourceData,
})
resp, err := client.ServiceAccountsAPIGetServiceAccountWithResponse(ctx, organizationID, data.Id())
+ if err != nil {
+ return diag.Errorf("getting service account: %v", err)
+ }
+
if resp.StatusCode() == http.StatusNotFound {
tflog.Warn(ctx, "resource is not found, removing from state", map[string]interface{}{
"resource_id": data.Id(),
@@ -228,18 +237,17 @@ func resourceServiceAccountDelete(ctx context.Context, data *schema.ResourceData
})
resp, err := client.ServiceAccountsAPIDeleteServiceAccount(ctx, organizationID, serviceAccountID)
- if err != nil {
+ if err := sdk.CheckRawResponseNoContent(resp, err); 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,
})
+ data.SetId("")
+
return nil
}
diff --git a/castai/resource_service_account_key.go b/castai/resource_service_account_key.go
new file mode 100644
index 00000000..037508b9
--- /dev/null
+++ b/castai/resource_service_account_key.go
@@ -0,0 +1,303 @@
+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/hashicorp/terraform-plugin-sdk/v2/helper/validation"
+
+ "github.com/castai/terraform-provider-castai/castai/sdk"
+)
+
+const (
+ FieldServiceAccountKeyID = "id"
+ FieldServiceAccountKeyOrganizationID = "organization_id"
+ FieldServiceAccountKeyServiceAccountID = "service_account_id"
+ FieldServiceAccountKeyName = "name"
+ FieldServiceAccountKeyPrefix = "prefix"
+ FieldServiceAccountKeyLastUsedAt = "last_used_at"
+ FieldServiceAccountKeyExpiresAt = "expires_at"
+ FieldServiceAccountKeyActive = "active"
+ FieldServiceAccountKeyToken = "token"
+)
+
+func resourceServiceAccountKey() *schema.Resource {
+ return &schema.Resource{
+ Description: "Service account key resource allows managing CAST AI service account keys.",
+ CreateContext: resourceServiceAccountKeyCreate,
+ ReadContext: resourceServiceAccountKeyRead,
+ UpdateContext: resourceServiceAccountKeyUpdate,
+ DeleteContext: resourceServiceAccountKeyDelete,
+ Timeouts: &schema.ResourceTimeout{
+ Create: schema.DefaultTimeout(3 * time.Minute),
+ Read: schema.DefaultTimeout(3 * time.Minute),
+ Update: schema.DefaultTimeout(3 * time.Minute),
+ Delete: schema.DefaultTimeout(3 * time.Minute),
+ },
+ Schema: map[string]*schema.Schema{
+ FieldServiceAccountKeyOrganizationID: {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "ID of the organization.",
+ ValidateDiagFunc: validation.ToDiagFunc(validation.IsUUID),
+ },
+ FieldServiceAccountKeyServiceAccountID: {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "ID of the service account.",
+ ValidateDiagFunc: validation.ToDiagFunc(validation.IsUUID),
+ },
+ FieldServiceAccountKeyName: {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "Name of the service account key.",
+ ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace),
+ },
+ FieldServiceAccountKeyPrefix: {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "Prefix of the service account key.",
+ },
+ FieldServiceAccountKeyLastUsedAt: {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "Last time the service account key was used.",
+ },
+ FieldServiceAccountKeyExpiresAt: {
+ Type: schema.TypeString,
+ Optional: true,
+ Default: "",
+ ForceNew: true,
+ Description: "The expiration time of the service account key in RFC3339 format. Defaults to an empty string.",
+ ValidateDiagFunc: validateRFC3339TimeOrEmpty,
+ },
+ FieldServiceAccountKeyActive: {
+ Type: schema.TypeBool,
+ Optional: true,
+ Default: true,
+ Description: "Whether the service account key is active. Defaults to true.",
+ },
+ FieldServiceAccountKeyToken: {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The token of the service account key used for authentication.",
+ },
+ },
+ }
+}
+
+func resourceServiceAccountKeyRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ client := meta.(*ProviderConfig).api
+
+ if data.Id() == "" {
+ return diag.Errorf("service account key ID is not set")
+ }
+
+ organizationID, err := getOrganizationID(ctx, data, meta)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ serviceAccountID := data.Get(FieldServiceAccountKeyServiceAccountID).(string)
+ if serviceAccountID == "" {
+ return diag.Errorf("service account ID is not set")
+ }
+ serviceAccountKeyID := data.Id()
+
+ logKeys := map[string]interface{}{
+ "resource_id": serviceAccountKeyID,
+ "organization_id": organizationID,
+ "service_account_id": serviceAccountID,
+ }
+
+ tflog.Info(ctx, "reading service account key", logKeys)
+
+ resp, err := client.ServiceAccountsAPIGetServiceAccountKeyWithResponse(ctx, organizationID, serviceAccountID, serviceAccountKeyID)
+ if err != nil {
+ return diag.Errorf("reading service account key: %v", err)
+ }
+ if resp.StatusCode() == http.StatusNotFound {
+ tflog.Warn(ctx, "resource is not found, removing from state", logKeys)
+ data.SetId("")
+ return nil
+ }
+
+ if err := sdk.CheckOKResponse(resp, err); err != nil {
+ return diag.Errorf("reading service account key: %v", err)
+ }
+
+ tflog.Info(ctx, "found service account key", logKeys)
+
+ serviceAccountKey := resp.JSON200
+
+ if err := data.Set(FieldServiceAccountKeyOrganizationID, organizationID); err != nil {
+ return diag.Errorf("setting field %s: %v", FieldServiceAccountKeyOrganizationID, err)
+ }
+
+ if err := data.Set(FieldServiceAccountKeyServiceAccountID, serviceAccountID); err != nil {
+ return diag.Errorf("setting field %s: %v", FieldServiceAccountKeyServiceAccountID, err)
+ }
+
+ if err := data.Set(FieldServiceAccountKeyName, serviceAccountKey.Key.Name); err != nil {
+ return diag.Errorf("setting field %s: %v", FieldServiceAccountKeyName, err)
+ }
+
+ if err := data.Set(FieldServiceAccountKeyPrefix, serviceAccountKey.Key.Prefix); err != nil {
+ return diag.Errorf("setting field %s: %v", FieldServiceAccountKeyPrefix, err)
+ }
+
+ if serviceAccountKey.Key.LastUsedAt != nil {
+ if err := data.Set(FieldServiceAccountKeyLastUsedAt, serviceAccountKey.Key.LastUsedAt.Format(time.RFC3339)); err != nil {
+ return diag.Errorf("setting field %s: %v", FieldServiceAccountKeyLastUsedAt, err)
+ }
+ }
+
+ if serviceAccountKey.Key.ExpiresAt != nil {
+ if err := data.Set(FieldServiceAccountKeyExpiresAt, serviceAccountKey.Key.ExpiresAt.Format(time.RFC3339)); err != nil {
+ return diag.Errorf("setting field %s: %v", FieldServiceAccountKeyExpiresAt, err)
+ }
+ }
+
+ if err := data.Set(FieldServiceAccountKeyActive, serviceAccountKey.Key.Active); err != nil {
+ return diag.Errorf("setting field %s: %v", FieldServiceAccountKeyActive, err)
+ }
+ return nil
+}
+
+func resourceServiceAccountKeyCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ var expiresAtTime *time.Time
+
+ client := meta.(*ProviderConfig).api
+
+ organizationID, err := getOrganizationID(ctx, data, meta)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ serviceAccountID := data.Get(FieldServiceAccountKeyServiceAccountID).(string)
+ name := data.Get(FieldServiceAccountKeyName).(string)
+ expiresAt := data.Get(FieldServiceAccountKeyExpiresAt).(string)
+ active := data.Get(FieldServiceAccountKeyActive).(bool)
+
+ if expiresAt != "" {
+ expiresAtParsed, err := time.Parse(time.RFC3339, expiresAt)
+ if err != nil {
+ return diag.Errorf("parsing expires_at date: %v", err)
+ }
+ expiresAtTime = &expiresAtParsed
+ }
+
+ logKeys := map[string]interface{}{
+ "name": name,
+ "organization_id": organizationID,
+ "service_account_id": serviceAccountID,
+ }
+
+ tflog.Info(ctx, "creating service account key", logKeys)
+
+ resp, err := client.ServiceAccountsAPICreateServiceAccountKeyWithResponse(
+ ctx,
+ organizationID,
+ serviceAccountID,
+ sdk.ServiceAccountsAPICreateServiceAccountKeyRequest{
+ Key: sdk.CastaiServiceaccountsV1beta1CreateServiceAccountKeyRequestKey{
+ Active: &active,
+ ExpiresAt: expiresAtTime,
+ Name: name,
+ },
+ },
+ )
+ if err := sdk.CheckResponseCreated(resp, err); err != nil {
+ return diag.Errorf("creating service account key: %v", err)
+ }
+
+ if resp.JSON201 == nil {
+ return diag.Errorf("creating service account key: response is missing")
+ }
+
+ if resp.JSON201.Id == nil {
+ return diag.Errorf("creating service account key: id is missing")
+ }
+
+ if resp.JSON201.Token == nil {
+ return diag.Errorf("creating service account key: token is missing")
+ }
+
+ serviceAccountKeyID := resp.JSON201.Id
+
+ logKeys["resource_id"] = serviceAccountKeyID
+ tflog.Info(ctx, "created service account key", logKeys)
+
+ data.SetId(*serviceAccountKeyID)
+
+ if err := data.Set(FieldServiceAccountKeyToken, *resp.JSON201.Token); err != nil {
+ return diag.Errorf("setting field %s: %v", FieldServiceAccountKeyToken, err)
+ }
+ return resourceServiceAccountKeyRead(ctx, data, meta)
+}
+
+func resourceServiceAccountKeyUpdate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ client := meta.(*ProviderConfig).api
+ organizationID, err := getOrganizationID(ctx, data, meta)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ serviceAccountID := data.Get(FieldServiceAccountKeyServiceAccountID).(string)
+ keyID := data.Id()
+ active := data.Get(FieldServiceAccountKeyActive).(bool)
+
+ logKeys := map[string]interface{}{
+ "organization_id": organizationID,
+ "service_account_id": serviceAccountID,
+ "resource_id": keyID,
+ }
+
+ tflog.Info(ctx, "updating service account key", logKeys)
+
+ resp, err := client.ServiceAccountsAPIUpdateServiceAccountKeyWithResponse(ctx, organizationID, serviceAccountID, keyID, sdk.KeyIsTheServiceAccountKeyToUpdate{
+ Active: active,
+ })
+
+ if err := sdk.CheckOKResponse(resp, err); err != nil {
+ return diag.Errorf("updating service account key: %v", err)
+ }
+
+ tflog.Info(ctx, "updated service account key", logKeys)
+
+ return resourceServiceAccountKeyRead(ctx, data, meta)
+}
+
+func resourceServiceAccountKeyDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ client := meta.(*ProviderConfig).api
+ organizationID, err := getOrganizationID(ctx, data, meta)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ serviceAccountID := data.Get(FieldServiceAccountKeyServiceAccountID).(string)
+ keyID := data.Id()
+
+ logKeys := map[string]interface{}{
+ "organization_id": organizationID,
+ "service_account_id": serviceAccountID,
+ "resource_id": keyID,
+ }
+
+ tflog.Info(ctx, "deleting service account key", logKeys)
+
+ resp, err := client.ServiceAccountsAPIDeleteServiceAccountKey(ctx, organizationID, serviceAccountID, keyID)
+ if err := sdk.CheckRawResponseNoContent(resp, err); err != nil {
+ return diag.Errorf("deleting service account key: %v", err)
+ }
+
+ tflog.Info(ctx, "deleted service account key", logKeys)
+
+ data.SetId("")
+
+ return nil
+}
diff --git a/castai/resource_service_account_key_test.go b/castai/resource_service_account_key_test.go
new file mode 100644
index 00000000..bc1e7af9
--- /dev/null
+++ b/castai/resource_service_account_key_test.go
@@ -0,0 +1,592 @@
+package castai
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "io"
+ "net/http"
+ "testing"
+
+ "github.com/golang/mock/gomock"
+ "github.com/hashicorp/go-cty/cty"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+ "github.com/stretchr/testify/require"
+
+ "github.com/castai/terraform-provider-castai/castai/sdk"
+ mock_sdk "github.com/castai/terraform-provider-castai/castai/sdk/mock"
+)
+
+func TestServiceAccountKey_CreateContext(t *testing.T) {
+ t.Parallel()
+
+ t.Run("when ServiceAccountsAPI responds with non-201 status then return error", func(t *testing.T) {
+ r := require.New(t)
+ mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t))
+
+ ctx := context.Background()
+ provider := &ProviderConfig{
+ api: &sdk.ClientWithResponses{
+ ClientInterface: mockClient,
+ },
+ }
+
+ organizationID := "4e4cd9eb-82eb-407e-a926-e5fef81cab50"
+ name := "service-account-key-name"
+ serviceAccountID := "4e4cd9eb-82eb-407e-a926-e5fef81cab51"
+ expiresAt := "2024-12-01T15:19:40.384Z"
+
+ body := io.NopCloser(bytes.NewReader([]byte("mock error response")))
+
+ mockClient.EXPECT().
+ ServiceAccountsAPICreateServiceAccountKey(gomock.Any(), organizationID, serviceAccountID, gomock.Any()).
+ Return(&http.Response{
+ StatusCode: http.StatusInternalServerError,
+ Body: body,
+ }, nil)
+
+ resource := resourceServiceAccountKey()
+ stateValue := cty.ObjectVal(map[string]cty.Value{
+ "organization_id": cty.StringVal(organizationID),
+ "name": cty.StringVal(name),
+ "service_account_id": cty.StringVal(serviceAccountID),
+ "expires_at": cty.StringVal(expiresAt),
+ })
+
+ state := terraform.NewInstanceStateShimmedFromValue(stateValue, 0)
+ data := resource.Data(state)
+
+ result := resource.CreateContext(ctx, data, provider)
+
+ r.NotNil(result)
+ r.True(result.HasError())
+ r.Len(result, 1)
+ r.Equal("creating service account key: expected status code 201, received: status=500 body=mock error response", result[0].Summary)
+ })
+
+ t.Run("when ServiceAccountsAPI responds with an error then return error", func(t *testing.T) {
+ r := require.New(t)
+ mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t))
+
+ ctx := context.Background()
+ provider := &ProviderConfig{
+ api: &sdk.ClientWithResponses{
+ ClientInterface: mockClient,
+ },
+ }
+
+ organizationID := "4e4cd9eb-82eb-407e-a926-e5fef81cab50"
+ name := "service-account-key-name"
+ serviceAccountID := "4e4cd9eb-82eb-407e-a926-e5fef81cab51"
+ expiresAt := "2024-12-01T15:19:40.384Z"
+
+ mockClient.EXPECT().
+ ServiceAccountsAPICreateServiceAccountKey(gomock.Any(), organizationID, serviceAccountID, gomock.Any()).
+ Return(nil, fmt.Errorf("mock network error"))
+
+ resource := resourceServiceAccountKey()
+ stateValue := cty.ObjectVal(map[string]cty.Value{
+ "organization_id": cty.StringVal(organizationID),
+ "name": cty.StringVal(name),
+ "service_account_id": cty.StringVal(serviceAccountID),
+ "expires_at": cty.StringVal(expiresAt),
+ })
+
+ state := terraform.NewInstanceStateShimmedFromValue(stateValue, 0)
+ data := resource.Data(state)
+
+ result := resource.CreateContext(ctx, data, provider)
+
+ r.NotNil(result)
+ r.True(result.HasError())
+ r.Len(result, 1)
+ r.Equal("creating service account key: mock network error", result[0].Summary)
+ })
+
+ t.Run("when ServiceAccountAPI respond with 201 then populate the state", func(t *testing.T) {
+ r := require.New(t)
+ mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t))
+
+ ctx := context.Background()
+ provider := &ProviderConfig{
+ api: &sdk.ClientWithResponses{
+ ClientInterface: mockClient,
+ },
+ }
+
+ organizationID := "4e4cd9eb-82eb-407e-a926-e5fef81cab50"
+ serviceAccountID := "b11f5945-22ca-4101-a86e-d6e37f44a415"
+ keyID := "da5664b3-87bf-4e03-9d1c-ec26049991b7"
+ expiresAt := "2024-12-01T15:19:40.384Z"
+
+ body := io.NopCloser(bytes.NewReader([]byte(fmt.Sprintf(`{
+ "key": {
+ "id": %q,
+ "name": "test-key",
+ "prefix": "123q",
+ "lastUsedAt": "2024-12-11T13:47:20.927Z",
+ "expiresAt": %q,
+ "active": true
+ }
+}`, keyID, expiresAt))))
+
+ createBody := io.NopCloser(bytes.NewReader([]byte(fmt.Sprintf(`{
+ "id": %q,
+ "name": "test-key",
+ "token": "secret",
+ "lastUsedAt": "2024-12-11T13:47:20.927Z",
+ "expiresAt": %q,
+ "active": true
+}`, keyID, expiresAt))))
+
+ mockClient.EXPECT().
+ ServiceAccountsAPICreateServiceAccountKey(gomock.Any(), organizationID, serviceAccountID, gomock.Any()).
+ Return(&http.Response{StatusCode: http.StatusCreated, Body: createBody, Header: map[string][]string{"Content-Type": {"json"}}}, nil)
+
+ mockClient.EXPECT().
+ ServiceAccountsAPIGetServiceAccountKey(gomock.Any(), organizationID, serviceAccountID, keyID).
+ Return(&http.Response{StatusCode: http.StatusOK, Body: body, Header: map[string][]string{"Content-Type": {"json"}}}, nil)
+
+ stateValue := cty.ObjectVal(map[string]cty.Value{
+ "organization_id": cty.StringVal(organizationID),
+ "service_account_id": cty.StringVal(serviceAccountID),
+ "expires_at": cty.StringVal(expiresAt),
+ })
+
+ state := terraform.NewInstanceStateShimmedFromValue(stateValue, 0)
+ state.ID = keyID
+
+ resource := resourceServiceAccountKey()
+ data := resource.Data(state)
+
+ result := resource.CreateContext(ctx, data, provider)
+
+ r.Nil(result)
+ r.False(result.HasError())
+ r.Equal(`ID = da5664b3-87bf-4e03-9d1c-ec26049991b7
+active = true
+expires_at = 2024-12-01T15:19:40Z
+last_used_at = 2024-12-11T13:47:20Z
+name = test-key
+organization_id = 4e4cd9eb-82eb-407e-a926-e5fef81cab50
+prefix = 123q
+service_account_id = b11f5945-22ca-4101-a86e-d6e37f44a415
+token = secret
+Tainted = false
+`, data.State().String())
+ })
+
+ // TODO: what happens if i dont provide expiresAt nor active
+}
+
+func TestServiceAccountKey_ReadContext(t *testing.T) {
+ t.Parallel()
+
+ t.Run("when ServiceAccountAPI respond with 404 then remove form the state gracefully", func(t *testing.T) {
+ r := require.New(t)
+ mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t))
+
+ ctx := context.Background()
+ provider := &ProviderConfig{
+ api: &sdk.ClientWithResponses{
+ ClientInterface: mockClient,
+ },
+ }
+ body := io.NopCloser(bytes.NewReader([]byte("")))
+
+ organizationID := "4e4cd9eb-82eb-407e-a926-e5fef81cab50"
+ serviceAccountID := "b11f5945-22ca-4101-a86e-d6e37f44a415"
+ keyID := "da5664b3-87bf-4e03-9d1c-ec26049991b7"
+
+ mockClient.EXPECT().
+ ServiceAccountsAPIGetServiceAccountKey(gomock.Any(), organizationID, serviceAccountID, keyID).
+ Return(&http.Response{StatusCode: http.StatusNotFound, Body: body, Header: map[string][]string{"Content-Type": {"json"}}}, nil)
+
+ stateValue := cty.ObjectVal(map[string]cty.Value{
+ "organization_id": cty.StringVal(organizationID),
+ "service_account_id": cty.StringVal(serviceAccountID),
+ })
+ state := terraform.NewInstanceStateShimmedFromValue(stateValue, 0)
+ state.ID = keyID
+
+ resource := resourceServiceAccountKey()
+ data := resource.Data(state)
+
+ result := resource.ReadContext(ctx, data, provider)
+
+ r.Nil(result)
+ r.False(result.HasError())
+ r.Empty(data.Id())
+ })
+
+ t.Run("when ServiceAccountAPI respond with 500 then return error", func(t *testing.T) {
+ r := require.New(t)
+ mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t))
+
+ ctx := context.Background()
+ provider := &ProviderConfig{
+ api: &sdk.ClientWithResponses{
+ ClientInterface: mockClient,
+ },
+ }
+ body := io.NopCloser(bytes.NewReader([]byte("panic")))
+
+ organizationID := "4e4cd9eb-82eb-407e-a926-e5fef81cab50"
+ serviceAccountID := "b11f5945-22ca-4101-a86e-d6e37f44a415"
+ keyID := "da5664b3-87bf-4e03-9d1c-ec26049991b7"
+
+ mockClient.EXPECT().
+ ServiceAccountsAPIGetServiceAccountKey(gomock.Any(), organizationID, serviceAccountID, keyID).
+ Return(&http.Response{StatusCode: http.StatusInternalServerError, Body: body, Header: map[string][]string{"Content-Type": {"json"}}}, nil)
+
+ stateValue := cty.ObjectVal(map[string]cty.Value{
+ "organization_id": cty.StringVal(organizationID),
+ "service_account_id": cty.StringVal(serviceAccountID),
+ })
+ state := terraform.NewInstanceStateShimmedFromValue(stateValue, 0)
+ state.ID = keyID
+
+ resource := resourceServiceAccountKey()
+ data := resource.Data(state)
+
+ result := resource.ReadContext(ctx, data, provider)
+
+ r.NotNil(result)
+ r.True(result.HasError())
+ r.Len(result, 1)
+ r.Equal("reading service account key: expected status code 200, received: status=500 body=panic", result[0].Summary)
+ })
+
+ t.Run("when ServiceAccountAPI respond with 200 then populate the state", func(t *testing.T) {
+ r := require.New(t)
+ mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t))
+
+ ctx := context.Background()
+ provider := &ProviderConfig{
+ api: &sdk.ClientWithResponses{
+ ClientInterface: mockClient,
+ },
+ }
+
+ organizationID := "4e4cd9eb-82eb-407e-a926-e5fef81cab50"
+ serviceAccountID := "b11f5945-22ca-4101-a86e-d6e37f44a415"
+ keyID := "da5664b3-87bf-4e03-9d1c-ec26049991b7"
+
+ body := io.NopCloser(bytes.NewReader([]byte(fmt.Sprintf(`{
+ "key": {
+ "id": %q,
+ "name": "test-key",
+ "prefix": "123q",
+ "lastUsedAt": "2024-12-11T13:47:20.927Z",
+ "expiresAt": "2024-12-11T13:47:20.927Z",
+ "active": true
+ }
+}`, keyID))))
+
+ mockClient.EXPECT().
+ ServiceAccountsAPIGetServiceAccountKey(gomock.Any(), organizationID, serviceAccountID, keyID).
+ Return(&http.Response{StatusCode: http.StatusOK, Body: body, Header: map[string][]string{"Content-Type": {"json"}}}, nil)
+
+ stateValue := cty.ObjectVal(map[string]cty.Value{
+ "organization_id": cty.StringVal(organizationID),
+ "service_account_id": cty.StringVal(serviceAccountID),
+ })
+ state := terraform.NewInstanceStateShimmedFromValue(stateValue, 0)
+ state.ID = keyID
+
+ resource := resourceServiceAccountKey()
+ data := resource.Data(state)
+
+ result := resource.ReadContext(ctx, data, provider)
+
+ r.Nil(result)
+ r.False(result.HasError())
+ r.Equal(`ID = da5664b3-87bf-4e03-9d1c-ec26049991b7
+active = true
+expires_at = 2024-12-11T13:47:20Z
+last_used_at = 2024-12-11T13:47:20Z
+name = test-key
+organization_id = 4e4cd9eb-82eb-407e-a926-e5fef81cab50
+prefix = 123q
+service_account_id = b11f5945-22ca-4101-a86e-d6e37f44a415
+Tainted = false
+`, data.State().String())
+ })
+}
+
+func TestServiceAccountKey_DeleteContext(t *testing.T) {
+ t.Parallel()
+
+ t.Run("when ServiceAccountsAPI responds with an error then return error", func(t *testing.T) {
+ r := require.New(t)
+ mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t))
+
+ ctx := context.Background()
+ provider := &ProviderConfig{
+ api: &sdk.ClientWithResponses{
+ ClientInterface: mockClient,
+ },
+ }
+
+ organizationID := "4e4cd9eb-82eb-407e-a926-e5fef81cab50"
+ serviceAccountID := "4e4cd9eb-82eb-407e-a926-e5fef81cab51"
+ keyID := "da5664b3-87bf-4e03-9d1c-ec26049991b7"
+
+ mockClient.EXPECT().
+ ServiceAccountsAPIDeleteServiceAccountKey(gomock.Any(), organizationID, serviceAccountID, keyID).
+ Return(nil, fmt.Errorf("mock network error"))
+
+ resource := resourceServiceAccountKey()
+ stateValue := cty.ObjectVal(map[string]cty.Value{
+ "organization_id": cty.StringVal(organizationID),
+ "service_account_id": cty.StringVal(serviceAccountID),
+ })
+
+ state := terraform.NewInstanceStateShimmedFromValue(stateValue, 0)
+ state.ID = keyID
+ data := resource.Data(state)
+
+ result := resource.DeleteContext(ctx, data, provider)
+
+ r.NotNil(result)
+ r.True(result.HasError())
+ r.Len(result, 1)
+ r.Equal("deleting service account key: mock network error", result[0].Summary)
+ })
+
+ t.Run("when ServiceAccountsAPI responds with non-201 status then return error", func(t *testing.T) {
+ r := require.New(t)
+ mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t))
+
+ ctx := context.Background()
+ provider := &ProviderConfig{
+ api: &sdk.ClientWithResponses{
+ ClientInterface: mockClient,
+ },
+ }
+
+ organizationID := "4e4cd9eb-82eb-407e-a926-e5fef81cab50"
+ serviceAccountID := "4e4cd9eb-82eb-407e-a926-e5fef81cab51"
+ keyID := "da5664b3-87bf-4e03-9d1c-ec26049991b7"
+
+ body := io.NopCloser(bytes.NewReader([]byte("mock error response")))
+
+ mockClient.EXPECT().
+ ServiceAccountsAPIDeleteServiceAccountKey(gomock.Any(), organizationID, serviceAccountID, keyID).
+ Return(&http.Response{
+ StatusCode: http.StatusInternalServerError,
+ Body: body,
+ }, nil)
+
+ resource := resourceServiceAccountKey()
+ stateValue := cty.ObjectVal(map[string]cty.Value{
+ "organization_id": cty.StringVal(organizationID),
+ "service_account_id": cty.StringVal(serviceAccountID),
+ })
+
+ state := terraform.NewInstanceStateShimmedFromValue(stateValue, 0)
+ state.ID = keyID
+ data := resource.Data(state)
+
+ result := resource.DeleteContext(ctx, data, provider)
+
+ r.NotNil(result)
+ r.True(result.HasError())
+ r.Len(result, 1)
+ r.Equal("deleting service account key: expected status code 204, received: status=500 body=mock error response", result[0].Summary)
+ })
+
+ t.Run("when ServiceAccountsAPI responds with 204 then return nil", func(t *testing.T) {
+ r := require.New(t)
+ mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t))
+
+ ctx := context.Background()
+ provider := &ProviderConfig{
+ api: &sdk.ClientWithResponses{
+ ClientInterface: mockClient,
+ },
+ }
+
+ organizationID := "4e4cd9eb-82eb-407e-a926-e5fef81cab50"
+ serviceAccountID := "4e4cd9eb-82eb-407e-a926-e5fef81cab51"
+ keyID := "da5664b3-87bf-4e03-9d1c-ec26049991b7"
+
+ mockClient.EXPECT().
+ ServiceAccountsAPIDeleteServiceAccountKey(gomock.Any(), organizationID, serviceAccountID, keyID).
+ Return(&http.Response{
+ StatusCode: http.StatusNoContent,
+ }, nil)
+
+ resource := resourceServiceAccountKey()
+ stateValue := cty.ObjectVal(map[string]cty.Value{
+ "organization_id": cty.StringVal(organizationID),
+ "service_account_id": cty.StringVal(serviceAccountID),
+ "name": cty.StringVal("key name"),
+ "expires_at": cty.StringVal("2022-01-01T00:00:00Z"),
+ "last_used_at": cty.StringVal("2022-01-01T00:00:00Z"),
+ "active": cty.BoolVal(true),
+ "prefix": cty.StringVal("key prefix"),
+ })
+
+ state := terraform.NewInstanceStateShimmedFromValue(stateValue, 0)
+ state.ID = keyID
+ data := resource.Data(state)
+
+ result := resource.DeleteContext(ctx, data, provider)
+
+ r.Nil(result)
+ r.False(result.HasError())
+ r.True(data.State().Empty())
+ })
+}
+
+func TestServiceAccountKey_UpdateContext(t *testing.T) {
+ t.Parallel()
+
+ t.Run("when ServiceAccountsAPI responds with an error then return error", func(t *testing.T) {
+ r := require.New(t)
+ mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t))
+ ctx := context.Background()
+ provider := &ProviderConfig{
+ api: &sdk.ClientWithResponses{
+ ClientInterface: mockClient,
+ },
+ }
+
+ organizationID := "4e4cd9eb-82eb-407e-a926-e5fef81cab50"
+ serviceAccountID := "4e4cd9eb-82eb-407e-a926-e5fef81cab51"
+ keyID := "da5664b3-87bf-4e03-9d1c-ec26049991b7"
+
+ mockClient.EXPECT().
+ ServiceAccountsAPIUpdateServiceAccountKey(gomock.Any(), organizationID, serviceAccountID, keyID, gomock.Any()).
+ Return(nil, fmt.Errorf("mock network error"))
+
+ resource := resourceServiceAccountKey()
+ stateValue := cty.ObjectVal(map[string]cty.Value{
+ "organization_id": cty.StringVal(organizationID),
+ "name": cty.StringVal("new name"),
+ "active": cty.BoolVal(true),
+ "service_account_id": cty.StringVal(serviceAccountID),
+ })
+ state := terraform.NewInstanceStateShimmedFromValue(stateValue, 0)
+ state.ID = keyID
+ data := resource.Data(state)
+
+ result := resource.UpdateContext(ctx, data, provider)
+
+ r.NotNil(result)
+ r.True(result.HasError())
+ r.Len(result, 1)
+ r.Equal("updating service account key: mock network error", result[0].Summary)
+ })
+
+ t.Run("when ServiceAccountsAPI responds with non-200 status then return error", func(t *testing.T) {
+ r := require.New(t)
+ mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t))
+ ctx := context.Background()
+ provider := &ProviderConfig{
+ api: &sdk.ClientWithResponses{
+ ClientInterface: mockClient,
+ },
+ }
+
+ organizationID := "4e4cd9eb-82eb-407e-a926-e5fef81cab50"
+ serviceAccountID := "4e4cd9eb-82eb-407e-a926-e5fef81cab51"
+ keyID := "da5664b3-87bf-4e03-9d1c-ec26049991b7"
+
+ body := io.NopCloser(bytes.NewReader([]byte("mock error response")))
+
+ mockClient.EXPECT().
+ ServiceAccountsAPIUpdateServiceAccountKey(gomock.Any(), organizationID, serviceAccountID, keyID, gomock.Any()).
+ Return(&http.Response{
+ StatusCode: http.StatusInternalServerError,
+ Body: body,
+ }, nil)
+
+ resource := resourceServiceAccountKey()
+ stateValue := cty.ObjectVal(map[string]cty.Value{
+ "organization_id": cty.StringVal(organizationID),
+ "service_account_id": cty.StringVal(serviceAccountID),
+ "name": cty.StringVal("new name"),
+ "active": cty.BoolVal(true),
+ })
+ state := terraform.NewInstanceStateShimmedFromValue(stateValue, 0)
+ state.ID = keyID
+ data := resource.Data(state)
+
+ result := resource.UpdateContext(ctx, data, provider)
+
+ r.NotNil(result)
+ r.True(result.HasError())
+ r.Len(result, 1)
+ r.Equal("updating service account key: expected status code 200, received: status=500 body=mock error response", result[0].Summary)
+ })
+
+ t.Run("when ServiceAccountsAPI responds with 200 then return nil", func(t *testing.T) {
+ r := require.New(t)
+ mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t))
+ ctx := context.Background()
+ provider := &ProviderConfig{
+ api: &sdk.ClientWithResponses{
+ ClientInterface: mockClient,
+ },
+ }
+
+ organizationID := "4e4cd9eb-82eb-407e-a926-e5fef81cab50"
+ serviceAccountID := "4e4cd9eb-82eb-407e-a926-e5fef81cab51"
+ keyID := "da5664b3-87bf-4e03-9d1c-ec26049991b7"
+ expiresAt := "2024-12-11T13:47:20.927Z"
+ name := "test-key"
+
+ body := io.NopCloser(bytes.NewReader([]byte(`{"active":false}`)))
+ readBody := io.NopCloser(bytes.NewReader([]byte(fmt.Sprintf(`{
+ "key": {
+ "id": %q,
+ "name": "test-key",
+ "prefix": "123q",
+ "lastUsedAt": "2024-12-11T13:47:20.927Z",
+ "expiresAt": %q,
+ "active": false
+ }
+}`, keyID, expiresAt))))
+
+ mockClient.EXPECT().
+ ServiceAccountsAPIUpdateServiceAccountKey(gomock.Any(), organizationID, serviceAccountID, keyID, gomock.Any()).
+ Return(&http.Response{
+ StatusCode: http.StatusOK,
+ Body: body,
+ }, nil)
+ mockClient.EXPECT().
+ ServiceAccountsAPIGetServiceAccountKey(gomock.Any(), organizationID, serviceAccountID, keyID).
+ Return(&http.Response{
+ StatusCode: http.StatusOK,
+ Body: readBody,
+ Header: map[string][]string{"Content-Type": {"json"}},
+ }, nil)
+
+ resource := resourceServiceAccountKey()
+ stateValue := cty.ObjectVal(map[string]cty.Value{
+ "organization_id": cty.StringVal(organizationID),
+ "service_account_id": cty.StringVal(serviceAccountID),
+ "active": cty.BoolVal(true),
+ "name": cty.StringVal(name),
+ })
+ state := terraform.NewInstanceStateShimmedFromValue(stateValue, 0)
+ state.ID = keyID
+ data := resource.Data(state)
+
+ result := resource.UpdateContext(ctx, data, provider)
+
+ r.Nil(result)
+ r.Equal(`ID = da5664b3-87bf-4e03-9d1c-ec26049991b7
+active = false
+expires_at = 2024-12-11T13:47:20Z
+last_used_at = 2024-12-11T13:47:20Z
+name = test-key
+organization_id = 4e4cd9eb-82eb-407e-a926-e5fef81cab50
+prefix = 123q
+service_account_id = 4e4cd9eb-82eb-407e-a926-e5fef81cab51
+Tainted = false
+`, data.State().String())
+ })
+}
diff --git a/castai/resource_service_account_test.go b/castai/resource_service_account_test.go
index 62ab2b31..d4d078d3 100644
--- a/castai/resource_service_account_test.go
+++ b/castai/resource_service_account_test.go
@@ -19,7 +19,7 @@ import (
mock_sdk "github.com/castai/terraform-provider-castai/castai/sdk/mock"
)
-func TestServiceAccountReadContext(t *testing.T) {
+func TestServiceAccount_ReadContext(t *testing.T) {
t.Parallel()
t.Run("when state is missing service account ID then return error", func(t *testing.T) {
@@ -184,7 +184,7 @@ Tainted = false
})
}
-func TestServiceAccountCreateContext(t *testing.T) {
+func TestServiceAccount_CreateContext(t *testing.T) {
t.Parallel()
t.Run("when ServiceAccountsAPI responds with 201 then populate the state", func(t *testing.T) {
@@ -348,7 +348,7 @@ func TestServiceAccountCreateContext(t *testing.T) {
})
}
-func TestServiceAccountDeleteContext(t *testing.T) {
+func TestServiceAccount_DeleteContext(t *testing.T) {
t.Run("when ServiceAccountsAPI responds with an error then return error", func(t *testing.T) {
r := require.New(t)
mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t))
@@ -421,7 +421,7 @@ func TestServiceAccountDeleteContext(t *testing.T) {
r.NotNil(result)
r.True(result.HasError())
r.Len(result, 1)
- r.Equal("deleteting service account: expected status: [204], received status: [500]", result[0].Summary)
+ r.Equal("deleting service account: expected status code 204, received: status=500 body=mock error response", result[0].Summary)
})
t.Run("when ServiceAccountsAPI responds with 204 then return nil", func(t *testing.T) {
@@ -457,15 +457,11 @@ func TestServiceAccountDeleteContext(t *testing.T) {
r.Nil(result)
r.False(result.HasError())
- r.Empty(data.Get(FieldServiceAccountID))
- r.Empty(data.Get(FieldServiceAccountEmail))
- r.Empty(data.Get(FieldServiceAccountName))
- r.Empty(data.Get(FieldServiceAccountDescription))
- r.Empty(data.Get(FieldServiceAccountAuthor))
+ r.True(data.State().Empty())
})
}
-func TestServiceAccountUpdateContext(t *testing.T) {
+func TestServiceAccount_UpdateContext(t *testing.T) {
t.Run("when ServiceAccountsAPI responds with an error then return error", func(t *testing.T) {
r := require.New(t)
mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t))
@@ -575,7 +571,7 @@ func TestServiceAccountUpdateContext(t *testing.T) {
"email": "user-email"
},
"keys": []
- }}`, serviceAccountID,userID))))
+ }}`, serviceAccountID, userID))))
mockClient.EXPECT().
ServiceAccountsAPIUpdateServiceAccount(gomock.Any(), organizationID, serviceAccountID, gomock.Any()).
@@ -588,7 +584,7 @@ func TestServiceAccountUpdateContext(t *testing.T) {
Return(&http.Response{
StatusCode: http.StatusOK,
Body: readBody,
- Header: map[string][]string{"Content-Type": {"json"}},
+ Header: map[string][]string{"Content-Type": {"json"}},
}, nil)
resource := resourceServiceAccount()
diff --git a/castai/sdk/api.gen.go b/castai/sdk/api.gen.go
index d2c0d3c1..6d8204dd 100644
--- a/castai/sdk/api.gen.go
+++ b/castai/sdk/api.gen.go
@@ -398,6 +398,11 @@ type GroupsIsTheGroupsToBeUpdated struct {
Name string `json:"name"`
}
+// KeyIsTheServiceAccountKeyToUpdate defines model for Key_is_the_service_account_key_to_update_.
+type KeyIsTheServiceAccountKeyToUpdate struct {
+ Active bool `json:"active"`
+}
+
// RoleBindingIsTheRoleBindingToBeUpdated defines model for RoleBinding_is_the_role_binding_to_be_updated_.
type RoleBindingIsTheRoleBindingToBeUpdated struct {
// Definition represents the role binding definition.
@@ -1424,6 +1429,9 @@ type CastaiRbacV1beta1Group struct {
// ID is the unique identifier of the group.
Id *string `json:"id,omitempty"`
+ // Method used to create group, eg.: console, terraform.
+ ManagedBy *string `json:"managedBy,omitempty"`
+
// Name is the name of the group.
Name *string `json:"name,omitempty"`
@@ -1511,6 +1519,9 @@ type CastaiRbacV1beta1RoleBinding struct {
// ID is the unique identifier of the role binding.
Id *string `json:"id,omitempty"`
+ // Method used to create role binding, eg.: console, terraform.
+ ManagedBy *string `json:"managedBy,omitempty"`
+
// Name is the name of the role binding.
Name *string `json:"name,omitempty"`
OrganizationId *string `json:"organizationId,omitempty"`
@@ -1608,6 +1619,9 @@ type CastaiRbacV1beta1UserSubject struct {
// Key is the readable version of the service account key.
type CastaiServiceaccountsV1beta1CreateServiceAccountKeyRequestKey struct {
+ // Active is the active state of the key.
+ Active *bool `json:"active,omitempty"`
+
// ExpiresAt is the expiration time of the key.
// A null value means that the key will never expire.
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
@@ -1668,6 +1682,12 @@ type CastaiServiceaccountsV1beta1DeleteServiceAccountKeyResponse = map[string]in
// DeleteServiceAccountResponse is the response for deleting a service account.
type CastaiServiceaccountsV1beta1DeleteServiceAccountResponse = map[string]interface{}
+// GetServiceAccountKeyResponse is the response for getting a service account key.
+type CastaiServiceaccountsV1beta1GetServiceAccountKeyResponse struct {
+ // Key is the key for the service account.
+ Key CastaiServiceaccountsV1beta1ServiceAccountKey `json:"key"`
+}
+
// GetServiceAccountResponse is the response for getting a service account.
type CastaiServiceaccountsV1beta1GetServiceAccountResponse struct {
// ServiceAccounts is the readable version of the service accounts.
@@ -1706,6 +1726,9 @@ type CastaiServiceaccountsV1beta1ServiceAccount struct {
// Keys is the list of keys associated with the service account.
Keys *[]CastaiServiceaccountsV1beta1ServiceAccountKey `json:"keys,omitempty"`
+ // Method used to create role binding, eg.: console, terraform.
+ ManagedBy *string `json:"managedBy,omitempty"`
+
// Name is the name of the service account.
Name *string `json:"name,omitempty"`
}
@@ -4296,6 +4319,7 @@ type WorkloadoptimizationV1ResourceConfig struct {
// QUANTILE - the quantile function.
// MAX - the max function.
Function WorkloadoptimizationV1ResourceConfigFunction `json:"function"`
+ Limit *WorkloadoptimizationV1ResourceLimitStrategy `json:"limit,omitempty"`
// Period of time over which the resource recommendation is calculated (default value is 24 hours).
LookBackPeriodSeconds *int32 `json:"lookBackPeriodSeconds"`
@@ -4328,6 +4352,12 @@ type WorkloadoptimizationV1ResourceConfigUpdate struct {
Min *float64 `json:"min"`
}
+// WorkloadoptimizationV1ResourceLimitStrategy defines model for workloadoptimization.v1.ResourceLimitStrategy.
+type WorkloadoptimizationV1ResourceLimitStrategy struct {
+ Multiplier *float64 `json:"multiplier,omitempty"`
+ None *bool `json:"none,omitempty"`
+}
+
// WorkloadoptimizationV1ResourceMetrics defines model for workloadoptimization.v1.ResourceMetrics.
type WorkloadoptimizationV1ResourceMetrics struct {
Max float64 `json:"max"`
@@ -4352,6 +4382,7 @@ type WorkloadoptimizationV1ResourcePolicies struct {
// QUANTILE - the quantile function.
// MAX - the max function.
Function WorkloadoptimizationV1ResourcePoliciesFunction `json:"function"`
+ Limit *WorkloadoptimizationV1ResourceLimitStrategy `json:"limit,omitempty"`
// Period of time over which the resource recommendation is calculated (default value is 24 hours).
LookBackPeriodSeconds *int32 `json:"lookBackPeriodSeconds"`
@@ -4464,6 +4495,7 @@ type WorkloadoptimizationV1UpdateWorkloadV2 struct {
// WorkloadoptimizationV1VPAConfig defines model for workloadoptimization.v1.VPAConfig.
type WorkloadoptimizationV1VPAConfig struct {
AntiAffinity WorkloadoptimizationV1AntiAffinitySettings `json:"antiAffinity"`
+ ApplyType WorkloadoptimizationV1ApplyType `json:"applyType"`
ContainerConstraints []WorkloadoptimizationV1ContainerConstraints `json:"containerConstraints"`
Cpu WorkloadoptimizationV1ResourceConfig `json:"cpu"`
@@ -4602,6 +4634,8 @@ type WorkloadoptimizationV1WorkloadRecommendation struct {
// WorkloadoptimizationV1WorkloadResourceConfigUpdate defines model for workloadoptimization.v1.WorkloadResourceConfigUpdate.
type WorkloadoptimizationV1WorkloadResourceConfigUpdate struct {
+ Limit *WorkloadoptimizationV1ResourceLimitStrategy `json:"limit,omitempty"`
+
// Period of time over which the resource recommendation is calculated (default value is 24 hours).
LookBackPeriodSeconds *int32 `json:"lookBackPeriodSeconds"`
@@ -4869,10 +4903,8 @@ type ServiceAccountsAPIUpdateServiceAccountJSONBody = ServiceAccountsAPIUpdateSe
// ServiceAccountsAPICreateServiceAccountKeyJSONBody defines parameters for ServiceAccountsAPICreateServiceAccountKey.
type ServiceAccountsAPICreateServiceAccountKeyJSONBody = ServiceAccountsAPICreateServiceAccountKeyRequest
-// ServiceAccountsAPIUpdateServiceAccountKeyParams defines parameters for ServiceAccountsAPIUpdateServiceAccountKey.
-type ServiceAccountsAPIUpdateServiceAccountKeyParams struct {
- KeyActive bool `form:"key.active" json:"key.active"`
-}
+// ServiceAccountsAPIUpdateServiceAccountKeyJSONBody defines parameters for ServiceAccountsAPIUpdateServiceAccountKey.
+type ServiceAccountsAPIUpdateServiceAccountKeyJSONBody = KeyIsTheServiceAccountKeyToUpdate
// UsersAPIRemoveOrganizationUsersParams defines parameters for UsersAPIRemoveOrganizationUsers.
type UsersAPIRemoveOrganizationUsersParams struct {
@@ -5145,6 +5177,9 @@ type ServiceAccountsAPIUpdateServiceAccountJSONRequestBody = ServiceAccountsAPIU
// ServiceAccountsAPICreateServiceAccountKeyJSONRequestBody defines body for ServiceAccountsAPICreateServiceAccountKey for application/json ContentType.
type ServiceAccountsAPICreateServiceAccountKeyJSONRequestBody = ServiceAccountsAPICreateServiceAccountKeyJSONBody
+// ServiceAccountsAPIUpdateServiceAccountKeyJSONRequestBody defines body for ServiceAccountsAPIUpdateServiceAccountKey for application/json ContentType.
+type ServiceAccountsAPIUpdateServiceAccountKeyJSONRequestBody = ServiceAccountsAPIUpdateServiceAccountKeyJSONBody
+
// UsersAPIAddUserToOrganizationJSONRequestBody defines body for UsersAPIAddUserToOrganization for application/json ContentType.
type UsersAPIAddUserToOrganizationJSONRequestBody = UsersAPIAddUserToOrganizationJSONBody
diff --git a/castai/sdk/client.gen.go b/castai/sdk/client.gen.go
index bccd2fdd..6b501406 100644
--- a/castai/sdk/client.gen.go
+++ b/castai/sdk/client.gen.go
@@ -446,12 +446,17 @@ type ClientInterface interface {
ServiceAccountsAPICreateServiceAccountKey(ctx context.Context, organizationId string, serviceAccountId string, body ServiceAccountsAPICreateServiceAccountKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
- // ServiceAccountsAPIUpdateServiceAccountKey request
- ServiceAccountsAPIUpdateServiceAccountKey(ctx context.Context, organizationId string, serviceAccountId string, keyId string, params *ServiceAccountsAPIUpdateServiceAccountKeyParams, reqEditors ...RequestEditorFn) (*http.Response, error)
+ // ServiceAccountsAPIUpdateServiceAccountKey request with any body
+ ServiceAccountsAPIUpdateServiceAccountKeyWithBody(ctx context.Context, organizationId string, serviceAccountId string, keyId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
+
+ ServiceAccountsAPIUpdateServiceAccountKey(ctx context.Context, organizationId string, serviceAccountId string, keyId string, body ServiceAccountsAPIUpdateServiceAccountKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
// ServiceAccountsAPIDeleteServiceAccountKey request
ServiceAccountsAPIDeleteServiceAccountKey(ctx context.Context, organizationId string, serviceAccountId string, keyId string, reqEditors ...RequestEditorFn) (*http.Response, error)
+ // ServiceAccountsAPIGetServiceAccountKey request
+ ServiceAccountsAPIGetServiceAccountKey(ctx context.Context, organizationId string, serviceAccountId string, keyId string, reqEditors ...RequestEditorFn) (*http.Response, error)
+
// UsersAPIRemoveOrganizationUsers request
UsersAPIRemoveOrganizationUsers(ctx context.Context, organizationId string, params *UsersAPIRemoveOrganizationUsersParams, reqEditors ...RequestEditorFn) (*http.Response, error)
@@ -2197,8 +2202,20 @@ func (c *Client) ServiceAccountsAPICreateServiceAccountKey(ctx context.Context,
return c.Client.Do(req)
}
-func (c *Client) ServiceAccountsAPIUpdateServiceAccountKey(ctx context.Context, organizationId string, serviceAccountId string, keyId string, params *ServiceAccountsAPIUpdateServiceAccountKeyParams, reqEditors ...RequestEditorFn) (*http.Response, error) {
- req, err := NewServiceAccountsAPIUpdateServiceAccountKeyRequest(c.Server, organizationId, serviceAccountId, keyId, params)
+func (c *Client) ServiceAccountsAPIUpdateServiceAccountKeyWithBody(ctx context.Context, organizationId string, serviceAccountId string, keyId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
+ req, err := NewServiceAccountsAPIUpdateServiceAccountKeyRequestWithBody(c.Server, organizationId, serviceAccountId, keyId, contentType, body)
+ if err != nil {
+ return nil, err
+ }
+ req = req.WithContext(ctx)
+ if err := c.applyEditors(ctx, req, reqEditors); err != nil {
+ return nil, err
+ }
+ return c.Client.Do(req)
+}
+
+func (c *Client) ServiceAccountsAPIUpdateServiceAccountKey(ctx context.Context, organizationId string, serviceAccountId string, keyId string, body ServiceAccountsAPIUpdateServiceAccountKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
+ req, err := NewServiceAccountsAPIUpdateServiceAccountKeyRequest(c.Server, organizationId, serviceAccountId, keyId, body)
if err != nil {
return nil, err
}
@@ -2221,6 +2238,18 @@ func (c *Client) ServiceAccountsAPIDeleteServiceAccountKey(ctx context.Context,
return c.Client.Do(req)
}
+func (c *Client) ServiceAccountsAPIGetServiceAccountKey(ctx context.Context, organizationId string, serviceAccountId string, keyId string, reqEditors ...RequestEditorFn) (*http.Response, error) {
+ req, err := NewServiceAccountsAPIGetServiceAccountKeyRequest(c.Server, organizationId, serviceAccountId, keyId)
+ if err != nil {
+ return nil, err
+ }
+ req = req.WithContext(ctx)
+ if err := c.applyEditors(ctx, req, reqEditors); err != nil {
+ return nil, err
+ }
+ return c.Client.Do(req)
+}
+
func (c *Client) UsersAPIRemoveOrganizationUsers(ctx context.Context, organizationId string, params *UsersAPIRemoveOrganizationUsersParams, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewUsersAPIRemoveOrganizationUsersRequest(c.Server, organizationId, params)
if err != nil {
@@ -7368,8 +7397,19 @@ func NewServiceAccountsAPICreateServiceAccountKeyRequestWithBody(server string,
return req, nil
}
-// NewServiceAccountsAPIUpdateServiceAccountKeyRequest generates requests for ServiceAccountsAPIUpdateServiceAccountKey
-func NewServiceAccountsAPIUpdateServiceAccountKeyRequest(server string, organizationId string, serviceAccountId string, keyId string, params *ServiceAccountsAPIUpdateServiceAccountKeyParams) (*http.Request, error) {
+// NewServiceAccountsAPIUpdateServiceAccountKeyRequest calls the generic ServiceAccountsAPIUpdateServiceAccountKey builder with application/json body
+func NewServiceAccountsAPIUpdateServiceAccountKeyRequest(server string, organizationId string, serviceAccountId string, keyId string, body ServiceAccountsAPIUpdateServiceAccountKeyJSONRequestBody) (*http.Request, error) {
+ var bodyReader io.Reader
+ buf, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ bodyReader = bytes.NewReader(buf)
+ return NewServiceAccountsAPIUpdateServiceAccountKeyRequestWithBody(server, organizationId, serviceAccountId, keyId, "application/json", bodyReader)
+}
+
+// NewServiceAccountsAPIUpdateServiceAccountKeyRequestWithBody generates requests for ServiceAccountsAPIUpdateServiceAccountKey with any type of body
+func NewServiceAccountsAPIUpdateServiceAccountKeyRequestWithBody(server string, organizationId string, serviceAccountId string, keyId string, contentType string, body io.Reader) (*http.Request, error) {
var err error
var pathParam0 string
@@ -7408,23 +7448,57 @@ func NewServiceAccountsAPIUpdateServiceAccountKeyRequest(server string, organiza
return nil, err
}
- queryValues := queryURL.Query()
+ req, err := http.NewRequest("PATCH", queryURL.String(), body)
+ if err != nil {
+ return nil, err
+ }
+
+ req.Header.Add("Content-Type", contentType)
- if queryFrag, err := runtime.StyleParamWithLocation("form", true, "key.active", runtime.ParamLocationQuery, params.KeyActive); err != nil {
+ return req, nil
+}
+
+// NewServiceAccountsAPIDeleteServiceAccountKeyRequest generates requests for ServiceAccountsAPIDeleteServiceAccountKey
+func NewServiceAccountsAPIDeleteServiceAccountKeyRequest(server string, organizationId string, serviceAccountId string, keyId string) (*http.Request, error) {
+ var err error
+
+ var pathParam0 string
+
+ pathParam0, err = runtime.StyleParamWithLocation("simple", false, "organizationId", runtime.ParamLocationPath, organizationId)
+ if err != nil {
return nil, err
- } else if parsed, err := url.ParseQuery(queryFrag); err != nil {
+ }
+
+ var pathParam1 string
+
+ pathParam1, err = runtime.StyleParamWithLocation("simple", false, "serviceAccountId", runtime.ParamLocationPath, serviceAccountId)
+ if err != nil {
return nil, err
- } else {
- for k, v := range parsed {
- for _, v2 := range v {
- queryValues.Add(k, v2)
- }
- }
}
- queryURL.RawQuery = queryValues.Encode()
+ var pathParam2 string
- req, err := http.NewRequest("PATCH", queryURL.String(), nil)
+ pathParam2, err = runtime.StyleParamWithLocation("simple", false, "keyId", runtime.ParamLocationPath, keyId)
+ if err != nil {
+ return nil, err
+ }
+
+ serverURL, err := url.Parse(server)
+ if err != nil {
+ return nil, err
+ }
+
+ operationPath := fmt.Sprintf("/v1/organizations/%s/service-accounts/%s/keys/%s", pathParam0, pathParam1, pathParam2)
+ if operationPath[0] == '/' {
+ operationPath = "." + operationPath
+ }
+
+ queryURL, err := serverURL.Parse(operationPath)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("DELETE", queryURL.String(), nil)
if err != nil {
return nil, err
}
@@ -7432,8 +7506,8 @@ func NewServiceAccountsAPIUpdateServiceAccountKeyRequest(server string, organiza
return req, nil
}
-// NewServiceAccountsAPIDeleteServiceAccountKeyRequest generates requests for ServiceAccountsAPIDeleteServiceAccountKey
-func NewServiceAccountsAPIDeleteServiceAccountKeyRequest(server string, organizationId string, serviceAccountId string, keyId string) (*http.Request, error) {
+// NewServiceAccountsAPIGetServiceAccountKeyRequest generates requests for ServiceAccountsAPIGetServiceAccountKey
+func NewServiceAccountsAPIGetServiceAccountKeyRequest(server string, organizationId string, serviceAccountId string, keyId string) (*http.Request, error) {
var err error
var pathParam0 string
@@ -7472,7 +7546,7 @@ func NewServiceAccountsAPIDeleteServiceAccountKeyRequest(server string, organiza
return nil, err
}
- req, err := http.NewRequest("DELETE", queryURL.String(), nil)
+ req, err := http.NewRequest("GET", queryURL.String(), nil)
if err != nil {
return nil, err
}
@@ -10129,12 +10203,17 @@ type ClientWithResponsesInterface interface {
ServiceAccountsAPICreateServiceAccountKeyWithResponse(ctx context.Context, organizationId string, serviceAccountId string, body ServiceAccountsAPICreateServiceAccountKeyJSONRequestBody) (*ServiceAccountsAPICreateServiceAccountKeyResponse, error)
- // ServiceAccountsAPIUpdateServiceAccountKey request
- ServiceAccountsAPIUpdateServiceAccountKeyWithResponse(ctx context.Context, organizationId string, serviceAccountId string, keyId string, params *ServiceAccountsAPIUpdateServiceAccountKeyParams) (*ServiceAccountsAPIUpdateServiceAccountKeyResponse, error)
+ // ServiceAccountsAPIUpdateServiceAccountKey request with any body
+ ServiceAccountsAPIUpdateServiceAccountKeyWithBodyWithResponse(ctx context.Context, organizationId string, serviceAccountId string, keyId string, contentType string, body io.Reader) (*ServiceAccountsAPIUpdateServiceAccountKeyResponse, error)
+
+ ServiceAccountsAPIUpdateServiceAccountKeyWithResponse(ctx context.Context, organizationId string, serviceAccountId string, keyId string, body ServiceAccountsAPIUpdateServiceAccountKeyJSONRequestBody) (*ServiceAccountsAPIUpdateServiceAccountKeyResponse, error)
// ServiceAccountsAPIDeleteServiceAccountKey request
ServiceAccountsAPIDeleteServiceAccountKeyWithResponse(ctx context.Context, organizationId string, serviceAccountId string, keyId string) (*ServiceAccountsAPIDeleteServiceAccountKeyResponse, error)
+ // ServiceAccountsAPIGetServiceAccountKey request
+ ServiceAccountsAPIGetServiceAccountKeyWithResponse(ctx context.Context, organizationId string, serviceAccountId string, keyId string) (*ServiceAccountsAPIGetServiceAccountKeyResponse, error)
+
// UsersAPIRemoveOrganizationUsers request
UsersAPIRemoveOrganizationUsersWithResponse(ctx context.Context, organizationId string, params *UsersAPIRemoveOrganizationUsersParams) (*UsersAPIRemoveOrganizationUsersResponse, error)
@@ -13110,8 +13189,7 @@ func (r ServiceAccountsAPIUpdateServiceAccountResponse) GetBody() []byte {
type ServiceAccountsAPICreateServiceAccountKeyResponse struct {
Body []byte
HTTPResponse *http.Response
- JSON200 *CastaiServiceaccountsV1beta1CreateServiceAccountKeyResponse
- JSON201 *map[string]interface{}
+ JSON201 *CastaiServiceaccountsV1beta1CreateServiceAccountKeyResponse
}
// Status returns HTTPResponse.Status
@@ -13172,6 +13250,7 @@ type ServiceAccountsAPIDeleteServiceAccountKeyResponse struct {
Body []byte
HTTPResponse *http.Response
JSON200 *CastaiServiceaccountsV1beta1DeleteServiceAccountKeyResponse
+ JSON204 *map[string]interface{}
}
// Status returns HTTPResponse.Status
@@ -13198,6 +13277,36 @@ func (r ServiceAccountsAPIDeleteServiceAccountKeyResponse) GetBody() []byte {
// TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240
+type ServiceAccountsAPIGetServiceAccountKeyResponse struct {
+ Body []byte
+ HTTPResponse *http.Response
+ JSON200 *CastaiServiceaccountsV1beta1GetServiceAccountKeyResponse
+}
+
+// Status returns HTTPResponse.Status
+func (r ServiceAccountsAPIGetServiceAccountKeyResponse) Status() string {
+ if r.HTTPResponse != nil {
+ return r.HTTPResponse.Status
+ }
+ return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r ServiceAccountsAPIGetServiceAccountKeyResponse) StatusCode() int {
+ if r.HTTPResponse != nil {
+ return r.HTTPResponse.StatusCode
+ }
+ return 0
+}
+
+// TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240
+// Body returns body of byte array
+func (r ServiceAccountsAPIGetServiceAccountKeyResponse) GetBody() []byte {
+ return r.Body
+}
+
+// TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240
+
type UsersAPIRemoveOrganizationUsersResponse struct {
Body []byte
HTTPResponse *http.Response
@@ -15776,9 +15885,17 @@ func (c *ClientWithResponses) ServiceAccountsAPICreateServiceAccountKeyWithRespo
return ParseServiceAccountsAPICreateServiceAccountKeyResponse(rsp)
}
-// ServiceAccountsAPIUpdateServiceAccountKeyWithResponse request returning *ServiceAccountsAPIUpdateServiceAccountKeyResponse
-func (c *ClientWithResponses) ServiceAccountsAPIUpdateServiceAccountKeyWithResponse(ctx context.Context, organizationId string, serviceAccountId string, keyId string, params *ServiceAccountsAPIUpdateServiceAccountKeyParams) (*ServiceAccountsAPIUpdateServiceAccountKeyResponse, error) {
- rsp, err := c.ServiceAccountsAPIUpdateServiceAccountKey(ctx, organizationId, serviceAccountId, keyId, params)
+// ServiceAccountsAPIUpdateServiceAccountKeyWithBodyWithResponse request with arbitrary body returning *ServiceAccountsAPIUpdateServiceAccountKeyResponse
+func (c *ClientWithResponses) ServiceAccountsAPIUpdateServiceAccountKeyWithBodyWithResponse(ctx context.Context, organizationId string, serviceAccountId string, keyId string, contentType string, body io.Reader) (*ServiceAccountsAPIUpdateServiceAccountKeyResponse, error) {
+ rsp, err := c.ServiceAccountsAPIUpdateServiceAccountKeyWithBody(ctx, organizationId, serviceAccountId, keyId, contentType, body)
+ if err != nil {
+ return nil, err
+ }
+ return ParseServiceAccountsAPIUpdateServiceAccountKeyResponse(rsp)
+}
+
+func (c *ClientWithResponses) ServiceAccountsAPIUpdateServiceAccountKeyWithResponse(ctx context.Context, organizationId string, serviceAccountId string, keyId string, body ServiceAccountsAPIUpdateServiceAccountKeyJSONRequestBody) (*ServiceAccountsAPIUpdateServiceAccountKeyResponse, error) {
+ rsp, err := c.ServiceAccountsAPIUpdateServiceAccountKey(ctx, organizationId, serviceAccountId, keyId, body)
if err != nil {
return nil, err
}
@@ -15794,6 +15911,15 @@ func (c *ClientWithResponses) ServiceAccountsAPIDeleteServiceAccountKeyWithRespo
return ParseServiceAccountsAPIDeleteServiceAccountKeyResponse(rsp)
}
+// ServiceAccountsAPIGetServiceAccountKeyWithResponse request returning *ServiceAccountsAPIGetServiceAccountKeyResponse
+func (c *ClientWithResponses) ServiceAccountsAPIGetServiceAccountKeyWithResponse(ctx context.Context, organizationId string, serviceAccountId string, keyId string) (*ServiceAccountsAPIGetServiceAccountKeyResponse, error) {
+ rsp, err := c.ServiceAccountsAPIGetServiceAccountKey(ctx, organizationId, serviceAccountId, keyId)
+ if err != nil {
+ return nil, err
+ }
+ return ParseServiceAccountsAPIGetServiceAccountKeyResponse(rsp)
+}
+
// UsersAPIRemoveOrganizationUsersWithResponse request returning *UsersAPIRemoveOrganizationUsersResponse
func (c *ClientWithResponses) UsersAPIRemoveOrganizationUsersWithResponse(ctx context.Context, organizationId string, params *UsersAPIRemoveOrganizationUsersParams) (*UsersAPIRemoveOrganizationUsersResponse, error) {
rsp, err := c.UsersAPIRemoveOrganizationUsers(ctx, organizationId, params)
@@ -18767,15 +18893,8 @@ func ParseServiceAccountsAPICreateServiceAccountKeyResponse(rsp *http.Response)
}
switch {
- case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
- var dest CastaiServiceaccountsV1beta1CreateServiceAccountKeyResponse
- if err := json.Unmarshal(bodyBytes, &dest); err != nil {
- return nil, err
- }
- response.JSON200 = &dest
-
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201:
- var dest map[string]interface{}
+ var dest CastaiServiceaccountsV1beta1CreateServiceAccountKeyResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
@@ -18833,6 +18952,39 @@ func ParseServiceAccountsAPIDeleteServiceAccountKeyResponse(rsp *http.Response)
}
response.JSON200 = &dest
+ case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 204:
+ var dest map[string]interface{}
+ if err := json.Unmarshal(bodyBytes, &dest); err != nil {
+ return nil, err
+ }
+ response.JSON204 = &dest
+
+ }
+
+ return response, nil
+}
+
+// ParseServiceAccountsAPIGetServiceAccountKeyResponse parses an HTTP response from a ServiceAccountsAPIGetServiceAccountKeyWithResponse call
+func ParseServiceAccountsAPIGetServiceAccountKeyResponse(rsp *http.Response) (*ServiceAccountsAPIGetServiceAccountKeyResponse, error) {
+ bodyBytes, err := ioutil.ReadAll(rsp.Body)
+ defer rsp.Body.Close()
+ if err != nil {
+ return nil, err
+ }
+
+ response := &ServiceAccountsAPIGetServiceAccountKeyResponse{
+ Body: bodyBytes,
+ HTTPResponse: rsp,
+ }
+
+ switch {
+ case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
+ var dest CastaiServiceaccountsV1beta1GetServiceAccountKeyResponse
+ if err := json.Unmarshal(bodyBytes, &dest); err != nil {
+ return nil, err
+ }
+ response.JSON200 = &dest
+
}
return response, nil
diff --git a/castai/sdk/mock/client.go b/castai/sdk/mock/client.go
index 959a3c9a..02e52b5a 100644
--- a/castai/sdk/mock/client.go
+++ b/castai/sdk/mock/client.go
@@ -3055,6 +3055,26 @@ func (mr *MockClientInterfaceMockRecorder) ServiceAccountsAPIGetServiceAccount(c
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountsAPIGetServiceAccount", reflect.TypeOf((*MockClientInterface)(nil).ServiceAccountsAPIGetServiceAccount), varargs...)
}
+// ServiceAccountsAPIGetServiceAccountKey mocks base method.
+func (m *MockClientInterface) ServiceAccountsAPIGetServiceAccountKey(ctx context.Context, organizationId, serviceAccountId, keyId string, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) {
+ m.ctrl.T.Helper()
+ varargs := []interface{}{ctx, organizationId, serviceAccountId, keyId}
+ for _, a := range reqEditors {
+ varargs = append(varargs, a)
+ }
+ ret := m.ctrl.Call(m, "ServiceAccountsAPIGetServiceAccountKey", varargs...)
+ ret0, _ := ret[0].(*http.Response)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// ServiceAccountsAPIGetServiceAccountKey indicates an expected call of ServiceAccountsAPIGetServiceAccountKey.
+func (mr *MockClientInterfaceMockRecorder) ServiceAccountsAPIGetServiceAccountKey(ctx, organizationId, serviceAccountId, keyId interface{}, reqEditors ...interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ varargs := append([]interface{}{ctx, organizationId, serviceAccountId, keyId}, reqEditors...)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountsAPIGetServiceAccountKey", reflect.TypeOf((*MockClientInterface)(nil).ServiceAccountsAPIGetServiceAccountKey), varargs...)
+}
+
// ServiceAccountsAPIListServiceAccounts mocks base method.
func (m *MockClientInterface) ServiceAccountsAPIListServiceAccounts(ctx context.Context, organizationId string, params *sdk.ServiceAccountsAPIListServiceAccountsParams, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) {
m.ctrl.T.Helper()
@@ -3096,9 +3116,9 @@ func (mr *MockClientInterfaceMockRecorder) ServiceAccountsAPIUpdateServiceAccoun
}
// ServiceAccountsAPIUpdateServiceAccountKey mocks base method.
-func (m *MockClientInterface) ServiceAccountsAPIUpdateServiceAccountKey(ctx context.Context, organizationId, serviceAccountId, keyId string, params *sdk.ServiceAccountsAPIUpdateServiceAccountKeyParams, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) {
+func (m *MockClientInterface) ServiceAccountsAPIUpdateServiceAccountKey(ctx context.Context, organizationId, serviceAccountId, keyId string, body sdk.ServiceAccountsAPIUpdateServiceAccountKeyJSONRequestBody, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) {
m.ctrl.T.Helper()
- varargs := []interface{}{ctx, organizationId, serviceAccountId, keyId, params}
+ varargs := []interface{}{ctx, organizationId, serviceAccountId, keyId, body}
for _, a := range reqEditors {
varargs = append(varargs, a)
}
@@ -3109,12 +3129,32 @@ func (m *MockClientInterface) ServiceAccountsAPIUpdateServiceAccountKey(ctx cont
}
// ServiceAccountsAPIUpdateServiceAccountKey indicates an expected call of ServiceAccountsAPIUpdateServiceAccountKey.
-func (mr *MockClientInterfaceMockRecorder) ServiceAccountsAPIUpdateServiceAccountKey(ctx, organizationId, serviceAccountId, keyId, params interface{}, reqEditors ...interface{}) *gomock.Call {
+func (mr *MockClientInterfaceMockRecorder) ServiceAccountsAPIUpdateServiceAccountKey(ctx, organizationId, serviceAccountId, keyId, body interface{}, reqEditors ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
- varargs := append([]interface{}{ctx, organizationId, serviceAccountId, keyId, params}, reqEditors...)
+ varargs := append([]interface{}{ctx, organizationId, serviceAccountId, keyId, body}, reqEditors...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountsAPIUpdateServiceAccountKey", reflect.TypeOf((*MockClientInterface)(nil).ServiceAccountsAPIUpdateServiceAccountKey), varargs...)
}
+// ServiceAccountsAPIUpdateServiceAccountKeyWithBody mocks base method.
+func (m *MockClientInterface) ServiceAccountsAPIUpdateServiceAccountKeyWithBody(ctx context.Context, organizationId, serviceAccountId, keyId, contentType string, body io.Reader, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) {
+ m.ctrl.T.Helper()
+ varargs := []interface{}{ctx, organizationId, serviceAccountId, keyId, contentType, body}
+ for _, a := range reqEditors {
+ varargs = append(varargs, a)
+ }
+ ret := m.ctrl.Call(m, "ServiceAccountsAPIUpdateServiceAccountKeyWithBody", varargs...)
+ ret0, _ := ret[0].(*http.Response)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// ServiceAccountsAPIUpdateServiceAccountKeyWithBody indicates an expected call of ServiceAccountsAPIUpdateServiceAccountKeyWithBody.
+func (mr *MockClientInterfaceMockRecorder) ServiceAccountsAPIUpdateServiceAccountKeyWithBody(ctx, organizationId, serviceAccountId, keyId, contentType, body interface{}, reqEditors ...interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ varargs := append([]interface{}{ctx, organizationId, serviceAccountId, keyId, contentType, body}, reqEditors...)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountsAPIUpdateServiceAccountKeyWithBody", reflect.TypeOf((*MockClientInterface)(nil).ServiceAccountsAPIUpdateServiceAccountKeyWithBody), varargs...)
+}
+
// ServiceAccountsAPIUpdateServiceAccountWithBody mocks base method.
func (m *MockClientInterface) ServiceAccountsAPIUpdateServiceAccountWithBody(ctx context.Context, organizationId, serviceAccountId, contentType string, body io.Reader, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) {
m.ctrl.T.Helper()
@@ -6218,6 +6258,21 @@ func (mr *MockClientWithResponsesInterfaceMockRecorder) ServiceAccountsAPIDelete
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountsAPIDeleteServiceAccountWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).ServiceAccountsAPIDeleteServiceAccountWithResponse), ctx, organizationId, serviceAccountId)
}
+// ServiceAccountsAPIGetServiceAccountKeyWithResponse mocks base method.
+func (m *MockClientWithResponsesInterface) ServiceAccountsAPIGetServiceAccountKeyWithResponse(ctx context.Context, organizationId, serviceAccountId, keyId string) (*sdk.ServiceAccountsAPIGetServiceAccountKeyResponse, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "ServiceAccountsAPIGetServiceAccountKeyWithResponse", ctx, organizationId, serviceAccountId, keyId)
+ ret0, _ := ret[0].(*sdk.ServiceAccountsAPIGetServiceAccountKeyResponse)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// ServiceAccountsAPIGetServiceAccountKeyWithResponse indicates an expected call of ServiceAccountsAPIGetServiceAccountKeyWithResponse.
+func (mr *MockClientWithResponsesInterfaceMockRecorder) ServiceAccountsAPIGetServiceAccountKeyWithResponse(ctx, organizationId, serviceAccountId, keyId interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountsAPIGetServiceAccountKeyWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).ServiceAccountsAPIGetServiceAccountKeyWithResponse), ctx, organizationId, serviceAccountId, keyId)
+}
+
// ServiceAccountsAPIGetServiceAccountWithResponse mocks base method.
func (m *MockClientWithResponsesInterface) ServiceAccountsAPIGetServiceAccountWithResponse(ctx context.Context, organizationId, serviceAccountId string) (*sdk.ServiceAccountsAPIGetServiceAccountResponse, error) {
m.ctrl.T.Helper()
@@ -6248,19 +6303,34 @@ func (mr *MockClientWithResponsesInterfaceMockRecorder) ServiceAccountsAPIListSe
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountsAPIListServiceAccountsWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).ServiceAccountsAPIListServiceAccountsWithResponse), ctx, organizationId, params)
}
+// ServiceAccountsAPIUpdateServiceAccountKeyWithBodyWithResponse mocks base method.
+func (m *MockClientWithResponsesInterface) ServiceAccountsAPIUpdateServiceAccountKeyWithBodyWithResponse(ctx context.Context, organizationId, serviceAccountId, keyId, contentType string, body io.Reader) (*sdk.ServiceAccountsAPIUpdateServiceAccountKeyResponse, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "ServiceAccountsAPIUpdateServiceAccountKeyWithBodyWithResponse", ctx, organizationId, serviceAccountId, keyId, contentType, body)
+ ret0, _ := ret[0].(*sdk.ServiceAccountsAPIUpdateServiceAccountKeyResponse)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// ServiceAccountsAPIUpdateServiceAccountKeyWithBodyWithResponse indicates an expected call of ServiceAccountsAPIUpdateServiceAccountKeyWithBodyWithResponse.
+func (mr *MockClientWithResponsesInterfaceMockRecorder) ServiceAccountsAPIUpdateServiceAccountKeyWithBodyWithResponse(ctx, organizationId, serviceAccountId, keyId, contentType, body interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountsAPIUpdateServiceAccountKeyWithBodyWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).ServiceAccountsAPIUpdateServiceAccountKeyWithBodyWithResponse), ctx, organizationId, serviceAccountId, keyId, contentType, body)
+}
+
// ServiceAccountsAPIUpdateServiceAccountKeyWithResponse mocks base method.
-func (m *MockClientWithResponsesInterface) ServiceAccountsAPIUpdateServiceAccountKeyWithResponse(ctx context.Context, organizationId, serviceAccountId, keyId string, params *sdk.ServiceAccountsAPIUpdateServiceAccountKeyParams) (*sdk.ServiceAccountsAPIUpdateServiceAccountKeyResponse, error) {
+func (m *MockClientWithResponsesInterface) ServiceAccountsAPIUpdateServiceAccountKeyWithResponse(ctx context.Context, organizationId, serviceAccountId, keyId string, body sdk.ServiceAccountsAPIUpdateServiceAccountKeyJSONRequestBody) (*sdk.ServiceAccountsAPIUpdateServiceAccountKeyResponse, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "ServiceAccountsAPIUpdateServiceAccountKeyWithResponse", ctx, organizationId, serviceAccountId, keyId, params)
+ ret := m.ctrl.Call(m, "ServiceAccountsAPIUpdateServiceAccountKeyWithResponse", ctx, organizationId, serviceAccountId, keyId, body)
ret0, _ := ret[0].(*sdk.ServiceAccountsAPIUpdateServiceAccountKeyResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ServiceAccountsAPIUpdateServiceAccountKeyWithResponse indicates an expected call of ServiceAccountsAPIUpdateServiceAccountKeyWithResponse.
-func (mr *MockClientWithResponsesInterfaceMockRecorder) ServiceAccountsAPIUpdateServiceAccountKeyWithResponse(ctx, organizationId, serviceAccountId, keyId, params interface{}) *gomock.Call {
+func (mr *MockClientWithResponsesInterfaceMockRecorder) ServiceAccountsAPIUpdateServiceAccountKeyWithResponse(ctx, organizationId, serviceAccountId, keyId, body interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountsAPIUpdateServiceAccountKeyWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).ServiceAccountsAPIUpdateServiceAccountKeyWithResponse), ctx, organizationId, serviceAccountId, keyId, params)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountsAPIUpdateServiceAccountKeyWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).ServiceAccountsAPIUpdateServiceAccountKeyWithResponse), ctx, organizationId, serviceAccountId, keyId, body)
}
// ServiceAccountsAPIUpdateServiceAccountWithBodyWithResponse mocks base method.
diff --git a/castai/sdk/utils_response.go b/castai/sdk/utils_response.go
index bdcaf39c..66a043ba 100644
--- a/castai/sdk/utils_response.go
+++ b/castai/sdk/utils_response.go
@@ -3,6 +3,7 @@ package sdk
import (
"encoding/json"
"fmt"
+ "io"
"net/http"
)
@@ -18,8 +19,12 @@ func CheckResponseNoContent(response Response, err error) error {
return checkResponse(response, err, http.StatusNoContent)
}
+func CheckRawResponseNoContent(response *http.Response, err error) error {
+ return checkRawResponse(response, err, http.StatusNoContent)
+}
+
func CheckResponseCreated(response Response, err error) error {
- return checkResponse(response, err, http.StatusCreated)
+ return checkResponse(response, err, http.StatusCreated)
}
func StatusOk(resp Response) error {
@@ -38,6 +43,21 @@ func checkResponse(response Response, err error, expectedStatus int) error {
return nil
}
+func checkRawResponse(response *http.Response, err error, expectedStatus int) error {
+ if err != nil {
+ return err
+ }
+
+ if response.StatusCode != expectedStatus {
+ body, err := io.ReadAll(response.Body)
+ if err != nil {
+ return fmt.Errorf("reading response body: %w", err)
+ }
+ return fmt.Errorf("expected status code %d, received: status=%d body=%s", expectedStatus, response.StatusCode, body)
+ }
+ return nil
+}
+
type ErrorResponse struct {
Message string `json:"message"`
FieldViolations []struct {
diff --git a/castai/validation.go b/castai/validation.go
new file mode 100644
index 00000000..c577350d
--- /dev/null
+++ b/castai/validation.go
@@ -0,0 +1,28 @@
+package castai
+
+import (
+ "time"
+
+ "github.com/hashicorp/go-cty/cty"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+)
+
+func validateRFC3339TimeOrEmpty(i interface{}, path cty.Path) diag.Diagnostics {
+ v, ok := i.(string)
+ if !ok || v == "" {
+ return nil // Allow empty strings without error
+ }
+
+ _, err := time.Parse(time.RFC3339, v)
+ if err != nil {
+ return diag.Diagnostics{
+ diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Invalid expires_at format",
+ Detail: "The expires_at field must be in RFC3339 format or an empty string.",
+ },
+ }
+ }
+
+ return nil
+}
diff --git a/docs/resources/service_account.md b/docs/resources/service_account.md
index c83f36f8..81f7c160 100644
--- a/docs/resources/service_account.md
+++ b/docs/resources/service_account.md
@@ -3,21 +3,33 @@
page_title: "castai_service_account Resource - terraform-provider-castai"
subcategory: ""
description: |-
- Service Account resource allows managing CAST AI service accounts.
+ Service account resource allows managing CAST AI service accounts.
---
# castai_service_account (Resource)
-Service Account resource allows managing CAST AI service accounts.
+Service account resource allows managing CAST AI service accounts.
## Example Usage
```terraform
resource "castai_service_account" "service_account" {
organization_id = organization.id
- name = "service-account-name"
+ name = "example-service-account"
description = "service account description"
}
+
+resource "castai_service_account_key" "service_account_key" {
+ organization_id = data.castai_organization.test.id
+ service_account_id = castai_service_account.service_account.id
+ name = "example-key"
+ active = true
+ expires_at = "2025-01-01T00:00:00Z"
+}
+
+output "service_account_key" {
+ value = castai_service_account_key.service_account_key.token
+}
```
@@ -46,6 +58,7 @@ Optional:
- `create` (String)
- `delete` (String)
+- `read` (String)
- `update` (String)
diff --git a/docs/resources/service_account_key.md b/docs/resources/service_account_key.md
new file mode 100644
index 00000000..d9b3ae37
--- /dev/null
+++ b/docs/resources/service_account_key.md
@@ -0,0 +1,47 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "castai_service_account_key Resource - terraform-provider-castai"
+subcategory: ""
+description: |-
+ Service account key resource allows managing CAST AI service account keys.
+---
+
+# castai_service_account_key (Resource)
+
+Service account key resource allows managing CAST AI service account keys.
+
+
+
+
+## Schema
+
+### Required
+
+- `name` (String) Name of the service account key.
+- `organization_id` (String) ID of the organization.
+- `service_account_id` (String) ID of the service account.
+
+### Optional
+
+- `active` (Boolean) Whether the service account key is active. Defaults to true.
+- `expires_at` (String) The expiration time of the service account key in RFC3339 format. Defaults to an empty string.
+- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+- `last_used_at` (String) Last time the service account key was used.
+- `prefix` (String) Prefix of the service account key.
+- `token` (String) The token of the service account key used for authentication.
+
+
+### Nested Schema for `timeouts`
+
+Optional:
+
+- `create` (String)
+- `delete` (String)
+- `read` (String)
+- `update` (String)
+
+
diff --git a/examples/resources/castai_service_account/resource.tf b/examples/resources/castai_service_account/resource.tf
index 5caf87f5..97c72248 100644
--- a/examples/resources/castai_service_account/resource.tf
+++ b/examples/resources/castai_service_account/resource.tf
@@ -1,5 +1,17 @@
resource "castai_service_account" "service_account" {
organization_id = organization.id
- name = "service-account-name"
+ name = "example-service-account"
description = "service account description"
}
+
+resource "castai_service_account_key" "service_account_key" {
+ organization_id = data.castai_organization.test.id
+ service_account_id = castai_service_account.service_account.id
+ name = "example-key"
+ active = true
+ expires_at = "2025-01-01T00:00:00Z"
+}
+
+output "service_account_key" {
+ value = castai_service_account_key.service_account_key.token
+}