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 +}