From 6555c26c1d07dce5edcfd012cff8d0f73e0a7bf6 Mon Sep 17 00:00:00 2001 From: Jakub Burghardt Date: Mon, 6 Nov 2023 10:03:28 +0100 Subject: [PATCH 1/2] add sso_connection resource --- Makefile | 4 +- castai/provider.go | 1 + castai/resource_sso_connection.go | 309 ++++++++ castai/resource_sso_connection_test.go | 518 ++++++++++++++ castai/sdk/api.gen.go | 125 ++++ castai/sdk/client.gen.go | 667 +++++++++++++++++- castai/sdk/mock/client.go | 245 +++++++ docs/resources/sso_connection.md | 71 ++ examples/resources/sso_connection/resource.tf | 9 + examples/sso_connection/README.md | 12 + examples/sso_connection/azure.tf | 44 ++ examples/sso_connection/main.tf | 23 + examples/sso_connection/providers.tf | 8 + examples/sso_connection/tf.vars.example | 2 + examples/sso_connection/variables.tf | 10 + examples/sso_connection/versions.tf | 16 + templates/resources/sso_connection.md.tmpl | 16 + 17 files changed, 2067 insertions(+), 13 deletions(-) create mode 100644 castai/resource_sso_connection.go create mode 100644 castai/resource_sso_connection_test.go create mode 100644 docs/resources/sso_connection.md create mode 100644 examples/resources/sso_connection/resource.tf create mode 100644 examples/sso_connection/README.md create mode 100644 examples/sso_connection/azure.tf create mode 100644 examples/sso_connection/main.tf create mode 100644 examples/sso_connection/providers.tf create mode 100644 examples/sso_connection/tf.vars.example create mode 100644 examples/sso_connection/variables.tf create mode 100644 examples/sso_connection/versions.tf create mode 100644 templates/resources/sso_connection.md.tmpl diff --git a/Makefile b/Makefile index 55518194..ec3c3926 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ init-examples: generate-sdk: @echo "==> Generating castai sdk client" - @API_TAGS=ExternalClusterAPI,PoliciesAPI,NodeConfigurationAPI,NodeTemplatesAPI,AuthTokenAPI,ScheduledRebalancingAPI,InventoryAPI,UsersAPI,OperationsAPI,EvictorAPI go generate castai/sdk/generate.go + @API_TAGS=ExternalClusterAPI,PoliciesAPI,NodeConfigurationAPI,NodeTemplatesAPI,AuthTokenAPI,ScheduledRebalancingAPI,InventoryAPI,UsersAPI,OperationsAPI,EvictorAPI,SSOAPI go generate castai/sdk/generate.go # The following command also rewrites existing documentation generate-docs: @@ -48,4 +48,4 @@ validate-terraform-examples: terraform validate; \ cd -; \ done \ - done \ No newline at end of file + done diff --git a/castai/provider.go b/castai/provider.go index 5c807b46..a31b6e22 100644 --- a/castai/provider.go +++ b/castai/provider.go @@ -48,6 +48,7 @@ func Provider(version string) *schema.Provider { "castai_eks_user_arn": resourceEKSClusterUserARN(), "castai_reservations": resourceReservations(), "castai_organization_members": resourceOrganizationMembers(), + "castai_sso_connection": resourceSSOConnection(), }, DataSourcesMap: map[string]*schema.Resource{ diff --git a/castai/resource_sso_connection.go b/castai/resource_sso_connection.go new file mode 100644 index 00000000..30f9fece --- /dev/null +++ b/castai/resource_sso_connection.go @@ -0,0 +1,309 @@ +package castai + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + "time" + + "github.com/castai/terraform-provider-castai/castai/sdk" + "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" + "golang.org/x/crypto/bcrypt" +) + +const ( + FieldSSOConnectionName = "name" + FieldSSOConnectionEmailDomain = "email_domain" + + FieldSSOConnectionAAD = "aad" + FieldSSOConnectionADDomain = "ad_domain" + FieldSSOConnectionADClientID = "client_id" + FieldSSOConnectionADClientSecret = "client_secret" + + FieldSSOConnectionOkta = "okta" + FieldSSOConnectionOktaDomain = "okta_domain" + FieldSSOConnectionOktaClientID = "client_id" + FieldSSOConnectionOktaClientSecret = "client_secret" +) + +func resourceSSOConnection() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceCastaiSSOConnectionCreate, + ReadContext: resourceCastaiSSOConnectionRead, + UpdateContext: resourceCastaiSSOConnectionUpdate, + DeleteContext: resourceCastaiSSOConnectionDelete, + CustomizeDiff: resourceCastaiSSOConnectionDiff, + Description: "SSO Connection resource allows creating SSO trust relationship with CAST AI.", + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(3 * time.Minute), + Update: schema.DefaultTimeout(3 * time.Minute), + Delete: schema.DefaultTimeout(3 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + FieldSSOConnectionName: { + Type: schema.TypeString, + Required: true, + Description: "Connection name", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), + }, + FieldSSOConnectionEmailDomain: { + Type: schema.TypeString, + Required: true, + Description: "Email domain of the connection", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), + }, + FieldSSOConnectionAAD: { + Type: schema.TypeList, + MaxItems: 1, + MinItems: 1, + Optional: true, + Description: "Azure AD connector", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + FieldSSOConnectionADDomain: { + Type: schema.TypeString, + Required: true, + Description: "Azure AD domain", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), + }, + FieldSSOConnectionADClientID: { + Type: schema.TypeString, + Required: true, + Description: "Azure AD client ID", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), + }, + FieldSSOConnectionADClientSecret: { + Type: schema.TypeString, + Sensitive: true, + Required: true, + Description: "Azure AD client secret", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), + DiffSuppressFunc: func(_, oldValue, newValue string, _ *schema.ResourceData) bool { + decodedSecret, err := base64.StdEncoding.DecodeString(oldValue) + if err != nil { + return false + } + return bcrypt.CompareHashAndPassword(decodedSecret, []byte(newValue)) == nil + }, + }, + }, + }, + }, + FieldSSOConnectionOkta: { + Type: schema.TypeList, + MaxItems: 1, + MinItems: 1, + Optional: true, + Description: "Okta connector", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + FieldSSOConnectionOktaDomain: { + Type: schema.TypeString, + Required: true, + Description: "Okta domain", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), + }, + FieldSSOConnectionOktaClientID: { + Type: schema.TypeString, + Required: true, + Description: "Okta client ID", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), + }, + FieldSSOConnectionOktaClientSecret: { + Type: schema.TypeString, + Required: true, + Sensitive: true, + Description: "Okta client secret", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), + DiffSuppressFunc: func(_, oldValue, newValue string, _ *schema.ResourceData) bool { + decodedSecret, err := base64.StdEncoding.DecodeString(oldValue) + if err != nil { + return false + } + return bcrypt.CompareHashAndPassword(decodedSecret, []byte(newValue)) == nil + }, + }, + }, + }, + }, + }, + } +} + +func resourceCastaiSSOConnectionCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*ProviderConfig).api + + req := sdk.CastaiSsoV1beta1CreateSSOConnection{ + Name: data.Get(FieldSSOConnectionName).(string), + EmailDomain: data.Get(FieldSSOConnectionEmailDomain).(string), + } + + if v, ok := data.Get(FieldSSOConnectionAAD).([]any); ok && len(v) > 0 { + req.Aad = toADConnector(v[0].(map[string]any)) + } + + if v, ok := data.Get(FieldSSOConnectionOkta).([]any); ok && len(v) > 0 { + req.Okta = toOktaConnector(v[0].(map[string]any)) + } + + resp, err := client.SSOAPICreateSSOConnectionWithResponse(ctx, req) + if err := sdk.CheckOKResponse(resp, err); err != nil { + return diag.Errorf("creating sso connection: %v", err) + } + + if err := checkSSOStatus(resp.JSON200); err != nil { + return diag.FromErr(err) + } + + data.SetId(*resp.JSON200.Id) + + return resourceCastaiSSOConnectionRead(ctx, data, meta) +} + +func resourceCastaiSSOConnectionRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + if data.Id() == "" { + return nil + } + + client := meta.(*ProviderConfig).api + resp, err := client.SSOAPIGetSSOConnectionWithResponse(ctx, data.Id()) + if err := sdk.CheckOKResponse(resp, err); err != nil { + return diag.Errorf("retrieving sso connection: %v", err) + } + + connection := resp.JSON200 + + if err := data.Set(FieldSSOConnectionName, connection.Name); err != nil { + return diag.Errorf("setting connection name: %v", err) + } + if err := data.Set(FieldSSOConnectionEmailDomain, connection.EmailDomain); err != nil { + return diag.Errorf("setting email domain: %v", err) + } + + return nil +} + +func resourceCastaiSSOConnectionUpdate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + if !data.HasChanges( + FieldSSOConnectionName, + FieldSSOConnectionEmailDomain, + FieldSSOConnectionAAD, + FieldSSOConnectionOkta, + ) { + return nil + } + + client := meta.(*ProviderConfig).api + req := sdk.CastaiSsoV1beta1UpdateSSOConnection{} + + if v, ok := data.GetOk(FieldSSOConnectionName); ok { + req.Name = toPtr(v.(string)) + } + if v, ok := data.GetOk(FieldSSOConnectionEmailDomain); ok { + req.EmailDomain = toPtr(v.(string)) + } + + if v, ok := data.Get(FieldSSOConnectionAAD).([]any); ok && len(v) > 0 { + req.Aad = toADConnector(v[0].(map[string]any)) + } + + if v, ok := data.Get(FieldSSOConnectionOkta).([]any); ok && len(v) > 0 { + req.Okta = toOktaConnector(v[0].(map[string]any)) + } + + resp, err := client.SSOAPIUpdateSSOConnectionWithResponse(ctx, data.Id(), req) + if err := sdk.CheckOKResponse(resp, err); err != nil { + return diag.Errorf("updating sso connection: %v", err) + } + + if err := checkSSOStatus(resp.JSON200); err != nil { + return diag.FromErr(err) + } + + return resourceCastaiSSOConnectionRead(ctx, data, meta) +} + +func resourceCastaiSSOConnectionDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*ProviderConfig).api + + resp, err := client.SSOAPIDeleteSSOConnectionWithResponse(ctx, data.Id()) + if err := sdk.CheckOKResponse(resp, err); err != nil { + return diag.Errorf("deleting sso connection: %v", err) + } + + return nil +} + +func checkSSOStatus(input *sdk.CastaiSsoV1beta1SSOConnection) error { + if input == nil && input.Status == nil { + return nil + } + + if *input.Status == sdk.STATUSACTIVE { + return nil + } + + if input.Error == nil { + return fmt.Errorf("invalid SSO connection status: %s", *input.Status) + } + + return fmt.Errorf("SSO connection status: %s failed with error: %s", *input.Status, *input.Error) +} + +func resourceCastaiSSOConnectionDiff(_ context.Context, rd *schema.ResourceDiff, _ interface{}) error { + connectors := 0 + if v, ok := rd.Get(FieldSSOConnectionAAD).([]any); ok && len(v) > 0 { + connectors++ + } + + if v, ok := rd.Get(FieldSSOConnectionOkta).([]any); ok && len(v) > 0 { + connectors++ + } + + if connectors != 1 { + return errors.New("only 1 connector can be configured") + } + + return nil +} + +func toADConnector(obj map[string]any) *sdk.CastaiSsoV1beta1AzureAAD { + if obj == nil { + return nil + } + + out := &sdk.CastaiSsoV1beta1AzureAAD{} + if v, ok := obj[FieldSSOConnectionADDomain].(string); ok { + out.AdDomain = v + } + if v, ok := obj[FieldSSOConnectionADClientID].(string); ok { + out.ClientId = v + } + if v, ok := obj[FieldSSOConnectionADClientSecret].(string); ok { + out.ClientSecret = toPtr(v) + } + + return out +} + +func toOktaConnector(obj map[string]any) *sdk.CastaiSsoV1beta1Okta { + if obj == nil { + return nil + } + + out := &sdk.CastaiSsoV1beta1Okta{} + if v, ok := obj[FieldSSOConnectionOktaDomain].(string); ok { + out.OktaDomain = v + } + if v, ok := obj[FieldSSOConnectionOktaClientID].(string); ok { + out.ClientId = v + } + if v, ok := obj[FieldSSOConnectionOktaClientSecret].(string); ok { + out.ClientSecret = toPtr(v) + } + + return out +} diff --git a/castai/resource_sso_connection_test.go b/castai/resource_sso_connection_test.go new file mode 100644 index 00000000..81ca46cf --- /dev/null +++ b/castai/resource_sso_connection_test.go @@ -0,0 +1,518 @@ +package castai + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "testing" + "time" + + "github.com/castai/terraform-provider-castai/castai/sdk" + mock_sdk "github.com/castai/terraform-provider-castai/castai/sdk/mock" + "github.com/golang/mock/gomock" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/stretchr/testify/require" +) + +func TestAccResourceSSOConnection(t *testing.T) { + rName := fmt.Sprintf("%v-sso-connection-%v", ResourcePrefix, acctest.RandString(8)) + resourceName := "castai_sso_connection.test" + + clientID := os.Getenv("SSO_CLIENT_ID") + clientSecret := os.Getenv("SSO_CLIENT_SECRET") + domain := os.Getenv("SSO_DOMAIN") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccSSOConnectionConfigurationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCreateSSOConnectionConfig(rName, clientID, clientSecret, domain), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "email_domain", "aad_connection@test.com"), + resource.TestCheckResourceAttrSet(resourceName, "aad.0.client_id"), + resource.TestCheckResourceAttrSet(resourceName, "aad.0.client_secret"), + resource.TestCheckResourceAttrSet(resourceName, "aad.0.ad_domain"), + ), + }, + }, + }) +} + +func TestSSOConnection_ReadContext(t *testing.T) { + readBody := `{"id":"fce35ba2-5c06-4078-8391-1ac8f7ba798b","name":"test_sso","createdAt":"2023-11-02T10:49:14.376757Z","updatedAt":"2023-11-02T10:49:14.450828Z","emailDomain":"test_email","aad":{"adDomain":"test_connector","clientId":"test_client","clientSecret":"test_secret"}}` + + mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t)) + + connectionID := "fce35ba2-5c06-4078-8391-1ac8f7ba798b" + + mockClient.EXPECT(). + SSOAPIGetSSOConnection(gomock.Any(), connectionID). + Return(&http.Response{StatusCode: 200, Body: io.NopCloser(bytes.NewReader([]byte(readBody))), Header: map[string][]string{"Content-Type": {"json"}}}, nil) + + resource := resourceSSOConnection() + data := resource.Data( + terraform.NewInstanceStateShimmedFromValue(cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal(connectionID), + }), 0)) + + result := resource.ReadContext(context.Background(), data, &ProviderConfig{ + api: &sdk.ClientWithResponses{ + ClientInterface: mockClient, + }, + }) + + r := require.New(t) + r.Nil(result) + r.False(result.HasError()) + r.Equal("test_sso", data.Get(FieldSSOConnectionName)) + r.Equal("test_email", data.Get(FieldSSOConnectionEmailDomain)) +} + +func TestSSOConnection_CreateADDConnector(t *testing.T) { + r := require.New(t) + mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t)) + + mockClient.EXPECT(). + SSOAPICreateSSOConnection(gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, body sdk.SSOAPICreateSSOConnectionJSONBody) (*http.Response, error) { + got, err := json.Marshal(body) + r.NoError(err) + + expected := []byte(`{ + "aad": { + "adDomain": "test_connector", + "clientId": "test_client", + "clientSecret": "test_secret" + }, + "emailDomain": "test_email", + "name": "test_sso" +} +`) + + equal, err := JSONBytesEqual(got, expected) + r.NoError(err) + r.True(equal, fmt.Sprintf("got: %v\n"+ + "expected: %v\n", string(got), string(expected))) + + return &http.Response{ + StatusCode: 200, + Header: map[string][]string{"Content-Type": {"json"}}, + Body: io.NopCloser(bytes.NewReader([]byte(`{"id": "b6bfc074-a267-400f-b8f1-db0850c369b1", "status": "STATUS_ACTIVE"}`))), + }, nil + }) + + connectionID := "b6bfc074-a267-400f-b8f1-db0850c369b1" + readBody := io.NopCloser(bytes.NewReader([]byte(`{ + "id": "b6bfc074-a267-400f-b8f1-db0850c369b1", + "name": "test_sso", + "createdAt": "2023-11-02T10:49:14.376757Z", + "updatedAt": "2023-11-02T10:49:14.450828Z", + "emailDomain": "test_email", + "aad": { + "adDomain": "test_connector", + "clientId": "test_client", + "clientSecret": "test_secret" + } +}`))) + + mockClient.EXPECT(). + SSOAPIGetSSOConnection(gomock.Any(), connectionID). + Return(&http.Response{StatusCode: 200, Body: readBody, Header: map[string][]string{"Content-Type": {"json"}}}, nil) + + resource := resourceSSOConnection() + data := resource.Data(terraform.NewInstanceStateShimmedFromValue(cty.ObjectVal(map[string]cty.Value{ + FieldSSOConnectionName: cty.StringVal("test_sso"), + FieldSSOConnectionEmailDomain: cty.StringVal("test_email"), + FieldSSOConnectionAAD: cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + FieldSSOConnectionADDomain: cty.StringVal("test_connector"), + FieldSSOConnectionADClientID: cty.StringVal("test_client"), + FieldSSOConnectionADClientSecret: cty.StringVal("test_secret"), + }), + }), + }), 0)) + + result := resource.CreateContext(context.Background(), data, &ProviderConfig{ + api: &sdk.ClientWithResponses{ + ClientInterface: mockClient, + }, + }) + + r.Nil(result) + r.False(result.HasError()) + r.Equal("test_sso", data.Get(FieldSSOConnectionName)) + r.Equal("test_email", data.Get(FieldSSOConnectionEmailDomain)) + equalADConnector(t, r, data.Get(FieldSSOConnectionAAD), "test_connector", "test_client", "test_secret") +} + +func TestSSOConnection_CreateOktaConnector(t *testing.T) { + r := require.New(t) + mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t)) + + mockClient.EXPECT(). + SSOAPICreateSSOConnection(gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, body sdk.SSOAPICreateSSOConnectionJSONBody) (*http.Response, error) { + got, err := json.Marshal(body) + r.NoError(err) + + expected := []byte(`{ + "okta": { + "oktaDomain": "test_connector", + "clientId": "test_client", + "clientSecret": "test_secret" + }, + "emailDomain": "test_email", + "name": "test_sso" +}`) + + equal, err := JSONBytesEqual(got, expected) + r.NoError(err) + r.True(equal, fmt.Sprintf("got: %v\n"+ + "expected: %v\n", string(got), string(expected))) + + return &http.Response{ + StatusCode: 200, + Header: map[string][]string{"Content-Type": {"json"}}, + Body: io.NopCloser(bytes.NewReader([]byte(`{"id": "b6bfc074-a267-400f-b8f1-db0850c369b1", "status": "STATUS_ACTIVE"}`))), + }, nil + }) + + connectionID := "b6bfc074-a267-400f-b8f1-db0850c369b1" + readBody := io.NopCloser(bytes.NewReader([]byte(`{ + "id": "b6bfc074-a267-400f-b8f1-db0850c369b1", + "name": "test_sso", + "createdAt": "2023-11-02T10:49:14.376757Z", + "updatedAt": "2023-11-02T10:49:14.450828Z", + "emailDomain": "test_email", + "okta": { + "oktaDomain": "test_connector", + "clientId": "test_client", + "clientSecret": "test_secret" + } +}`))) + + mockClient.EXPECT(). + SSOAPIGetSSOConnection(gomock.Any(), connectionID). + Return(&http.Response{StatusCode: 200, Body: readBody, Header: map[string][]string{"Content-Type": {"json"}}}, nil) + + resource := resourceSSOConnection() + data := resource.Data(terraform.NewInstanceStateShimmedFromValue(cty.ObjectVal(map[string]cty.Value{ + FieldSSOConnectionName: cty.StringVal("test_sso"), + FieldSSOConnectionEmailDomain: cty.StringVal("test_email"), + FieldSSOConnectionOkta: cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + FieldSSOConnectionOktaDomain: cty.StringVal("test_connector"), + FieldSSOConnectionOktaClientID: cty.StringVal("test_client"), + FieldSSOConnectionOktaClientSecret: cty.StringVal("test_secret"), + }), + }), + }), 0)) + + result := resource.CreateContext(context.Background(), data, &ProviderConfig{ + api: &sdk.ClientWithResponses{ + ClientInterface: mockClient, + }, + }) + + r.Nil(result) + r.False(result.HasError()) + r.Equal("test_sso", data.Get(FieldSSOConnectionName)) + r.Equal("test_email", data.Get(FieldSSOConnectionEmailDomain)) + equalOktaConnector(t, r, data.Get(FieldSSOConnectionOkta), "test_connector", "test_client", "test_secret") +} + +func TestSSOConnection_UpdateADDConnector(t *testing.T) { + r := require.New(t) + mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t)) + + ctx := context.Background() + provider := &ProviderConfig{ + api: &sdk.ClientWithResponses{ + ClientInterface: mockClient, + }, + } + connectionID := "b6bfc074-a267-400f-b8f1-db0850c369b1" + + raw := make(map[string]interface{}) + raw[FieldSSOConnectionName] = "updated_name" + + resource := resourceSSOConnection() + data := schema.TestResourceDataRaw(t, resource.Schema, raw) + data.SetId(connectionID) + r.NoError(data.Set(FieldSSOConnectionAAD, []map[string]interface{}{ + { + FieldSSOConnectionADDomain: "updated_domain", + FieldSSOConnectionADClientID: "updated_client_id", + FieldSSOConnectionADClientSecret: "updated_client_secret", + }, + })) + + mockClient.EXPECT().SSOAPIUpdateSSOConnection(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, _ string, body sdk.SSOAPIUpdateSSOConnectionJSONBody) (*http.Response, error) { + got, err := json.Marshal(body) + r.NoError(err) + + expected := []byte(`{ + "aad": { + "adDomain": "updated_domain", + "clientId": "updated_client_id", + "clientSecret": "updated_client_secret" + }, + "name": "updated_name" +}`) + + eq, err := JSONBytesEqual(got, expected) + r.NoError(err) + r.True(eq, fmt.Sprintf("got: %v\n"+ + "expected: %v\n", string(got), string(expected))) + + returnBody := []byte(`{ + "aad": { + "adDomain": "updated_domain", + "clientId": "updated_client_id", + "clientSecret": "updated_client_secret" + }, + "status": "STATUS_ACTIVE", + "name": "updated_name" +}`) + + return &http.Response{ + StatusCode: 200, + Header: map[string][]string{"Content-Type": {"json"}}, + Body: io.NopCloser(bytes.NewReader(returnBody)), + }, nil + }).Times(1) + + readBody := io.NopCloser(bytes.NewReader([]byte(`{ + "id": "b6bfc074-a267-400f-b8f1-db0850c369b1", + "name": "updated_name", + "createdAt": "2023-11-02T10:49:14.376757Z", + "updatedAt": "2023-11-02T10:49:14.450828Z", + "emailDomain": "test_email", + "aad": { + "adDomain": "updated_domain", + "clientId": "updated_client_id", + "clientSecret": "updated_client_secret" + } +}`))) + mockClient.EXPECT(). + SSOAPIGetSSOConnection(gomock.Any(), connectionID). + Return(&http.Response{StatusCode: 200, Body: readBody, Header: map[string][]string{"Content-Type": {"json"}}}, nil) + + updateResult := resource.UpdateContext(ctx, data, provider) + + r.Nil(updateResult) + r.False(updateResult.HasError()) + r.Equal("updated_name", data.Get(FieldSSOConnectionName)) + equalADConnector(t, r, data.Get(FieldSSOConnectionAAD), "updated_domain", "updated_client_id", "updated_client_secret") +} + +func TestSSOConnection_UpdateOktaConnector(t *testing.T) { + r := require.New(t) + mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t)) + + ctx := context.Background() + provider := &ProviderConfig{ + api: &sdk.ClientWithResponses{ + ClientInterface: mockClient, + }, + } + connectionID := "b6bfc074-a267-400f-b8f1-db0850c369b1" + + raw := make(map[string]interface{}) + raw[FieldSSOConnectionName] = "updated_name" + + resource := resourceSSOConnection() + data := schema.TestResourceDataRaw(t, resource.Schema, raw) + data.SetId(connectionID) + r.NoError(data.Set(FieldSSOConnectionOkta, []map[string]interface{}{ + { + FieldSSOConnectionOktaDomain: "updated_domain", + FieldSSOConnectionOktaClientID: "updated_client_id", + FieldSSOConnectionOktaClientSecret: "updated_client_secret", + }, + })) + + mockClient.EXPECT().SSOAPIUpdateSSOConnection(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, _ string, body sdk.SSOAPIUpdateSSOConnectionJSONBody) (*http.Response, error) { + got, err := json.Marshal(body) + r.NoError(err) + + expected := []byte(`{ + "okta": { + "oktaDomain": "updated_domain", + "clientId": "updated_client_id", + "clientSecret": "updated_client_secret" + }, + "name": "updated_name" +}`) + + eq, err := JSONBytesEqual(got, expected) + r.NoError(err) + r.True(eq, fmt.Sprintf("got: %v\n"+ + "expected: %v\n", string(got), string(expected))) + + returnBody := []byte(`{ + "okta": { + "oktaDomain": "updated_domain", + "clientId": "updated_client_id", + "clientSecret": "updated_client_secret" + }, + "status": "STATUS_ACTIVE", + "name": "updated_name" +}`) + + return &http.Response{ + StatusCode: 200, + Header: map[string][]string{"Content-Type": {"json"}}, + Body: io.NopCloser(bytes.NewReader(returnBody)), + }, nil + }).Times(1) + + readBody := io.NopCloser(bytes.NewReader([]byte(`{ + "id": "b6bfc074-a267-400f-b8f1-db0850c369b1", + "name": "updated_name", + "createdAt": "2023-11-02T10:49:14.376757Z", + "updatedAt": "2023-11-02T10:49:14.450828Z", + "emailDomain": "test_email", + "okta": { + "oktaDomain": "updated_domain", + "clientId": "updated_client_id", + "clientSecret": "updated_client_secret" + } +}`))) + mockClient.EXPECT(). + SSOAPIGetSSOConnection(gomock.Any(), connectionID). + Return(&http.Response{StatusCode: 200, Body: readBody, Header: map[string][]string{"Content-Type": {"json"}}}, nil) + + updateResult := resource.UpdateContext(ctx, data, provider) + r.Nil(updateResult) + r.False(updateResult.HasError()) + r.Equal("updated_name", data.Get(FieldSSOConnectionName)) + equalOktaConnector(t, r, data.Get(FieldSSOConnectionOkta), "updated_domain", "updated_client_id", "updated_client_secret") +} + +func TestSSOConnection_DeleteContext(t *testing.T) { + r := require.New(t) + mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t)) + + provider := &ProviderConfig{ + api: &sdk.ClientWithResponses{ + ClientInterface: mockClient, + }, + } + + connectionID := "b6bfc074-a267-400f-b8f1-db0850c369b1" + + resource := resourceSSOConnection() + data := resource.Data( + terraform.NewInstanceStateShimmedFromValue( + cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal(connectionID), + }), 0), + ) + + mockClient.EXPECT(). + SSOAPIDeleteSSOConnection(gomock.Any(), connectionID). + Return(&http.Response{StatusCode: 200, Body: io.NopCloser(bytes.NewReader([]byte(`{}`))), Header: map[string][]string{"Content-Type": {"json"}}}, nil) + + ctx := context.Background() + result := resource.DeleteContext(ctx, data, provider) + r.Nil(result) + r.False(result.HasError()) + r.Empty(data.Get(FieldSSOConnectionOkta)) + r.Empty(data.Get(FieldSSOConnectionAAD)) + r.Empty(data.Get(FieldSSOConnectionName)) + r.Empty(data.Get(FieldSSOConnectionEmailDomain)) +} + +func testAccCreateSSOConnectionConfig(rName, clientID, clientSecret, adDomain string) string { + return fmt.Sprintf(` +resource "castai_sso_connection" "test" { + name = %[1]q + email_domain = "aad_connection@test.com" + aad { + client_id = %[2]q + client_secret = %[3]q + ad_domain = %[4]q + } +}`, rName, clientID, clientSecret, adDomain) +} + +func testAccSSOConnectionConfigurationDestroy(s *terraform.State) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + client := testAccProvider.Meta().(*ProviderConfig).api + for _, rs := range s.RootModule().Resources { + if rs.Type != "castai_sso_connection" { + continue + } + + response, err := client.SSOAPIGetSSOConnectionWithResponse(ctx, rs.Primary.ID) + if err != nil { + return err + } + + if response.StatusCode() == http.StatusNotFound { + return nil + } + + return fmt.Errorf("sso connection %s still exists", rs.Primary.ID) + } + + return nil +} + +func equalOktaConnector(t *testing.T, r *require.Assertions, in interface{}, expectedDomain, expectedClientID, expectedClientSecret string) { + t.Helper() + r.NotNil(in) + + array, ok := in.([]interface{}) + r.True(ok) + r.Len(array, 1) + values, ok := array[0].(map[string]interface{}) + r.True(ok) + + domain, ok := values["okta_domain"] + r.True(ok) + r.Equal(expectedDomain, domain) + clientID, ok := values["client_id"] + r.True(ok) + r.Equal(expectedClientID, clientID) + clientSecret, ok := values["client_secret"] + r.True(ok) + r.Equal(expectedClientSecret, clientSecret) +} + +func equalADConnector(t *testing.T, r *require.Assertions, in interface{}, expectedDomain, expectedClientID, expectedClientSecret string) { + t.Helper() + r.NotNil(in) + + array, ok := in.([]interface{}) + r.True(ok) + r.Len(array, 1) + values, ok := array[0].(map[string]interface{}) + r.True(ok) + + domain, ok := values["ad_domain"] + r.True(ok) + r.Equal(expectedDomain, domain) + clientID, ok := values["client_id"] + r.True(ok) + r.Equal(expectedClientID, clientID) + clientSecret, ok := values["client_secret"] + r.True(ok) + r.Equal(expectedClientSecret, clientSecret) +} diff --git a/castai/sdk/api.gen.go b/castai/sdk/api.gen.go index 421eeaea..f67c9075 100644 --- a/castai/sdk/api.gen.go +++ b/castai/sdk/api.gen.go @@ -60,6 +60,14 @@ const ( CastaiInventoryV1beta1StorageInfoDeviceTypeSsd CastaiInventoryV1beta1StorageInfoDeviceType = "ssd" ) +// Defines values for CastaiSsoV1beta1SSOConnectionStatus. +const ( + STATUSACTIVE CastaiSsoV1beta1SSOConnectionStatus = "STATUS_ACTIVE" + STATUSFAILED CastaiSsoV1beta1SSOConnectionStatus = "STATUS_FAILED" + STATUSINACTIVE CastaiSsoV1beta1SSOConnectionStatus = "STATUS_INACTIVE" + STATUSUNKNOWN CastaiSsoV1beta1SSOConnectionStatus = "STATUS_UNKNOWN" +) + // Defines values for CastaiV1Cloud. const ( CastaiV1CloudAWS CastaiV1Cloud = "AWS" @@ -639,6 +647,111 @@ type CastaiOperationsV1beta1OperationError struct { Reason *string `json:"reason,omitempty"` } +// AzureAAD represents a Azure AAD connector. +type CastaiSsoV1beta1AzureAAD struct { + // ADDomain is the domain of the Azure AD. + AdDomain string `json:"adDomain"` + + // ClientId is the client id of the Azure AD. + ClientId string `json:"clientId"` + + // ClientSecret is the client secret of the Azure AD. + ClientSecret *string `json:"clientSecret,omitempty"` +} + +// CreateSSOConnection represents a sso connection creation request. +type CastaiSsoV1beta1CreateSSOConnection struct { + // AzureAAD represents a Azure AAD connector. + Aad *CastaiSsoV1beta1AzureAAD `json:"aad,omitempty"` + + // EmailDomain is the email domain of the connection. + EmailDomain string `json:"emailDomain"` + + // Name is the name of the connection. + Name string `json:"name"` + + // Okta represents a Okta connector. + Okta *CastaiSsoV1beta1Okta `json:"okta,omitempty"` +} + +// Defines the container for the sso delete response. +type CastaiSsoV1beta1DeleteSSOConnectionResponse = map[string]interface{} + +// Defines the container for the sso list response. +type CastaiSsoV1beta1ListSSOConnectionsResponse struct { + Connections []CastaiSsoV1beta1SSOConnection `json:"connections"` +} + +// Okta represents a Okta connector. +type CastaiSsoV1beta1Okta struct { + // ClientId is the client id of the Okta. + ClientId string `json:"clientId"` + + // ClientSecret is the client secret of the Okta. + ClientSecret *string `json:"clientSecret,omitempty"` + + // OktaDomain is the domain of the Okta. + OktaDomain string `json:"oktaDomain"` +} + +// SSOConnection represents a sso connection. +type CastaiSsoV1beta1SSOConnection struct { + // AzureAAD represents a Azure AAD connector. + Aad *CastaiSsoV1beta1AzureAAD `json:"aad,omitempty"` + + // CreatedAt is the time when the connection was created. + CreatedAt *time.Time `json:"createdAt,omitempty"` + + // EmailDomain is the email domain of the connection. + EmailDomain string `json:"emailDomain"` + + // Error is the error message of the connection. + Error *string `json:"error,omitempty"` + + // Id is the unique identifier of the connection. + Id *string `json:"id,omitempty"` + + // Name is the name of the connection. + Name string `json:"name"` + + // Okta represents a Okta connector. + Okta *CastaiSsoV1beta1Okta `json:"okta,omitempty"` + + // Status is the status of the connection. + // + // - STATUS_UNKNOWN: StatusUnknown is the default status. + // - STATUS_ACTIVE: StatusActive is the active status. + // - STATUS_INACTIVE: StatusInactive is the inactive status. + // - STATUS_FAILED: StatusFailed is the failed status. + Status *CastaiSsoV1beta1SSOConnectionStatus `json:"status,omitempty"` + + // UpdatedAt is the time when the connection was last updated. + UpdatedAt *time.Time `json:"updatedAt,omitempty"` +} + +// Status is the status of the connection. +// +// - STATUS_UNKNOWN: StatusUnknown is the default status. +// - STATUS_ACTIVE: StatusActive is the active status. +// - STATUS_INACTIVE: StatusInactive is the inactive status. +// - STATUS_FAILED: StatusFailed is the failed status. +type CastaiSsoV1beta1SSOConnectionStatus string + +// SSOConnection represents a sso connection. +type CastaiSsoV1beta1UpdateSSOConnection struct { + // AzureAAD represents a Azure AAD connector. + Aad *CastaiSsoV1beta1AzureAAD `json:"aad,omitempty"` + + // EmailDomain is the email domain of the connection. + EmailDomain *string `json:"emailDomain,omitempty"` + + // Name is the name of the connection. + Name *string `json:"name,omitempty"` + + // Okta represents a Okta connector. + Okta *CastaiSsoV1beta1Okta `json:"okta,omitempty"` +} + // Types of cloud service providers CAST AI supports. // // - invalid: Invalid. @@ -2245,6 +2358,12 @@ type ExternalClusterAPIGetCredentialsScriptTemplateParams struct { CrossRole *bool `form:"crossRole,omitempty" json:"crossRole,omitempty"` } +// SSOAPICreateSSOConnectionJSONBody defines parameters for SSOAPICreateSSOConnection. +type SSOAPICreateSSOConnectionJSONBody = CastaiSsoV1beta1CreateSSOConnection + +// SSOAPIUpdateSSOConnectionJSONBody defines parameters for SSOAPIUpdateSSOConnection. +type SSOAPIUpdateSSOConnectionJSONBody = CastaiSsoV1beta1UpdateSSOConnection + // AuthTokenAPICreateAuthTokenJSONRequestBody defines body for AuthTokenAPICreateAuthToken for application/json ContentType. type AuthTokenAPICreateAuthTokenJSONRequestBody = AuthTokenAPICreateAuthTokenJSONBody @@ -2332,6 +2451,12 @@ type ScheduledRebalancingAPICreateRebalancingScheduleJSONRequestBody = Scheduled // ScheduledRebalancingAPIUpdateRebalancingScheduleJSONRequestBody defines body for ScheduledRebalancingAPIUpdateRebalancingSchedule for application/json ContentType. type ScheduledRebalancingAPIUpdateRebalancingScheduleJSONRequestBody = ScheduledRebalancingAPIUpdateRebalancingScheduleJSONBody +// SSOAPICreateSSOConnectionJSONRequestBody defines body for SSOAPICreateSSOConnection for application/json ContentType. +type SSOAPICreateSSOConnectionJSONRequestBody = SSOAPICreateSSOConnectionJSONBody + +// SSOAPIUpdateSSOConnectionJSONRequestBody defines body for SSOAPIUpdateSSOConnection for application/json ContentType. +type SSOAPIUpdateSSOConnectionJSONRequestBody = SSOAPIUpdateSSOConnectionJSONBody + // Getter for additional properties for CastaiEvictorV1LabelSelector_MatchLabels. Returns the specified // element and whether it was found func (a CastaiEvictorV1LabelSelector_MatchLabels) Get(fieldName string) (value string, found bool) { diff --git a/castai/sdk/client.gen.go b/castai/sdk/client.gen.go index d936d843..d4c6a844 100644 --- a/castai/sdk/client.gen.go +++ b/castai/sdk/client.gen.go @@ -382,6 +382,25 @@ type ClientInterface interface { // ExternalClusterAPIGetCredentialsScriptTemplate request ExternalClusterAPIGetCredentialsScriptTemplate(ctx context.Context, provider string, params *ExternalClusterAPIGetCredentialsScriptTemplateParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // SSOAPIListSSOConnections request + SSOAPIListSSOConnections(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // SSOAPICreateSSOConnection request with any body + SSOAPICreateSSOConnectionWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + SSOAPICreateSSOConnection(ctx context.Context, body SSOAPICreateSSOConnectionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // SSOAPIDeleteSSOConnection request + SSOAPIDeleteSSOConnection(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // SSOAPIGetSSOConnection request + SSOAPIGetSSOConnection(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // SSOAPIUpdateSSOConnection request with any body + SSOAPIUpdateSSOConnectionWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + SSOAPIUpdateSSOConnection(ctx context.Context, id string, body SSOAPIUpdateSSOConnectionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // ScheduledRebalancingAPIListAvailableRebalancingTZ request ScheduledRebalancingAPIListAvailableRebalancingTZ(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) } @@ -1670,6 +1689,90 @@ func (c *Client) ExternalClusterAPIGetCredentialsScriptTemplate(ctx context.Cont return c.Client.Do(req) } +func (c *Client) SSOAPIListSSOConnections(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSSOAPIListSSOConnectionsRequest(c.Server) + 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) SSOAPICreateSSOConnectionWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSSOAPICreateSSOConnectionRequestWithBody(c.Server, 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) SSOAPICreateSSOConnection(ctx context.Context, body SSOAPICreateSSOConnectionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSSOAPICreateSSOConnectionRequest(c.Server, 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) SSOAPIDeleteSSOConnection(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSSOAPIDeleteSSOConnectionRequest(c.Server, id) + 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) SSOAPIGetSSOConnection(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSSOAPIGetSSOConnectionRequest(c.Server, id) + 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) SSOAPIUpdateSSOConnectionWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSSOAPIUpdateSSOConnectionRequestWithBody(c.Server, id, 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) SSOAPIUpdateSSOConnection(ctx context.Context, id string, body SSOAPIUpdateSSOConnectionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSSOAPIUpdateSSOConnectionRequest(c.Server, id, 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) ScheduledRebalancingAPIListAvailableRebalancingTZ(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewScheduledRebalancingAPIListAvailableRebalancingTZRequest(c.Server) if err != nil { @@ -4992,6 +5095,188 @@ func NewExternalClusterAPIGetCredentialsScriptTemplateRequest(server string, pro return req, nil } +// NewSSOAPIListSSOConnectionsRequest generates requests for SSOAPIListSSOConnections +func NewSSOAPIListSSOConnectionsRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/sso-connections") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewSSOAPICreateSSOConnectionRequest calls the generic SSOAPICreateSSOConnection builder with application/json body +func NewSSOAPICreateSSOConnectionRequest(server string, body SSOAPICreateSSOConnectionJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewSSOAPICreateSSOConnectionRequestWithBody(server, "application/json", bodyReader) +} + +// NewSSOAPICreateSSOConnectionRequestWithBody generates requests for SSOAPICreateSSOConnection with any type of body +func NewSSOAPICreateSSOConnectionRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/sso-connections") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewSSOAPIDeleteSSOConnectionRequest generates requests for SSOAPIDeleteSSOConnection +func NewSSOAPIDeleteSSOConnectionRequest(server string, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/sso-connections/%s", pathParam0) + 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 + } + + return req, nil +} + +// NewSSOAPIGetSSOConnectionRequest generates requests for SSOAPIGetSSOConnection +func NewSSOAPIGetSSOConnectionRequest(server string, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/sso-connections/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewSSOAPIUpdateSSOConnectionRequest calls the generic SSOAPIUpdateSSOConnection builder with application/json body +func NewSSOAPIUpdateSSOConnectionRequest(server string, id string, body SSOAPIUpdateSSOConnectionJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewSSOAPIUpdateSSOConnectionRequestWithBody(server, id, "application/json", bodyReader) +} + +// NewSSOAPIUpdateSSOConnectionRequestWithBody generates requests for SSOAPIUpdateSSOConnection with any type of body +func NewSSOAPIUpdateSSOConnectionRequestWithBody(server string, id string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/sso-connections/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewScheduledRebalancingAPIListAvailableRebalancingTZRequest generates requests for ScheduledRebalancingAPIListAvailableRebalancingTZ func NewScheduledRebalancingAPIListAvailableRebalancingTZRequest(server string) (*http.Request, error) { var err error @@ -5354,6 +5639,25 @@ type ClientWithResponsesInterface interface { // ExternalClusterAPIGetCredentialsScriptTemplate request ExternalClusterAPIGetCredentialsScriptTemplateWithResponse(ctx context.Context, provider string, params *ExternalClusterAPIGetCredentialsScriptTemplateParams) (*ExternalClusterAPIGetCredentialsScriptTemplateResponse, error) + // SSOAPIListSSOConnections request + SSOAPIListSSOConnectionsWithResponse(ctx context.Context) (*SSOAPIListSSOConnectionsResponse, error) + + // SSOAPICreateSSOConnection request with any body + SSOAPICreateSSOConnectionWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader) (*SSOAPICreateSSOConnectionResponse, error) + + SSOAPICreateSSOConnectionWithResponse(ctx context.Context, body SSOAPICreateSSOConnectionJSONRequestBody) (*SSOAPICreateSSOConnectionResponse, error) + + // SSOAPIDeleteSSOConnection request + SSOAPIDeleteSSOConnectionWithResponse(ctx context.Context, id string) (*SSOAPIDeleteSSOConnectionResponse, error) + + // SSOAPIGetSSOConnection request + SSOAPIGetSSOConnectionWithResponse(ctx context.Context, id string) (*SSOAPIGetSSOConnectionResponse, error) + + // SSOAPIUpdateSSOConnection request with any body + SSOAPIUpdateSSOConnectionWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader) (*SSOAPIUpdateSSOConnectionResponse, error) + + SSOAPIUpdateSSOConnectionWithResponse(ctx context.Context, id string, body SSOAPIUpdateSSOConnectionJSONRequestBody) (*SSOAPIUpdateSSOConnectionResponse, error) + // ScheduledRebalancingAPIListAvailableRebalancingTZ request ScheduledRebalancingAPIListAvailableRebalancingTZWithResponse(ctx context.Context) (*ScheduledRebalancingAPIListAvailableRebalancingTZResponse, error) } @@ -7704,14 +8008,14 @@ func (r ExternalClusterAPIGetCredentialsScriptTemplateResponse) GetBody() []byte // TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240 -type ScheduledRebalancingAPIListAvailableRebalancingTZResponse struct { +type SSOAPIListSSOConnectionsResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *ScheduledrebalancingV1ListAvailableRebalancingTZResponse + JSON200 *CastaiSsoV1beta1ListSSOConnectionsResponse } // Status returns HTTPResponse.Status -func (r ScheduledRebalancingAPIListAvailableRebalancingTZResponse) Status() string { +func (r SSOAPIListSSOConnectionsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7719,7 +8023,7 @@ func (r ScheduledRebalancingAPIListAvailableRebalancingTZResponse) Status() stri } // StatusCode returns HTTPResponse.StatusCode -func (r ScheduledRebalancingAPIListAvailableRebalancingTZResponse) StatusCode() int { +func (r SSOAPIListSSOConnectionsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7728,18 +8032,168 @@ func (r ScheduledRebalancingAPIListAvailableRebalancingTZResponse) StatusCode() // TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240 // Body returns body of byte array -func (r ScheduledRebalancingAPIListAvailableRebalancingTZResponse) GetBody() []byte { +func (r SSOAPIListSSOConnectionsResponse) GetBody() []byte { return r.Body } // TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240 -// AuthTokenAPIListAuthTokensWithResponse request returning *AuthTokenAPIListAuthTokensResponse -func (c *ClientWithResponses) AuthTokenAPIListAuthTokensWithResponse(ctx context.Context, params *AuthTokenAPIListAuthTokensParams) (*AuthTokenAPIListAuthTokensResponse, error) { - rsp, err := c.AuthTokenAPIListAuthTokens(ctx, params) - if err != nil { - return nil, err - } +type SSOAPICreateSSOConnectionResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *CastaiSsoV1beta1SSOConnection +} + +// Status returns HTTPResponse.Status +func (r SSOAPICreateSSOConnectionResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r SSOAPICreateSSOConnectionResponse) 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 SSOAPICreateSSOConnectionResponse) GetBody() []byte { + return r.Body +} + +// TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240 + +type SSOAPIDeleteSSOConnectionResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *CastaiSsoV1beta1DeleteSSOConnectionResponse +} + +// Status returns HTTPResponse.Status +func (r SSOAPIDeleteSSOConnectionResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r SSOAPIDeleteSSOConnectionResponse) 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 SSOAPIDeleteSSOConnectionResponse) GetBody() []byte { + return r.Body +} + +// TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240 + +type SSOAPIGetSSOConnectionResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *CastaiSsoV1beta1SSOConnection +} + +// Status returns HTTPResponse.Status +func (r SSOAPIGetSSOConnectionResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r SSOAPIGetSSOConnectionResponse) 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 SSOAPIGetSSOConnectionResponse) GetBody() []byte { + return r.Body +} + +// TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240 + +type SSOAPIUpdateSSOConnectionResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *CastaiSsoV1beta1SSOConnection +} + +// Status returns HTTPResponse.Status +func (r SSOAPIUpdateSSOConnectionResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r SSOAPIUpdateSSOConnectionResponse) 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 SSOAPIUpdateSSOConnectionResponse) GetBody() []byte { + return r.Body +} + +// TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240 + +type ScheduledRebalancingAPIListAvailableRebalancingTZResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *ScheduledrebalancingV1ListAvailableRebalancingTZResponse +} + +// Status returns HTTPResponse.Status +func (r ScheduledRebalancingAPIListAvailableRebalancingTZResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ScheduledRebalancingAPIListAvailableRebalancingTZResponse) 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 ScheduledRebalancingAPIListAvailableRebalancingTZResponse) GetBody() []byte { + return r.Body +} + +// TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240 + +// AuthTokenAPIListAuthTokensWithResponse request returning *AuthTokenAPIListAuthTokensResponse +func (c *ClientWithResponses) AuthTokenAPIListAuthTokensWithResponse(ctx context.Context, params *AuthTokenAPIListAuthTokensParams) (*AuthTokenAPIListAuthTokensResponse, error) { + rsp, err := c.AuthTokenAPIListAuthTokens(ctx, params) + if err != nil { + return nil, err + } return ParseAuthTokenAPIListAuthTokensResponse(rsp) } @@ -8668,6 +9122,67 @@ func (c *ClientWithResponses) ExternalClusterAPIGetCredentialsScriptTemplateWith return ParseExternalClusterAPIGetCredentialsScriptTemplateResponse(rsp) } +// SSOAPIListSSOConnectionsWithResponse request returning *SSOAPIListSSOConnectionsResponse +func (c *ClientWithResponses) SSOAPIListSSOConnectionsWithResponse(ctx context.Context) (*SSOAPIListSSOConnectionsResponse, error) { + rsp, err := c.SSOAPIListSSOConnections(ctx) + if err != nil { + return nil, err + } + return ParseSSOAPIListSSOConnectionsResponse(rsp) +} + +// SSOAPICreateSSOConnectionWithBodyWithResponse request with arbitrary body returning *SSOAPICreateSSOConnectionResponse +func (c *ClientWithResponses) SSOAPICreateSSOConnectionWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader) (*SSOAPICreateSSOConnectionResponse, error) { + rsp, err := c.SSOAPICreateSSOConnectionWithBody(ctx, contentType, body) + if err != nil { + return nil, err + } + return ParseSSOAPICreateSSOConnectionResponse(rsp) +} + +func (c *ClientWithResponses) SSOAPICreateSSOConnectionWithResponse(ctx context.Context, body SSOAPICreateSSOConnectionJSONRequestBody) (*SSOAPICreateSSOConnectionResponse, error) { + rsp, err := c.SSOAPICreateSSOConnection(ctx, body) + if err != nil { + return nil, err + } + return ParseSSOAPICreateSSOConnectionResponse(rsp) +} + +// SSOAPIDeleteSSOConnectionWithResponse request returning *SSOAPIDeleteSSOConnectionResponse +func (c *ClientWithResponses) SSOAPIDeleteSSOConnectionWithResponse(ctx context.Context, id string) (*SSOAPIDeleteSSOConnectionResponse, error) { + rsp, err := c.SSOAPIDeleteSSOConnection(ctx, id) + if err != nil { + return nil, err + } + return ParseSSOAPIDeleteSSOConnectionResponse(rsp) +} + +// SSOAPIGetSSOConnectionWithResponse request returning *SSOAPIGetSSOConnectionResponse +func (c *ClientWithResponses) SSOAPIGetSSOConnectionWithResponse(ctx context.Context, id string) (*SSOAPIGetSSOConnectionResponse, error) { + rsp, err := c.SSOAPIGetSSOConnection(ctx, id) + if err != nil { + return nil, err + } + return ParseSSOAPIGetSSOConnectionResponse(rsp) +} + +// SSOAPIUpdateSSOConnectionWithBodyWithResponse request with arbitrary body returning *SSOAPIUpdateSSOConnectionResponse +func (c *ClientWithResponses) SSOAPIUpdateSSOConnectionWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader) (*SSOAPIUpdateSSOConnectionResponse, error) { + rsp, err := c.SSOAPIUpdateSSOConnectionWithBody(ctx, id, contentType, body) + if err != nil { + return nil, err + } + return ParseSSOAPIUpdateSSOConnectionResponse(rsp) +} + +func (c *ClientWithResponses) SSOAPIUpdateSSOConnectionWithResponse(ctx context.Context, id string, body SSOAPIUpdateSSOConnectionJSONRequestBody) (*SSOAPIUpdateSSOConnectionResponse, error) { + rsp, err := c.SSOAPIUpdateSSOConnection(ctx, id, body) + if err != nil { + return nil, err + } + return ParseSSOAPIUpdateSSOConnectionResponse(rsp) +} + // ScheduledRebalancingAPIListAvailableRebalancingTZWithResponse request returning *ScheduledRebalancingAPIListAvailableRebalancingTZResponse func (c *ClientWithResponses) ScheduledRebalancingAPIListAvailableRebalancingTZWithResponse(ctx context.Context) (*ScheduledRebalancingAPIListAvailableRebalancingTZResponse, error) { rsp, err := c.ScheduledRebalancingAPIListAvailableRebalancingTZ(ctx) @@ -10675,6 +11190,136 @@ func ParseExternalClusterAPIGetCredentialsScriptTemplateResponse(rsp *http.Respo return response, nil } +// ParseSSOAPIListSSOConnectionsResponse parses an HTTP response from a SSOAPIListSSOConnectionsWithResponse call +func ParseSSOAPIListSSOConnectionsResponse(rsp *http.Response) (*SSOAPIListSSOConnectionsResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := &SSOAPIListSSOConnectionsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest CastaiSsoV1beta1ListSSOConnectionsResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseSSOAPICreateSSOConnectionResponse parses an HTTP response from a SSOAPICreateSSOConnectionWithResponse call +func ParseSSOAPICreateSSOConnectionResponse(rsp *http.Response) (*SSOAPICreateSSOConnectionResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := &SSOAPICreateSSOConnectionResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest CastaiSsoV1beta1SSOConnection + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseSSOAPIDeleteSSOConnectionResponse parses an HTTP response from a SSOAPIDeleteSSOConnectionWithResponse call +func ParseSSOAPIDeleteSSOConnectionResponse(rsp *http.Response) (*SSOAPIDeleteSSOConnectionResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := &SSOAPIDeleteSSOConnectionResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest CastaiSsoV1beta1DeleteSSOConnectionResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseSSOAPIGetSSOConnectionResponse parses an HTTP response from a SSOAPIGetSSOConnectionWithResponse call +func ParseSSOAPIGetSSOConnectionResponse(rsp *http.Response) (*SSOAPIGetSSOConnectionResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := &SSOAPIGetSSOConnectionResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest CastaiSsoV1beta1SSOConnection + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseSSOAPIUpdateSSOConnectionResponse parses an HTTP response from a SSOAPIUpdateSSOConnectionWithResponse call +func ParseSSOAPIUpdateSSOConnectionResponse(rsp *http.Response) (*SSOAPIUpdateSSOConnectionResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := &SSOAPIUpdateSSOConnectionResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest CastaiSsoV1beta1SSOConnection + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseScheduledRebalancingAPIListAvailableRebalancingTZResponse parses an HTTP response from a ScheduledRebalancingAPIListAvailableRebalancingTZWithResponse call func ParseScheduledRebalancingAPIListAvailableRebalancingTZResponse(rsp *http.Response) (*ScheduledRebalancingAPIListAvailableRebalancingTZResponse, error) { bodyBytes, err := ioutil.ReadAll(rsp.Body) diff --git a/castai/sdk/mock/client.go b/castai/sdk/mock/client.go index 73d2782a..9d7f291c 100644 --- a/castai/sdk/mock/client.go +++ b/castai/sdk/mock/client.go @@ -1775,6 +1775,146 @@ func (mr *MockClientInterfaceMockRecorder) PoliciesAPIUpsertClusterPoliciesWithB return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PoliciesAPIUpsertClusterPoliciesWithBody", reflect.TypeOf((*MockClientInterface)(nil).PoliciesAPIUpsertClusterPoliciesWithBody), varargs...) } +// SSOAPICreateSSOConnection mocks base method. +func (m *MockClientInterface) SSOAPICreateSSOConnection(ctx context.Context, body sdk.SSOAPICreateSSOConnectionJSONRequestBody, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "SSOAPICreateSSOConnection", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SSOAPICreateSSOConnection indicates an expected call of SSOAPICreateSSOConnection. +func (mr *MockClientInterfaceMockRecorder) SSOAPICreateSSOConnection(ctx, body interface{}, reqEditors ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSOAPICreateSSOConnection", reflect.TypeOf((*MockClientInterface)(nil).SSOAPICreateSSOConnection), varargs...) +} + +// SSOAPICreateSSOConnectionWithBody mocks base method. +func (m *MockClientInterface) SSOAPICreateSSOConnectionWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, contentType, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "SSOAPICreateSSOConnectionWithBody", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SSOAPICreateSSOConnectionWithBody indicates an expected call of SSOAPICreateSSOConnectionWithBody. +func (mr *MockClientInterfaceMockRecorder) SSOAPICreateSSOConnectionWithBody(ctx, contentType, body interface{}, reqEditors ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, contentType, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSOAPICreateSSOConnectionWithBody", reflect.TypeOf((*MockClientInterface)(nil).SSOAPICreateSSOConnectionWithBody), varargs...) +} + +// SSOAPIDeleteSSOConnection mocks base method. +func (m *MockClientInterface) SSOAPIDeleteSSOConnection(ctx context.Context, id string, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, id} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "SSOAPIDeleteSSOConnection", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SSOAPIDeleteSSOConnection indicates an expected call of SSOAPIDeleteSSOConnection. +func (mr *MockClientInterfaceMockRecorder) SSOAPIDeleteSSOConnection(ctx, id interface{}, reqEditors ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, id}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSOAPIDeleteSSOConnection", reflect.TypeOf((*MockClientInterface)(nil).SSOAPIDeleteSSOConnection), varargs...) +} + +// SSOAPIGetSSOConnection mocks base method. +func (m *MockClientInterface) SSOAPIGetSSOConnection(ctx context.Context, id string, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, id} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "SSOAPIGetSSOConnection", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SSOAPIGetSSOConnection indicates an expected call of SSOAPIGetSSOConnection. +func (mr *MockClientInterfaceMockRecorder) SSOAPIGetSSOConnection(ctx, id interface{}, reqEditors ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, id}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSOAPIGetSSOConnection", reflect.TypeOf((*MockClientInterface)(nil).SSOAPIGetSSOConnection), varargs...) +} + +// SSOAPIListSSOConnections mocks base method. +func (m *MockClientInterface) SSOAPIListSSOConnections(ctx context.Context, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "SSOAPIListSSOConnections", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SSOAPIListSSOConnections indicates an expected call of SSOAPIListSSOConnections. +func (mr *MockClientInterfaceMockRecorder) SSOAPIListSSOConnections(ctx interface{}, reqEditors ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSOAPIListSSOConnections", reflect.TypeOf((*MockClientInterface)(nil).SSOAPIListSSOConnections), varargs...) +} + +// SSOAPIUpdateSSOConnection mocks base method. +func (m *MockClientInterface) SSOAPIUpdateSSOConnection(ctx context.Context, id string, body sdk.SSOAPIUpdateSSOConnectionJSONRequestBody, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, id, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "SSOAPIUpdateSSOConnection", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SSOAPIUpdateSSOConnection indicates an expected call of SSOAPIUpdateSSOConnection. +func (mr *MockClientInterfaceMockRecorder) SSOAPIUpdateSSOConnection(ctx, id, body interface{}, reqEditors ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, id, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSOAPIUpdateSSOConnection", reflect.TypeOf((*MockClientInterface)(nil).SSOAPIUpdateSSOConnection), varargs...) +} + +// SSOAPIUpdateSSOConnectionWithBody mocks base method. +func (m *MockClientInterface) SSOAPIUpdateSSOConnectionWithBody(ctx context.Context, id, contentType string, body io.Reader, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, id, contentType, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "SSOAPIUpdateSSOConnectionWithBody", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SSOAPIUpdateSSOConnectionWithBody indicates an expected call of SSOAPIUpdateSSOConnectionWithBody. +func (mr *MockClientInterfaceMockRecorder) SSOAPIUpdateSSOConnectionWithBody(ctx, id, contentType, body interface{}, reqEditors ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, id, contentType, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSOAPIUpdateSSOConnectionWithBody", reflect.TypeOf((*MockClientInterface)(nil).SSOAPIUpdateSSOConnectionWithBody), varargs...) +} + // ScheduledRebalancingAPICreateRebalancingJob mocks base method. func (m *MockClientInterface) ScheduledRebalancingAPICreateRebalancingJob(ctx context.Context, clusterId string, body sdk.ScheduledRebalancingAPICreateRebalancingJobJSONRequestBody, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) { m.ctrl.T.Helper() @@ -3533,6 +3673,111 @@ func (mr *MockClientWithResponsesInterfaceMockRecorder) PoliciesAPIUpsertCluster return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PoliciesAPIUpsertClusterPoliciesWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).PoliciesAPIUpsertClusterPoliciesWithResponse), ctx, clusterId, body) } +// SSOAPICreateSSOConnectionWithBodyWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) SSOAPICreateSSOConnectionWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader) (*sdk.SSOAPICreateSSOConnectionResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SSOAPICreateSSOConnectionWithBodyWithResponse", ctx, contentType, body) + ret0, _ := ret[0].(*sdk.SSOAPICreateSSOConnectionResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SSOAPICreateSSOConnectionWithBodyWithResponse indicates an expected call of SSOAPICreateSSOConnectionWithBodyWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) SSOAPICreateSSOConnectionWithBodyWithResponse(ctx, contentType, body interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSOAPICreateSSOConnectionWithBodyWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).SSOAPICreateSSOConnectionWithBodyWithResponse), ctx, contentType, body) +} + +// SSOAPICreateSSOConnectionWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) SSOAPICreateSSOConnectionWithResponse(ctx context.Context, body sdk.SSOAPICreateSSOConnectionJSONRequestBody) (*sdk.SSOAPICreateSSOConnectionResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SSOAPICreateSSOConnectionWithResponse", ctx, body) + ret0, _ := ret[0].(*sdk.SSOAPICreateSSOConnectionResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SSOAPICreateSSOConnectionWithResponse indicates an expected call of SSOAPICreateSSOConnectionWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) SSOAPICreateSSOConnectionWithResponse(ctx, body interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSOAPICreateSSOConnectionWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).SSOAPICreateSSOConnectionWithResponse), ctx, body) +} + +// SSOAPIDeleteSSOConnectionWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) SSOAPIDeleteSSOConnectionWithResponse(ctx context.Context, id string) (*sdk.SSOAPIDeleteSSOConnectionResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SSOAPIDeleteSSOConnectionWithResponse", ctx, id) + ret0, _ := ret[0].(*sdk.SSOAPIDeleteSSOConnectionResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SSOAPIDeleteSSOConnectionWithResponse indicates an expected call of SSOAPIDeleteSSOConnectionWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) SSOAPIDeleteSSOConnectionWithResponse(ctx, id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSOAPIDeleteSSOConnectionWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).SSOAPIDeleteSSOConnectionWithResponse), ctx, id) +} + +// SSOAPIGetSSOConnectionWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) SSOAPIGetSSOConnectionWithResponse(ctx context.Context, id string) (*sdk.SSOAPIGetSSOConnectionResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SSOAPIGetSSOConnectionWithResponse", ctx, id) + ret0, _ := ret[0].(*sdk.SSOAPIGetSSOConnectionResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SSOAPIGetSSOConnectionWithResponse indicates an expected call of SSOAPIGetSSOConnectionWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) SSOAPIGetSSOConnectionWithResponse(ctx, id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSOAPIGetSSOConnectionWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).SSOAPIGetSSOConnectionWithResponse), ctx, id) +} + +// SSOAPIListSSOConnectionsWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) SSOAPIListSSOConnectionsWithResponse(ctx context.Context) (*sdk.SSOAPIListSSOConnectionsResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SSOAPIListSSOConnectionsWithResponse", ctx) + ret0, _ := ret[0].(*sdk.SSOAPIListSSOConnectionsResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SSOAPIListSSOConnectionsWithResponse indicates an expected call of SSOAPIListSSOConnectionsWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) SSOAPIListSSOConnectionsWithResponse(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSOAPIListSSOConnectionsWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).SSOAPIListSSOConnectionsWithResponse), ctx) +} + +// SSOAPIUpdateSSOConnectionWithBodyWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) SSOAPIUpdateSSOConnectionWithBodyWithResponse(ctx context.Context, id, contentType string, body io.Reader) (*sdk.SSOAPIUpdateSSOConnectionResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SSOAPIUpdateSSOConnectionWithBodyWithResponse", ctx, id, contentType, body) + ret0, _ := ret[0].(*sdk.SSOAPIUpdateSSOConnectionResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SSOAPIUpdateSSOConnectionWithBodyWithResponse indicates an expected call of SSOAPIUpdateSSOConnectionWithBodyWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) SSOAPIUpdateSSOConnectionWithBodyWithResponse(ctx, id, contentType, body interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSOAPIUpdateSSOConnectionWithBodyWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).SSOAPIUpdateSSOConnectionWithBodyWithResponse), ctx, id, contentType, body) +} + +// SSOAPIUpdateSSOConnectionWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) SSOAPIUpdateSSOConnectionWithResponse(ctx context.Context, id string, body sdk.SSOAPIUpdateSSOConnectionJSONRequestBody) (*sdk.SSOAPIUpdateSSOConnectionResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SSOAPIUpdateSSOConnectionWithResponse", ctx, id, body) + ret0, _ := ret[0].(*sdk.SSOAPIUpdateSSOConnectionResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SSOAPIUpdateSSOConnectionWithResponse indicates an expected call of SSOAPIUpdateSSOConnectionWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) SSOAPIUpdateSSOConnectionWithResponse(ctx, id, body interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSOAPIUpdateSSOConnectionWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).SSOAPIUpdateSSOConnectionWithResponse), ctx, id, body) +} + // ScheduledRebalancingAPICreateRebalancingJobWithBodyWithResponse mocks base method. func (m *MockClientWithResponsesInterface) ScheduledRebalancingAPICreateRebalancingJobWithBodyWithResponse(ctx context.Context, clusterId, contentType string, body io.Reader) (*sdk.ScheduledRebalancingAPICreateRebalancingJobResponse, error) { m.ctrl.T.Helper() diff --git a/docs/resources/sso_connection.md b/docs/resources/sso_connection.md new file mode 100644 index 00000000..6f6386eb --- /dev/null +++ b/docs/resources/sso_connection.md @@ -0,0 +1,71 @@ +--- +page_title: "castai_sso_connection Resource - terraform-provider-castai" +subcategory: "" +description: |- + SSO Connection resource allows creating SSO trust relationship with CAST AI. +--- + +# castai_sso_connection (Resource) + +SSO Connection resource allows creating SSO trust relationship with CAST AI. + +## Example Usage + +```terraform +resource "castai_sso_connection" "sso" { + name = "aad_connection" + email_domain = "aad_connection@test.com" + aad { + client_id = azuread_application.castai_sso.client_id + client_secret = azuread_application_password.castai_sso.value + ad_domain = azuread_application.castai_sso.publisher_domain + } +} +``` + + +## Schema + +### Required + +- `email_domain` (String) Email domain of the connection +- `name` (String) Connection name + +### Optional + +- `aad` (Block List, Max: 1) Azure AD connector (see [below for nested schema](#nestedblock--aad)) +- `okta` (Block List, Max: 1) Okta connector (see [below for nested schema](#nestedblock--okta)) +- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `aad` + +Required: + +- `ad_domain` (String) Azure AD domain +- `client_id` (String) Azure AD client ID +- `client_secret` (String, Sensitive) Azure AD client secret + + + +### Nested Schema for `okta` + +Required: + +- `client_id` (String) Okta client ID +- `client_secret` (String, Sensitive) Okta client secret +- `okta_domain` (String) Okta domain + + + +### Nested Schema for `timeouts` + +Optional: + +- `create` (String) +- `delete` (String) +- `update` (String) diff --git a/examples/resources/sso_connection/resource.tf b/examples/resources/sso_connection/resource.tf new file mode 100644 index 00000000..728fb310 --- /dev/null +++ b/examples/resources/sso_connection/resource.tf @@ -0,0 +1,9 @@ +resource "castai_sso_connection" "sso" { + name = "aad_connection" + email_domain = "aad_connection@test.com" + aad { + client_id = azuread_application.castai_sso.client_id + client_secret = azuread_application_password.castai_sso.value + ad_domain = azuread_application.castai_sso.publisher_domain + } +} diff --git a/examples/sso_connection/README.md b/examples/sso_connection/README.md new file mode 100644 index 00000000..ed9ae416 --- /dev/null +++ b/examples/sso_connection/README.md @@ -0,0 +1,12 @@ +## CAST AI example for creating SSO connection + +Following example shows how setup Azure AD to create SSO trust relationship with CAST AI. + +Prerequisites: +- CAST AI account +- Obtained CAST AI [API Access key](https://docs.cast.ai/docs/authentication#obtaining-api-access-key) with Full Access + +1. Rename `tf.vars.example` to `tf.vars` +2. Update `tf.vars` file. +3. Run `terraform init` +4. Run `terraform apply apply -var-file=tf.vars` \ No newline at end of file diff --git a/examples/sso_connection/azure.tf b/examples/sso_connection/azure.tf new file mode 100644 index 00000000..44b45d22 --- /dev/null +++ b/examples/sso_connection/azure.tf @@ -0,0 +1,44 @@ +data "azuread_client_config" "current" {} + +resource "azuread_application" "castai_sso" { + display_name = "castai_sso" + + web { + redirect_uris = ["https://login.cast.ai/login/callback"] + } + + required_resource_access { + resource_app_id = data.azuread_application_published_app_ids.well_known.result.MicrosoftGraph + + resource_access { + id = azuread_service_principal.msgraph.app_role_ids["Directory.Read.All"] + type = "Role" + } + + resource_access { + id = azuread_service_principal.msgraph.oauth2_permission_scope_ids["User.Read"] + type = "Scope" + } + } +} + +resource "azuread_application_password" "castai_sso" { + application_id = azuread_application.castai_sso.id +} + +data "azuread_application_published_app_ids" "well_known" {} + +resource "azuread_service_principal" "msgraph" { + client_id = data.azuread_application_published_app_ids.well_known.result.MicrosoftGraph + use_existing = true +} + +resource "azuread_service_principal" "castai_sso" { + client_id = azuread_application.castai_sso.client_id +} + +resource "azuread_app_role_assignment" "this" { + app_role_id = azuread_service_principal.msgraph.app_role_ids["Directory.Read.All"] + principal_object_id = azuread_service_principal.castai_sso.object_id + resource_object_id = azuread_service_principal.msgraph.object_id +} diff --git a/examples/sso_connection/main.tf b/examples/sso_connection/main.tf new file mode 100644 index 00000000..4bd963ce --- /dev/null +++ b/examples/sso_connection/main.tf @@ -0,0 +1,23 @@ +resource "castai_sso_connection" "sso" { + name = "azure_sso" + email_domain = azuread_application.castai_sso.publisher_domain + aad { + client_id = azuread_application.castai_sso.client_id + client_secret = azuread_application_password.castai_sso.value + ad_domain = azuread_application.castai_sso.publisher_domain + } + depends_on = [time_sleep.wait_10_seconds] +} + +# Since creating castai_sso_connection immediately using azuread credentials fails with +# 'The identity of the calling application could not be established' or +# 'ClientSecretCredential authentication failed' for the sake of this example +# we will use a simple sleep to ensure this example will work out of the box. +resource "time_sleep" "wait_10_seconds" { + depends_on = [ + azuread_app_role_assignment.this, + azuread_application_password.castai_sso + ] + + create_duration = "10s" +} diff --git a/examples/sso_connection/providers.tf b/examples/sso_connection/providers.tf new file mode 100644 index 00000000..168b217e --- /dev/null +++ b/examples/sso_connection/providers.tf @@ -0,0 +1,8 @@ +provider "castai" { + api_url = var.castai_api_url + api_token = var.castai_api_token +} + +provider "azurerm" { + features {} +} diff --git a/examples/sso_connection/tf.vars.example b/examples/sso_connection/tf.vars.example new file mode 100644 index 00000000..a6a4ce09 --- /dev/null +++ b/examples/sso_connection/tf.vars.example @@ -0,0 +1,2 @@ +castai_api_url = "PLACEHOLDER" +castai_api_token = "PLACEHOLDER" diff --git a/examples/sso_connection/variables.tf b/examples/sso_connection/variables.tf new file mode 100644 index 00000000..0ce1430c --- /dev/null +++ b/examples/sso_connection/variables.tf @@ -0,0 +1,10 @@ +variable "castai_api_token" { + type = string + description = "CAST AI API token created in console.cast.ai API Access keys section." +} + +variable "castai_api_url" { + type = string + description = "CAST AI api url." + default = "https://api.cast.ai" +} diff --git a/examples/sso_connection/versions.tf b/examples/sso_connection/versions.tf new file mode 100644 index 00000000..bdd5cd22 --- /dev/null +++ b/examples/sso_connection/versions.tf @@ -0,0 +1,16 @@ +terraform { + required_providers { + castai = { + source = "castai/castai" + version = ">= 5.8.0" + } + azurerm = { + source = "hashicorp/azurerm" + version = ">= 3.79.0" + } + azuread = { + source = "hashicorp/azuread" + version = ">= 2.45.0" + } + } +} diff --git a/templates/resources/sso_connection.md.tmpl b/templates/resources/sso_connection.md.tmpl new file mode 100644 index 00000000..f5199d33 --- /dev/null +++ b/templates/resources/sso_connection.md.tmpl @@ -0,0 +1,16 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile "examples/resources/sso_connection/resource.tf" }} + +{{ .SchemaMarkdown | trimspace }} From 989eae803366c610d6039a4298db11f8dcff45cd Mon Sep 17 00:00:00 2001 From: Jakub Burghardt Date: Wed, 8 Nov 2023 11:20:09 +0100 Subject: [PATCH 2/2] add missing workflow envs --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 930d88ab..735b93f3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,5 +56,8 @@ jobs: ARM_TENANT_ID: ${{ secrets.AZURE_TF_ACCEPTANCE_TEST_ARM_TENANT_ID }} GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_TF_ACCEPTANCE_TEST_CREDENTIALS }} GOOGLE_PROJECT_ID: ${{ secrets.GOOGLE_TF_ACCEPTANCE_PROJECT_ID }} + SSO_CLIENT_ID: ${{ secrets.SSO_CLIENT_ID }} + SSO_CLIENT_SECRET: ${{ secrets.SSO_CLIENT_SECRET }} + SSO_DOMAIN: ${{ secrets.SSO_DOMAIN }} run: make testacc