diff --git a/pkg/acceptance/bettertestspoc/assert/objectassert/connection_snowflake_ext.go b/pkg/acceptance/bettertestspoc/assert/objectassert/connection_snowflake_ext.go new file mode 100644 index 0000000000..4e237798ef --- /dev/null +++ b/pkg/acceptance/bettertestspoc/assert/objectassert/connection_snowflake_ext.go @@ -0,0 +1,56 @@ +package objectassert + +import ( + "fmt" + "slices" + "strings" + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" +) + +func (c *ConnectionAssert) HasFailoverAllowedToAccounts(expected []string) *ConnectionAssert { + c.AddAssertion(func(t *testing.T, o *sdk.Connection) error { + t.Helper() + if !slices.Equal(expected, o.FailoverAllowedToAccounts) { + return fmt.Errorf("expected failover_allowed_to_accounts to be: %v; got: %v", expected, o.FailoverAllowedToAccounts) + } + return nil + }) + return c +} + +func (c *ConnectionAssert) HasNoComment() *ConnectionAssert { + c.AddAssertion(func(t *testing.T, o *sdk.Connection) error { + t.Helper() + if o.Comment != nil { + return fmt.Errorf("expected comment to have nil; got: %s", *o.Comment) + } + return nil + }) + return c +} + +func (c *ConnectionAssert) HasConnectionUrlNotEmpty() *ConnectionAssert { + c.AddAssertion(func(t *testing.T, o *sdk.Connection) error { + t.Helper() + if o.ConnectionUrl == "" { + return fmt.Errorf("expected connection url not empty, got: %s", o.ConnectionUrl) + } + return nil + }) + + return c +} + +func (c *ConnectionAssert) HasPrimaryIdentifier(expected sdk.ExternalObjectIdentifier) *ConnectionAssert { + c.AddAssertion(func(t *testing.T, o *sdk.Connection) error { + t.Helper() + expectedString := strings.ReplaceAll(expected.FullyQualifiedName(), `"`, "") + if o.Primary != expectedString { + return fmt.Errorf("expected primary identifier: %v; got: %v", expectedString, o.Primary) + } + return nil + }) + return c +} diff --git a/pkg/acceptance/bettertestspoc/assert/objectassert/connection_snowflake_gen.go b/pkg/acceptance/bettertestspoc/assert/objectassert/connection_snowflake_gen.go new file mode 100644 index 0000000000..d1c0946276 --- /dev/null +++ b/pkg/acceptance/bettertestspoc/assert/objectassert/connection_snowflake_gen.go @@ -0,0 +1,145 @@ +// Code generated by assertions generator; DO NOT EDIT. + +package objectassert + +import ( + "fmt" + "testing" + "time" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" +) + +type ConnectionAssert struct { + *assert.SnowflakeObjectAssert[sdk.Connection, sdk.AccountObjectIdentifier] +} + +func Connection(t *testing.T, id sdk.AccountObjectIdentifier) *ConnectionAssert { + t.Helper() + return &ConnectionAssert{ + assert.NewSnowflakeObjectAssertWithProvider(sdk.ObjectTypeConnection, id, acc.TestClient().Connection.Show), + } +} + +func ConnectionFromObject(t *testing.T, connection *sdk.Connection) *ConnectionAssert { + t.Helper() + return &ConnectionAssert{ + assert.NewSnowflakeObjectAssertWithObject(sdk.ObjectTypeConnection, connection.ID(), connection), + } +} + +func (c *ConnectionAssert) HasSnowflakeRegion(expected string) *ConnectionAssert { + c.AddAssertion(func(t *testing.T, o *sdk.Connection) error { + t.Helper() + if o.SnowflakeRegion != expected { + return fmt.Errorf("expected snowflake region: %v; got: %v", expected, o.SnowflakeRegion) + } + return nil + }) + return c +} + +func (c *ConnectionAssert) HasCreatedOn(expected time.Time) *ConnectionAssert { + c.AddAssertion(func(t *testing.T, o *sdk.Connection) error { + t.Helper() + if o.CreatedOn != expected { + return fmt.Errorf("expected created on: %v; got: %v", expected, o.CreatedOn) + } + return nil + }) + return c +} + +func (c *ConnectionAssert) HasAccountName(expected string) *ConnectionAssert { + c.AddAssertion(func(t *testing.T, o *sdk.Connection) error { + t.Helper() + if o.AccountName != expected { + return fmt.Errorf("expected account name: %v; got: %v", expected, o.AccountName) + } + return nil + }) + return c +} + +func (c *ConnectionAssert) HasName(expected string) *ConnectionAssert { + c.AddAssertion(func(t *testing.T, o *sdk.Connection) error { + t.Helper() + if o.Name != expected { + return fmt.Errorf("expected name: %v; got: %v", expected, o.Name) + } + return nil + }) + return c +} + +func (c *ConnectionAssert) HasComment(expected string) *ConnectionAssert { + c.AddAssertion(func(t *testing.T, o *sdk.Connection) error { + t.Helper() + if o.Comment == nil { + return fmt.Errorf("expected comment to have value; got: nil") + } + if *o.Comment != expected { + return fmt.Errorf("expected comment: %v; got: %v", expected, *o.Comment) + } + return nil + }) + return c +} + +func (c *ConnectionAssert) HasIsPrimary(expected bool) *ConnectionAssert { + c.AddAssertion(func(t *testing.T, o *sdk.Connection) error { + t.Helper() + if o.IsPrimary != expected { + return fmt.Errorf("expected is primary: %v; got: %v", expected, o.IsPrimary) + } + return nil + }) + return c +} + +func (c *ConnectionAssert) HasPrimary(expected string) *ConnectionAssert { + c.AddAssertion(func(t *testing.T, o *sdk.Connection) error { + t.Helper() + if o.Primary != expected { + return fmt.Errorf("expected primary: %v; got: %v", expected, o.Primary) + } + return nil + }) + return c +} + +func (c *ConnectionAssert) HasConnectionUrl(expected string) *ConnectionAssert { + c.AddAssertion(func(t *testing.T, o *sdk.Connection) error { + t.Helper() + if o.ConnectionUrl != expected { + return fmt.Errorf("expected connection url: %v; got: %v", expected, o.ConnectionUrl) + } + return nil + }) + return c +} + +func (c *ConnectionAssert) HasOrganizationName(expected string) *ConnectionAssert { + c.AddAssertion(func(t *testing.T, o *sdk.Connection) error { + t.Helper() + if o.OrganizationName != expected { + return fmt.Errorf("expected organization name: %v; got: %v", expected, o.OrganizationName) + } + return nil + }) + return c +} + +func (c *ConnectionAssert) HasAccountLocator(expected string) *ConnectionAssert { + c.AddAssertion(func(t *testing.T, o *sdk.Connection) error { + t.Helper() + if o.AccountLocator != expected { + return fmt.Errorf("expected account locator: %v; got: %v", expected, o.AccountLocator) + } + return nil + }) + return c +} diff --git a/pkg/acceptance/bettertestspoc/assert/objectassert/gen/sdk_object_def.go b/pkg/acceptance/bettertestspoc/assert/objectassert/gen/sdk_object_def.go index 3e03a38629..42dabe5c9d 100644 --- a/pkg/acceptance/bettertestspoc/assert/objectassert/gen/sdk_object_def.go +++ b/pkg/acceptance/bettertestspoc/assert/objectassert/gen/sdk_object_def.go @@ -17,6 +17,11 @@ var allStructs = []SdkObjectDef{ ObjectType: sdk.ObjectTypeDatabase, ObjectStruct: sdk.Database{}, }, + { + IdType: "sdk.AccountObjectIdentifier", + ObjectType: sdk.ObjectTypeConnection, + ObjectStruct: sdk.Connection{}, + }, { IdType: "sdk.DatabaseObjectIdentifier", ObjectType: sdk.ObjectTypeDatabaseRole, diff --git a/pkg/acceptance/helpers/connection_client.go b/pkg/acceptance/helpers/connection_client.go new file mode 100644 index 0000000000..24fdf9384a --- /dev/null +++ b/pkg/acceptance/helpers/connection_client.go @@ -0,0 +1,72 @@ +package helpers + +import ( + "context" + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/stretchr/testify/require" +) + +type ConnectionClient struct { + context *TestClientContext + ids *IdsGenerator +} + +func NewConnectionClient(context *TestClientContext, idsGenerator *IdsGenerator) *ConnectionClient { + return &ConnectionClient{ + context: context, + ids: idsGenerator, + } +} + +func (c *ConnectionClient) client() sdk.Connections { + return c.context.client.Connections +} + +func (c *ConnectionClient) Create(t *testing.T, id sdk.AccountObjectIdentifier) (*sdk.Connection, func()) { + t.Helper() + ctx := context.Background() + request := sdk.NewCreateConnectionRequest(id) + err := c.client().Create(ctx, request) + require.NoError(t, err) + connection, err := c.client().ShowByID(ctx, id) + require.NoError(t, err) + return connection, c.DropFunc(t, id) +} + +func (c *ConnectionClient) CreateReplication(t *testing.T, id sdk.AccountObjectIdentifier, replicaOf sdk.ExternalObjectIdentifier) (*sdk.Connection, func()) { + t.Helper() + ctx := context.Background() + request := sdk.NewCreateConnectionRequest(id).WithAsReplicaOf(replicaOf) + err := c.client().Create(ctx, request) + require.NoError(t, err) + connection, err := c.client().ShowByID(ctx, id) + require.NoError(t, err) + return connection, c.DropFunc(t, id) +} + +func (c *ConnectionClient) Alter(t *testing.T, id sdk.AccountObjectIdentifier, req *sdk.AlterConnectionRequest) { + t.Helper() + ctx := context.Background() + + err := c.client().Alter(ctx, req) + require.NoError(t, err) +} + +func (c *ConnectionClient) DropFunc(t *testing.T, id sdk.AccountObjectIdentifier) func() { + t.Helper() + ctx := context.Background() + + return func() { + err := c.client().Drop(ctx, sdk.NewDropConnectionRequest(id).WithIfExists(true)) + require.NoError(t, err) + } +} + +func (c *ConnectionClient) Show(t *testing.T, id sdk.AccountObjectIdentifier) (*sdk.Connection, error) { + t.Helper() + ctx := context.Background() + + return c.client().ShowByID(ctx, id) +} diff --git a/pkg/acceptance/helpers/test_client.go b/pkg/acceptance/helpers/test_client.go index 9ddfd21102..cbfc8de2b9 100644 --- a/pkg/acceptance/helpers/test_client.go +++ b/pkg/acceptance/helpers/test_client.go @@ -17,6 +17,7 @@ type TestClient struct { ApplicationPackage *ApplicationPackageClient AuthenticationPolicy *AuthenticationPolicyClient BcrBundles *BcrBundlesClient + Connection *ConnectionClient Context *ContextClient CortexSearchService *CortexSearchServiceClient CatalogIntegration *CatalogIntegrationClient @@ -84,6 +85,7 @@ func NewTestClient(c *sdk.Client, database string, schema string, warehouse stri ApplicationPackage: NewApplicationPackageClient(context, idsGenerator), AuthenticationPolicy: NewAuthenticationPolicyClient(context, idsGenerator), BcrBundles: NewBcrBundlesClient(context), + Connection: NewConnectionClient(context, idsGenerator), Context: NewContextClient(context), CortexSearchService: NewCortexSearchServiceClient(context, idsGenerator), CatalogIntegration: NewCatalogIntegrationClient(context, idsGenerator), diff --git a/pkg/sdk/client.go b/pkg/sdk/client.go index b5a76425c9..94b0f9f68a 100644 --- a/pkg/sdk/client.go +++ b/pkg/sdk/client.go @@ -47,6 +47,7 @@ type Client struct { Applications Applications AuthenticationPolicies AuthenticationPolicies Comments Comments + Connections Connections CortexSearchServices CortexSearchServices DatabaseRoles DatabaseRoles Databases Databases @@ -205,6 +206,7 @@ func (c *Client) initialize() { c.Applications = &applications{client: c} c.AuthenticationPolicies = &authenticationPolicies{client: c} c.Comments = &comments{client: c} + c.Connections = &connections{client: c} c.ContextFunctions = &contextFunctions{client: c} c.ConversionFunctions = &conversionFunctions{client: c} c.CortexSearchServices = &cortexSearchServices{client: c} diff --git a/pkg/sdk/connections_def.go b/pkg/sdk/connections_def.go new file mode 100644 index 0000000000..39f583aff2 --- /dev/null +++ b/pkg/sdk/connections_def.go @@ -0,0 +1,107 @@ +package sdk + +//go:generate go run ./poc/main.go + +import ( + g "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/poc/generator" +) + +var ConnectionDef = g.NewInterface( + "Connections", + "Connection", + g.KindOfT[AccountObjectIdentifier](), +).CreateOperation( + "https://docs.snowflake.com/en/sql-reference/sql/create-connection", + g.NewQueryStruct("CreateConnection"). + Create(). + SQL("CONNECTION"). + IfNotExists(). + Name(). + OptionalIdentifier( + "AsReplicaOf", + g.KindOfT[ExternalObjectIdentifier](), + g.IdentifierOptions().Required().SQL("AS REPLICA OF")). + OptionalComment(). + WithValidation(g.ValidIdentifier, "name"). + WithValidation(g.ValidIdentifierIfSet, "AsReplicaOf"), +).AlterOperation( + "https://docs.snowflake.com/en/sql-reference/sql/alter-connection", + g.NewQueryStruct("Alter"). + Alter(). + SQL("CONNECTION"). + IfExists(). + Name(). + OptionalQueryStructField( + "EnableConnectionFailover", + g.NewQueryStruct("EnableConnectionFailover"). + List("ToAccounts", "AccountIdentifier", g.ListOptions().NoParentheses()), + g.KeywordOptions().SQL("ENABLE FAILOVER TO ACCOUNTS"), + ). + OptionalQueryStructField( + "DisableConnectionFailover", + g.NewQueryStruct("DisableConnectionFailover"). + OptionalQueryStructField( + "ToAccounts", + g.NewQueryStruct("ToAccounts"). + List("Accounts", "AccountIdentifier", g.ListOptions().NoParentheses()), + g.KeywordOptions().SQL("TO ACCOUNTS"), + ), + g.KeywordOptions().SQL("DISABLE FAILOVER"), + ). + OptionalSQL("PRIMARY"). + OptionalQueryStructField( + "Set", + g.NewQueryStruct("Set"). + OptionalComment(). + WithValidation(g.AtLeastOneValueSet, "Comment"), + g.KeywordOptions().SQL("SET"), + ). + OptionalQueryStructField( + "Unset", + g.NewQueryStruct("Unset"). + OptionalSQL("COMMENT"). + WithValidation(g.AtLeastOneValueSet, "Comment"), + g.KeywordOptions().SQL("UNSET"), + ). + WithValidation(g.ExactlyOneValueSet, "EnableConnectionFailover", "DisableConnectionFailover", "Primary", "Set", "Unset"), +).DropOperation( + "https://docs.snowflake.com/en/sql-reference/sql/drop-connection", + g.NewQueryStruct("DropConnection"). + Drop(). + SQL("CONNECTION"). + IfExists(). + Name(). + WithValidation(g.ValidIdentifier, "name"), +).ShowOperation( + "https://docs.snowflake.com/en/sql-reference/sql/show-connections", + g.DbStruct("connectionRow"). + OptionalText("region_group"). + Text("snowflake_region"). + Field("created_on", "time.Time"). + Text("account_name"). + Text("name"). + Field("comment", "sql.NullString"). + Text("is_primary"). + Text("primary"). + Text("failover_allowed_to_accounts"). + Text("connection_url"). + Text("organization_name"). + Text("account_locator"), + g.PlainStruct("Connection"). + OptionalText("RegionGroup"). + Text("SnowflakeRegion"). + Field("CreatedOn", "time.Time"). + Text("AccountName"). + Text("Name"). + OptionalText("Comment"). + Bool("IsPrimary"). + Text("Primary"). + Field("FailoverAllowedToAccounts", "[]string"). + Text("ConnectionUrl"). + Text("OrganizationName"). + Text("AccountLocator"), + g.NewQueryStruct("ShowConnections"). + Show(). + SQL("CONNECTIONS"). + OptionalLike(), +).ShowByIdOperation() diff --git a/pkg/sdk/connections_dto_builders_gen.go b/pkg/sdk/connections_dto_builders_gen.go new file mode 100644 index 0000000000..eb226b05a2 --- /dev/null +++ b/pkg/sdk/connections_dto_builders_gen.go @@ -0,0 +1,131 @@ +// Code generated by dto builder generator; DO NOT EDIT. + +package sdk + +func NewCreateConnectionRequest( + name AccountObjectIdentifier, +) *CreateConnectionRequest { + s := CreateConnectionRequest{} + s.name = name + return &s +} + +func (s *CreateConnectionRequest) WithIfNotExists(IfNotExists bool) *CreateConnectionRequest { + s.IfNotExists = &IfNotExists + return s +} + +func (s *CreateConnectionRequest) WithAsReplicaOf(AsReplicaOf ExternalObjectIdentifier) *CreateConnectionRequest { + s.AsReplicaOf = &AsReplicaOf + return s +} + +func (s *CreateConnectionRequest) WithComment(Comment string) *CreateConnectionRequest { + s.Comment = &Comment + return s +} + +func NewAlterConnectionRequest( + name AccountObjectIdentifier, +) *AlterConnectionRequest { + s := AlterConnectionRequest{} + s.name = name + return &s +} + +func (s *AlterConnectionRequest) WithIfExists(IfExists bool) *AlterConnectionRequest { + s.IfExists = &IfExists + return s +} + +func (s *AlterConnectionRequest) WithEnableConnectionFailover(EnableConnectionFailover EnableConnectionFailoverRequest) *AlterConnectionRequest { + s.EnableConnectionFailover = &EnableConnectionFailover + return s +} + +func (s *AlterConnectionRequest) WithDisableConnectionFailover(DisableConnectionFailover DisableConnectionFailoverRequest) *AlterConnectionRequest { + s.DisableConnectionFailover = &DisableConnectionFailover + return s +} + +func (s *AlterConnectionRequest) WithPrimary(Primary bool) *AlterConnectionRequest { + s.Primary = &Primary + return s +} + +func (s *AlterConnectionRequest) WithSet(Set SetRequest) *AlterConnectionRequest { + s.Set = &Set + return s +} + +func (s *AlterConnectionRequest) WithUnset(Unset UnsetRequest) *AlterConnectionRequest { + s.Unset = &Unset + return s +} + +func NewEnableConnectionFailoverRequest() *EnableConnectionFailoverRequest { + return &EnableConnectionFailoverRequest{} +} + +func (s *EnableConnectionFailoverRequest) WithToAccounts(ToAccounts []AccountIdentifier) *EnableConnectionFailoverRequest { + s.ToAccounts = ToAccounts + return s +} + +func NewDisableConnectionFailoverRequest() *DisableConnectionFailoverRequest { + return &DisableConnectionFailoverRequest{} +} + +func (s *DisableConnectionFailoverRequest) WithToAccounts(ToAccounts ToAccountsRequest) *DisableConnectionFailoverRequest { + s.ToAccounts = &ToAccounts + return s +} + +func NewToAccountsRequest() *ToAccountsRequest { + return &ToAccountsRequest{} +} + +func (s *ToAccountsRequest) WithAccounts(Accounts []AccountIdentifier) *ToAccountsRequest { + s.Accounts = Accounts + return s +} + +func NewSetRequest() *SetRequest { + return &SetRequest{} +} + +func (s *SetRequest) WithComment(Comment string) *SetRequest { + s.Comment = &Comment + return s +} + +func NewUnsetRequest() *UnsetRequest { + return &UnsetRequest{} +} + +func (s *UnsetRequest) WithComment(Comment bool) *UnsetRequest { + s.Comment = &Comment + return s +} + +func NewDropConnectionRequest( + name AccountObjectIdentifier, +) *DropConnectionRequest { + s := DropConnectionRequest{} + s.name = name + return &s +} + +func (s *DropConnectionRequest) WithIfExists(IfExists bool) *DropConnectionRequest { + s.IfExists = &IfExists + return s +} + +func NewShowConnectionRequest() *ShowConnectionRequest { + return &ShowConnectionRequest{} +} + +func (s *ShowConnectionRequest) WithLike(Like Like) *ShowConnectionRequest { + s.Like = &Like + return s +} diff --git a/pkg/sdk/connections_dto_gen.go b/pkg/sdk/connections_dto_gen.go new file mode 100644 index 0000000000..1323bf0af3 --- /dev/null +++ b/pkg/sdk/connections_dto_gen.go @@ -0,0 +1,56 @@ +package sdk + +//go:generate go run ./dto-builder-generator/main.go + +var ( + _ optionsProvider[CreateConnectionOptions] = new(CreateConnectionRequest) + _ optionsProvider[AlterConnectionOptions] = new(AlterConnectionRequest) + _ optionsProvider[DropConnectionOptions] = new(DropConnectionRequest) + _ optionsProvider[ShowConnectionOptions] = new(ShowConnectionRequest) +) + +type CreateConnectionRequest struct { + IfNotExists *bool + name AccountObjectIdentifier // required + AsReplicaOf *ExternalObjectIdentifier + Comment *string +} + +type AlterConnectionRequest struct { + IfExists *bool + name AccountObjectIdentifier // required + EnableConnectionFailover *EnableConnectionFailoverRequest + DisableConnectionFailover *DisableConnectionFailoverRequest + Primary *bool + Set *SetRequest + Unset *UnsetRequest +} + +type EnableConnectionFailoverRequest struct { + ToAccounts []AccountIdentifier +} + +type DisableConnectionFailoverRequest struct { + ToAccounts *ToAccountsRequest +} + +type ToAccountsRequest struct { + Accounts []AccountIdentifier +} + +type SetRequest struct { + Comment *string +} + +type UnsetRequest struct { + Comment *bool +} + +type DropConnectionRequest struct { + IfExists *bool + name AccountObjectIdentifier // required +} + +type ShowConnectionRequest struct { + Like *Like +} diff --git a/pkg/sdk/connections_gen.go b/pkg/sdk/connections_gen.go new file mode 100644 index 0000000000..80885df87b --- /dev/null +++ b/pkg/sdk/connections_gen.go @@ -0,0 +1,104 @@ +package sdk + +import ( + "context" + "database/sql" + "time" +) + +type Connections interface { + Create(ctx context.Context, request *CreateConnectionRequest) error + Alter(ctx context.Context, request *AlterConnectionRequest) error + Drop(ctx context.Context, request *DropConnectionRequest) error + Show(ctx context.Context, request *ShowConnectionRequest) ([]Connection, error) + ShowByID(ctx context.Context, id AccountObjectIdentifier) (*Connection, error) +} + +// CreateConnectionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-connection. +type CreateConnectionOptions struct { + create bool `ddl:"static" sql:"CREATE"` + connection bool `ddl:"static" sql:"CONNECTION"` + IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` + name AccountObjectIdentifier `ddl:"identifier"` + AsReplicaOf *ExternalObjectIdentifier `ddl:"identifier" sql:"AS REPLICA OF"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` +} + +// AlterConnectionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/alter-connection. +type AlterConnectionOptions struct { + alter bool `ddl:"static" sql:"ALTER"` + connection bool `ddl:"static" sql:"CONNECTION"` + IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` + name AccountObjectIdentifier `ddl:"identifier"` + EnableConnectionFailover *EnableConnectionFailover `ddl:"keyword" sql:"ENABLE FAILOVER TO ACCOUNTS"` + DisableConnectionFailover *DisableConnectionFailover `ddl:"keyword" sql:"DISABLE FAILOVER"` + Primary *bool `ddl:"keyword" sql:"PRIMARY"` + Set *Set `ddl:"keyword" sql:"SET"` + Unset *Unset `ddl:"keyword" sql:"UNSET"` +} +type EnableConnectionFailover struct { + ToAccounts []AccountIdentifier `ddl:"list,no_parentheses"` +} +type DisableConnectionFailover struct { + ToAccounts *ToAccounts `ddl:"keyword" sql:"TO ACCOUNTS"` +} +type ToAccounts struct { + Accounts []AccountIdentifier `ddl:"list,no_parentheses"` +} +type Set struct { + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` +} +type Unset struct { + Comment *bool `ddl:"keyword" sql:"COMMENT"` +} + +// DropConnectionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/drop-connection. +type DropConnectionOptions struct { + drop bool `ddl:"static" sql:"DROP"` + connection bool `ddl:"static" sql:"CONNECTION"` + IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` + name AccountObjectIdentifier `ddl:"identifier"` +} + +// ShowConnectionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/show-connections. +type ShowConnectionOptions struct { + show bool `ddl:"static" sql:"SHOW"` + connections bool `ddl:"static" sql:"CONNECTIONS"` + Like *Like `ddl:"keyword" sql:"LIKE"` +} +type connectionRow struct { + RegionGroup sql.NullString `db:"region_group"` + SnowflakeRegion string `db:"snowflake_region"` + CreatedOn time.Time `db:"created_on"` + AccountName string `db:"account_name"` + Name string `db:"name"` + Comment sql.NullString `db:"comment"` + IsPrimary string `db:"is_primary"` + Primary string `db:"primary"` + FailoverAllowedToAccounts string `db:"failover_allowed_to_accounts"` + ConnectionUrl string `db:"connection_url"` + OrganizationName string `db:"organization_name"` + AccountLocator string `db:"account_locator"` +} +type Connection struct { + RegionGroup *string + SnowflakeRegion string + CreatedOn time.Time + AccountName string + Name string + Comment *string + IsPrimary bool + Primary string + FailoverAllowedToAccounts []string + ConnectionUrl string + OrganizationName string + AccountLocator string +} + +func (c *Connection) ID() AccountObjectIdentifier { + return NewAccountObjectIdentifier(c.Name) +} + +func (c *Connection) ObjectType() ObjectType { + return ObjectTypeConnection +} diff --git a/pkg/sdk/connections_gen_test.go b/pkg/sdk/connections_gen_test.go new file mode 100644 index 0000000000..8ee6f7441a --- /dev/null +++ b/pkg/sdk/connections_gen_test.go @@ -0,0 +1,194 @@ +package sdk + +import "testing" + +func TestConnections_Create(t *testing.T) { + id := randomAccountObjectIdentifier() + defaultOpts := func() *CreateConnectionOptions { + return &CreateConnectionOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *CreateConnectionOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: valid identifier for [opts.name]", func(t *testing.T) { + opts := defaultOpts() + opts.name = invalidAccountObjectIdentifier + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: valid identifier for [opts.ReplicaOf]", func(t *testing.T) { + opts := defaultOpts() + opts.name = id + opts.AsReplicaOf = &emptyExternalObjectIdentifier + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("basic", func(t *testing.T) { + opts := defaultOpts() + opts.name = id + assertOptsValidAndSQLEquals(t, opts, "CREATE CONNECTION %s", id.FullyQualifiedName()) + }) + + t.Run("all options", func(t *testing.T) { + opts := defaultOpts() + opts.name = id + opts.IfNotExists = Bool(true) + opts.Comment = String("comment") + assertOptsValidAndSQLEquals(t, opts, "CREATE CONNECTION IF NOT EXISTS %s COMMENT = 'comment'", id.FullyQualifiedName()) + }) + + t.Run("as replica of", func(t *testing.T) { + externalId := randomExternalObjectIdentifier() + opts := defaultOpts() + opts.name = id + opts.AsReplicaOf = &externalId + assertOptsValidAndSQLEquals(t, opts, "CREATE CONNECTION %s AS REPLICA OF %s", id.FullyQualifiedName(), externalId.FullyQualifiedName()) + }) + + t.Run("as replica of - all options", func(t *testing.T) { + externalId := randomExternalObjectIdentifier() + opts := defaultOpts() + opts.name = id + opts.IfNotExists = Bool(true) + opts.AsReplicaOf = &externalId + opts.Comment = String("comment") + assertOptsValidAndSQLEquals(t, opts, "CREATE CONNECTION IF NOT EXISTS %s AS REPLICA OF %s COMMENT = 'comment'", id.FullyQualifiedName(), externalId.FullyQualifiedName()) + }) +} + +func TestConnections_Alter(t *testing.T) { + id := randomAccountObjectIdentifier() + defaultOpts := func() *AlterConnectionOptions { + return &AlterConnectionOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *AlterConnectionOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + t.Run("validation: exactly one field from [opts.EnableConnectionFailover opts.DisableConnectionFailover opts.Primary opts.Set opts.Unset] should be present", func(t *testing.T) { + opts := defaultOpts() + opts.EnableConnectionFailover = &EnableConnectionFailover{} + opts.DisableConnectionFailover = &DisableConnectionFailover{} + opts.Primary = Bool(true) + opts.Set = &Set{} + opts.Unset = &Unset{} + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AlterConnectionOptions", "EnableConnectionFailover", "DisableConnectionFailover", "Primary", "Set", "Unset")) + }) + + t.Run("validation: at least one of the fields [opts.Set.Comment] should be set", func(t *testing.T) { + opts := defaultOpts() + opts.Set = &Set{} + assertOptsInvalidJoinedErrors(t, opts, errAtLeastOneOf("AlterConnectionOptions.Set", "Comment")) + }) + + t.Run("validation: at least one of the fields [opts.Unset.Comment] should be set", func(t *testing.T) { + opts := defaultOpts() + opts.Unset = &Unset{} + assertOptsInvalidJoinedErrors(t, opts, errAtLeastOneOf("AlterConnectionOptions.Unset", "Comment")) + }) + + t.Run("alter enable failover to accounts", func(t *testing.T) { + accountIdentifier := randomAccountIdentifier() + secondAccountIdentifier := randomAccountIdentifier() + opts := defaultOpts() + opts.EnableConnectionFailover = &EnableConnectionFailover{ + ToAccounts: []AccountIdentifier{accountIdentifier, secondAccountIdentifier}, + } + assertOptsValidAndSQLEquals(t, opts, "ALTER CONNECTION %s ENABLE FAILOVER TO ACCOUNTS %s, %s", id.FullyQualifiedName(), accountIdentifier.FullyQualifiedName(), secondAccountIdentifier.FullyQualifiedName()) + }) + + t.Run("alter disable failover to all accounts", func(t *testing.T) { + opts := defaultOpts() + opts.DisableConnectionFailover = &DisableConnectionFailover{} + assertOptsValidAndSQLEquals(t, opts, "ALTER CONNECTION %s DISABLE FAILOVER", id.FullyQualifiedName()) + }) + + t.Run("alter disable failover to accounts", func(t *testing.T) { + accountIdentifier := randomAccountIdentifier() + opts := defaultOpts() + opts.DisableConnectionFailover = &DisableConnectionFailover{ + ToAccounts: &ToAccounts{[]AccountIdentifier{accountIdentifier}}, + } + assertOptsValidAndSQLEquals(t, opts, "ALTER CONNECTION %s DISABLE FAILOVER TO ACCOUNTS %s", id.FullyQualifiedName(), accountIdentifier.FullyQualifiedName()) + }) + + t.Run("primary", func(t *testing.T) { + opts := defaultOpts() + opts.Primary = Bool(true) + assertOptsValidAndSQLEquals(t, opts, "ALTER CONNECTION %s PRIMARY", id.FullyQualifiedName()) + }) + + t.Run("set comment", func(t *testing.T) { + opts := defaultOpts() + opts.Set = &Set{Comment: String("test comment")} + assertOptsValidAndSQLEquals(t, opts, "ALTER CONNECTION %s SET COMMENT = 'test comment'", id.FullyQualifiedName()) + }) + + t.Run("unset comment", func(t *testing.T) { + opts := defaultOpts() + opts.Unset = &Unset{Comment: Bool(true)} + assertOptsValidAndSQLEquals(t, opts, "ALTER CONNECTION %s UNSET COMMENT", id.FullyQualifiedName()) + }) +} + +func TestConnections_Drop(t *testing.T) { + id := randomAccountObjectIdentifier() + defaultOpts := func() *DropConnectionOptions { + return &DropConnectionOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *DropConnectionOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + t.Run("validation: valid identifier for [opts.name]", func(t *testing.T) { + opts := defaultOpts() + opts.name = emptyAccountObjectIdentifier + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("basic", func(t *testing.T) { + opts := defaultOpts() + assertOptsValidAndSQLEquals(t, opts, "DROP CONNECTION %s", id.FullyQualifiedName()) + }) + + t.Run("all options", func(t *testing.T) { + opts := defaultOpts() + opts.IfExists = Bool(true) + assertOptsValidAndSQLEquals(t, opts, "DROP CONNECTION IF EXISTS %s", id.FullyQualifiedName()) + }) +} + +func TestConnections_Show(t *testing.T) { + defaultOpts := func() *ShowConnectionOptions { + return &ShowConnectionOptions{} + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *ShowConnectionOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("basic", func(t *testing.T) { + opts := defaultOpts() + assertOptsValidAndSQLEquals(t, opts, "SHOW CONNECTIONS") + }) + + t.Run("all options", func(t *testing.T) { + opts := defaultOpts() + opts.Like = &Like{ + String("test_connection_name"), + } + assertOptsValidAndSQLEquals(t, opts, "SHOW CONNECTIONS LIKE 'test_connection_name'") + }) +} diff --git a/pkg/sdk/connections_impl_gen.go b/pkg/sdk/connections_impl_gen.go new file mode 100644 index 0000000000..325141116e --- /dev/null +++ b/pkg/sdk/connections_impl_gen.go @@ -0,0 +1,139 @@ +package sdk + +import ( + "context" + "strconv" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" +) + +var _ Connections = (*connections)(nil) + +type connections struct { + client *Client +} + +func (v *connections) Create(ctx context.Context, request *CreateConnectionRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *connections) Alter(ctx context.Context, request *AlterConnectionRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *connections) Drop(ctx context.Context, request *DropConnectionRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *connections) Show(ctx context.Context, request *ShowConnectionRequest) ([]Connection, error) { + opts := request.toOpts() + dbRows, err := validateAndQuery[connectionRow](v.client, ctx, opts) + if err != nil { + return nil, err + } + resultList := convertRows[connectionRow, Connection](dbRows) + return resultList, nil +} + +func (v *connections) ShowByID(ctx context.Context, id AccountObjectIdentifier) (*Connection, error) { + connections, err := v.Show(ctx, NewShowConnectionRequest().WithLike( + Like{ + Pattern: String(id.Name()), + })) + if err != nil { + return nil, err + } + return collections.FindFirst(connections, func(r Connection) bool { return r.Name == id.Name() }) +} + +func (r *CreateConnectionRequest) toOpts() *CreateConnectionOptions { + opts := &CreateConnectionOptions{ + IfNotExists: r.IfNotExists, + name: r.name, + + Comment: r.Comment, + } + + if r.AsReplicaOf != nil { + opts.AsReplicaOf = r.AsReplicaOf + } + + return opts +} + +func (r *AlterConnectionRequest) toOpts() *AlterConnectionOptions { + opts := &AlterConnectionOptions{ + IfExists: r.IfExists, + name: r.name, + Primary: r.Primary, + } + + if r.EnableConnectionFailover != nil { + opts.EnableConnectionFailover = &EnableConnectionFailover{ + ToAccounts: r.EnableConnectionFailover.ToAccounts, + } + } + + if r.DisableConnectionFailover != nil { + opts.DisableConnectionFailover = &DisableConnectionFailover{} + + if r.DisableConnectionFailover.ToAccounts != nil { + opts.DisableConnectionFailover.ToAccounts = &ToAccounts{ + Accounts: r.DisableConnectionFailover.ToAccounts.Accounts, + } + } + } + + if r.Set != nil { + opts.Set = &Set{ + Comment: r.Set.Comment, + } + } + + if r.Unset != nil { + opts.Unset = &Unset{ + Comment: r.Unset.Comment, + } + } + + return opts +} + +func (r *DropConnectionRequest) toOpts() *DropConnectionOptions { + opts := &DropConnectionOptions{ + IfExists: r.IfExists, + name: r.name, + } + return opts +} + +func (r *ShowConnectionRequest) toOpts() *ShowConnectionOptions { + opts := &ShowConnectionOptions{ + Like: r.Like, + } + return opts +} + +func (r connectionRow) convert() *Connection { + c := &Connection{ + SnowflakeRegion: r.SnowflakeRegion, + CreatedOn: r.CreatedOn, + AccountName: r.AccountName, + Name: r.Name, + Primary: r.Primary, + FailoverAllowedToAccounts: ParseCommaSeparatedStringArray(r.FailoverAllowedToAccounts, false), + ConnectionUrl: r.ConnectionUrl, + OrganizationName: r.OrganizationName, + AccountLocator: r.AccountLocator, + } + b, _ := strconv.ParseBool(r.IsPrimary) + c.IsPrimary = b + if r.Comment.Valid { + c.Comment = String(r.Comment.String) + } + + return c +} diff --git a/pkg/sdk/connections_validations_gen.go b/pkg/sdk/connections_validations_gen.go new file mode 100644 index 0000000000..df6b4e1d0f --- /dev/null +++ b/pkg/sdk/connections_validations_gen.go @@ -0,0 +1,62 @@ +package sdk + +var ( + _ validatable = new(CreateConnectionOptions) + _ validatable = new(AlterConnectionOptions) + _ validatable = new(DropConnectionOptions) + _ validatable = new(ShowConnectionOptions) +) + +func (opts *CreateConnectionOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if opts.AsReplicaOf != nil && !ValidObjectIdentifier(opts.AsReplicaOf) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + return JoinErrors(errs...) +} + +func (opts *AlterConnectionOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !exactlyOneValueSet(opts.EnableConnectionFailover, opts.DisableConnectionFailover, opts.Primary, opts.Set, opts.Unset) { + errs = append(errs, errExactlyOneOf("AlterConnectionOptions", "EnableConnectionFailover", "DisableConnectionFailover", "Primary", "Set", "Unset")) + } + if valueSet(opts.Set) { + if !anyValueSet(opts.Set.Comment) { + errs = append(errs, errAtLeastOneOf("AlterConnectionOptions.Set", "Comment")) + } + } + if valueSet(opts.Unset) { + if !anyValueSet(opts.Unset.Comment) { + errs = append(errs, errAtLeastOneOf("AlterConnectionOptions.Unset", "Comment")) + } + } + return JoinErrors(errs...) +} + +func (opts *DropConnectionOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + return JoinErrors(errs...) +} + +func (opts *ShowConnectionOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + return JoinErrors(errs...) +} diff --git a/pkg/sdk/poc/main.go b/pkg/sdk/poc/main.go index 72c675a73f..6787167c25 100644 --- a/pkg/sdk/poc/main.go +++ b/pkg/sdk/poc/main.go @@ -46,6 +46,7 @@ var definitionMapping = map[string]*generator.Interface{ "external_volumes_def.go": sdk.ExternalVolumesDef, "authentication_policies_def.go": sdk.AuthenticationPoliciesDef, "secrets_def.go": sdk.SecretsDef, + "connections_def.go": sdk.ConnectionDef, } func main() { diff --git a/pkg/sdk/random_test.go b/pkg/sdk/random_test.go index 0f58dc3a79..552eb68c15 100644 --- a/pkg/sdk/random_test.go +++ b/pkg/sdk/random_test.go @@ -10,6 +10,7 @@ var ( // TODO: Add to the generator emptyAccountObjectIdentifier = NewAccountObjectIdentifier("") + emptyExternalObjectIdentifier = NewExternalObjectIdentifier(NewAccountIdentifier("", ""), NewObjectIdentifierFromFullyQualifiedName("")) emptyDatabaseObjectIdentifier = NewDatabaseObjectIdentifier("", "") emptySchemaObjectIdentifier = NewSchemaObjectIdentifier("", "", "") emptySchemaObjectIdentifierWithArguments = NewSchemaObjectIdentifierWithArguments("", "", "") @@ -39,6 +40,10 @@ func randomDatabaseObjectIdentifierInDatabase(databaseId AccountObjectIdentifier return NewDatabaseObjectIdentifier(databaseId.Name(), random.StringN(12)) } +func randomAccountIdentifier() AccountIdentifier { + return NewAccountIdentifier(random.StringN(12), random.StringN(12)) +} + func randomAccountObjectIdentifier() AccountObjectIdentifier { return NewAccountObjectIdentifier(random.StringN(12)) } diff --git a/pkg/sdk/testint/connections_gen_integration_test.go b/pkg/sdk/testint/connections_gen_integration_test.go new file mode 100644 index 0000000000..79573d1b6d --- /dev/null +++ b/pkg/sdk/testint/connections_gen_integration_test.go @@ -0,0 +1,364 @@ +package testint + +import ( + "fmt" + "strings" + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/stretchr/testify/require" + + assertions "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert/objectassert" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testenvs" +) + +const ConnectionFailoverToAccountInSameRegionErrorMessage = "The connection cannot be failed over to an account in the same region" + +func TestInt_Connections(t *testing.T) { + client := testClient(t) + secondaryClient := testSecondaryClient(t) + ctx := testContext(t) + + sessionDetails, err := client.ContextFunctions.CurrentSessionDetails(ctx) + require.NoError(t, err) + accountId := testClientHelper().Account.GetAccountIdentifier(t) + + t.Run("Create minimal", func(t *testing.T) { + // TODO: [SNOW-1002023]: Unskip; Business Critical Snowflake Edition needed + _ = testenvs.GetOrSkipTest(t, testenvs.TestFailoverGroups) + + id := testClientHelper().Ids.RandomAccountObjectIdentifier() + require.NoError(t, err) + + err = client.Connections.Create(ctx, sdk.NewCreateConnectionRequest(id)) + require.NoError(t, err) + t.Cleanup(testClientHelper().Connection.DropFunc(t, id)) + + externalObjectIdentifier := sdk.NewExternalObjectIdentifier(accountId, id) + assertions.AssertThatObject(t, objectassert.Connection(t, id). + HasSnowflakeRegion(sessionDetails.Region). + HasAccountName(sessionDetails.AccountName). + HasName(id.Name()). + HasNoComment(). + HasIsPrimary(true). + HasPrimaryIdentifier(externalObjectIdentifier). + HasFailoverAllowedToAccounts( + []string{ + accountId.Name(), + }, + ). + HasOrganizationName(sessionDetails.OrganizationName). + HasAccountLocator(client.GetAccountLocator()). + HasConnectionUrl( + strings.ToLower( + fmt.Sprintf("%s-%s.snowflakecomputing.com", sessionDetails.OrganizationName, id.Name()), + ), + ), + ) + }) + + t.Run("Create all options", func(t *testing.T) { + // TODO: [SNOW-1002023]: Unskip; Business Critical Snowflake Edition needed + _ = testenvs.GetOrSkipTest(t, testenvs.TestFailoverGroups) + + id := testClientHelper().Ids.RandomAccountObjectIdentifier() + err := client.Connections.Create(ctx, sdk.NewCreateConnectionRequest(id). + WithIfNotExists(true). + WithComment("test comment for connection")) + require.NoError(t, err) + t.Cleanup(testClientHelper().Connection.DropFunc(t, id)) + + externalObjectIdentifier := sdk.NewExternalObjectIdentifier(accountId, id) + assertions.AssertThatObject(t, objectassert.Connection(t, id). + HasSnowflakeRegion(sessionDetails.Region). + HasAccountName(sessionDetails.AccountName). + HasName(id.Name()). + HasComment("test comment for connection"). + HasIsPrimary(true). + HasPrimaryIdentifier(externalObjectIdentifier). + HasFailoverAllowedToAccounts( + []string{ + accountId.Name(), + }, + ). + HasOrganizationName(sessionDetails.OrganizationName). + HasAccountLocator(client.GetAccountLocator()). + HasConnectionUrl( + strings.ToLower( + fmt.Sprintf("%s-%s.snowflakecomputing.com", sessionDetails.OrganizationName, id.Name()), + ), + ), + ) + }) + + t.Run("Alter enable failover", func(t *testing.T) { + // TODO: [SNOW-1002023]: Unskip; Business Critical Snowflake Edition needed + _ = testenvs.GetOrSkipTest(t, testenvs.TestFailoverGroups) + + id := testClientHelper().Ids.RandomAccountObjectIdentifier() + secondaryAccountId := secondaryTestClientHelper().Ids.AccountIdentifierWithLocator() + + _, connectionCleanup := testClientHelper().Connection.Create(t, id) + t.Cleanup(connectionCleanup) + + err := client.Connections.Alter(ctx, sdk.NewAlterConnectionRequest(id). + WithEnableConnectionFailover( + *sdk.NewEnableConnectionFailoverRequest().WithToAccounts( + []sdk.AccountIdentifier{ + secondaryAccountId, + }, + ), + ), + ) + require.ErrorContains(t, err, ConnectionFailoverToAccountInSameRegionErrorMessage) + + // TODO: [SNOW-1763442] + /* + require.NoError(t, err) + externalObjectIdentifier := sdk.NewExternalObjectIdentifier(accountId, id) + assertions.AssertThatObject(t, objectassert.Connection(t, id). + HasSnowflakeRegion(sessionDetails.Region). + HasAccountName(sessionDetails.AccountName). + HasName(id.Name()). + HasNoComment(). + HasIsPrimary(true). + HasPrimaryIdentifier(externalObjectIdentifier). + HasFailoverAllowedToAccounts( + []string{ + accountId.Name(), + secondaryAccountId.Name(), + }, + ). + HasOrganizationName(sessionDetails.OrganizationName). + HasAccountLocator(client.GetAccountLocator()), + HasConnectionUrl( + strings.ToLower( + fmt.Sprintf("%s-%s.snowflakecomputing.com", sessionDetails.OrganizationName, id.Name()), + ), + ), + ) + */ + }) + + t.Run("Create as replica of", func(t *testing.T) { + // TODO: [SNOW-1002023]: Unskip; Business Critical Snowflake Edition needed + _ = testenvs.GetOrSkipTest(t, testenvs.TestFailoverGroups) + + secondaryAccountId := secondaryTestClientHelper().Ids.AccountIdentifierWithLocator() + + primaryConn, connectionCleanup := testClientHelper().Connection.Create(t, testClientHelper().Ids.RandomAccountObjectIdentifier()) + t.Cleanup(connectionCleanup) + + err := client.Connections.Alter(ctx, sdk.NewAlterConnectionRequest(primaryConn.ID()). + WithEnableConnectionFailover( + *sdk.NewEnableConnectionFailoverRequest().WithToAccounts( + []sdk.AccountIdentifier{ + secondaryAccountId, + }, + ), + ), + ) + require.ErrorContains(t, err, ConnectionFailoverToAccountInSameRegionErrorMessage) + // TODO: [SNOW-1763442] + // + // require.NoError(t, err) + + // create replica on secondary account + /* + externalObjectIdentifier := sdk.NewExternalObjectIdentifier(accountId, id) + err = secondaryClient.Connections.Create(ctx, sdk.NewCreateConnectionRequest(id). + WithAsReplicaOf(sdk.AsReplicaOfRequest{ + AsReplicaOf: externalObjectIdentifier, + })) + t.Cleanup(testClientHelper().Connection.DropFunc(t, id)) + require.NoError(t, err) + + assertions.AssertThatObject(t, objectassert.Connection(t, id). + HasSnowflakeRegion(sessionDetails.Region). + HasAccountName(sessionDetails.AccountName). + HasName(id.Name()). + HasNoComment(). + HasIsPrimary(false). + HasPrimaryIdentifier(externalObjectIdentifier). + HasFailoverAllowedToAccounts( + []string{ + accountId.Name(), + secondaryAccountId.Name(), + }, + ). + HasOrganizationName(sessionDetails.OrganizationName). + HasAccountLocator(client.GetAccountLocator()). + HasConnectionUrl( + strings.ToLower( + fmt.Sprintf("%s-%s.snowflakecomputing.com", sessionDetails.OrganizationName, id.Name()), + ), + ), + ) + */ + }) + + t.Run("Alter disable failover", func(t *testing.T) { + // TODO: [SNOW-1763442]: Unskip; Business Critical Snowflake Edition needed + _ = testenvs.GetOrSkipTest(t, testenvs.TestFailoverGroups) + + id := testClientHelper().Ids.RandomAccountObjectIdentifier() + secondaryAccountId := secondaryTestClientHelper().Account.GetAccountIdentifier(t) + + primaryConn, connectionCleanup := testClientHelper().Connection.Create(t, id) + t.Cleanup(connectionCleanup) + + // Add secondary account to failover list + err := client.Connections.Alter(ctx, sdk.NewAlterConnectionRequest(id). + WithEnableConnectionFailover( + *sdk.NewEnableConnectionFailoverRequest().WithToAccounts( + []sdk.AccountIdentifier{ + secondaryAccountId, + }, + ), + ), + ) + require.ErrorContains(t, err, ConnectionFailoverToAccountInSameRegionErrorMessage) + // TODO: [SNOW-1763442] + // + // require.NoError(t, err) + + // Disable promotion of this connection + err = client.Connections.Alter(ctx, sdk.NewAlterConnectionRequest(id). + WithDisableConnectionFailover(*sdk.NewDisableConnectionFailoverRequest())) + require.NoError(t, err) + + // Assert that promotion for other account has been disabled + externalObjectIdentifier := sdk.NewExternalObjectIdentifier(accountId, id) + assertions.AssertThatObject(t, objectassert.Connection(t, primaryConn.ID()). + HasPrimaryIdentifier(externalObjectIdentifier). + HasFailoverAllowedToAccounts( + []string{ + accountId.Name(), + }, + ), + ) + + // Try to create repllication on secondary account + err = secondaryClient.Connections.Create(ctx, sdk.NewCreateConnectionRequest(id).WithAsReplicaOf(externalObjectIdentifier)) + require.ErrorContains(t, err, "This account is not authorized to create a secondary connection of this primary connection") + }) + + t.Run("Alter comment", func(t *testing.T) { + // TODO: [SNOW-1002023]: Unskip; Business Critical Snowflake Edition needed + _ = testenvs.GetOrSkipTest(t, testenvs.TestFailoverGroups) + + id := testClientHelper().Ids.RandomAccountObjectIdentifier() + _, connectionCleanup := testClientHelper().Connection.Create(t, id) + t.Cleanup(connectionCleanup) + + // Set + err := client.Connections.Alter(ctx, sdk.NewAlterConnectionRequest(id). + WithSet(*sdk.NewSetRequest(). + WithComment("new integration test comment"))) + require.NoError(t, err) + + assertions.AssertThatObject(t, objectassert.Connection(t, id). + HasName(id.Name()). + HasComment("new integration test comment"), + ) + + // Unset + err = client.Connections.Alter(ctx, sdk.NewAlterConnectionRequest(id). + WithUnset(*sdk.NewUnsetRequest(). + WithComment(true))) + require.NoError(t, err) + + assertions.AssertThatObject(t, objectassert.Connection(t, id). + HasName(id.Name()). + HasNoComment(), + ) + }) + + t.Run("Drop", func(t *testing.T) { + // TODO: [SNOW-1002023]: Unskip; Business Critical Snowflake Edition needed + _ = testenvs.GetOrSkipTest(t, testenvs.TestFailoverGroups) + + id := testClientHelper().Ids.RandomAccountObjectIdentifier() + _, connectionCleanup := testClientHelper().Connection.Create(t, id) + t.Cleanup(connectionCleanup) + + connection, err := client.Connections.ShowByID(ctx, id) + require.NoError(t, err) + require.NotNil(t, connection) + + err = client.Connections.Drop(ctx, sdk.NewDropConnectionRequest(id)) + require.NoError(t, err) + + connection, err = client.Connections.ShowByID(ctx, id) + require.Nil(t, connection) + require.Error(t, err) + }) + + t.Run("Drop with if exists", func(t *testing.T) { + // TODO: [SNOW-1002023]: Unskip; Business Critical Snowflake Edition needed + _ = testenvs.GetOrSkipTest(t, testenvs.TestFailoverGroups) + + err = client.Connections.Drop(ctx, sdk.NewDropConnectionRequest(NonExistingAccountObjectIdentifier)) + require.ErrorIs(t, err, sdk.ErrObjectNotExistOrAuthorized) + + err = client.Connections.Drop(ctx, sdk.NewDropConnectionRequest(NonExistingAccountObjectIdentifier).WithIfExists(true)) + require.NoError(t, err) + }) + + t.Run("Show", func(t *testing.T) { + // TODO: [SNOW-1002023]: Unskip; Business Critical Snowflake Edition needed + _ = testenvs.GetOrSkipTest(t, testenvs.TestFailoverGroups) + + id1 := testClientHelper().Ids.RandomAccountObjectIdentifier() + id2 := testClientHelper().Ids.RandomAccountObjectIdentifier() + + connection1, connectionCleanup1 := testClientHelper().Connection.Create(t, id1) + t.Cleanup(connectionCleanup1) + + connection2, connectionCleanup2 := testClientHelper().Connection.Create(t, id2) + t.Cleanup(connectionCleanup2) + + returnedConnections, err := client.Connections.Show(ctx, sdk.NewShowConnectionRequest()) + require.NoError(t, err) + require.Contains(t, returnedConnections, *connection1) + require.Contains(t, returnedConnections, *connection2) + }) + + t.Run("Show with Like", func(t *testing.T) { + // TODO: [SNOW-1002023]: Unskip; Business Critical Snowflake Edition needed + _ = testenvs.GetOrSkipTest(t, testenvs.TestFailoverGroups) + + id1 := testClientHelper().Ids.RandomAccountObjectIdentifier() + id2 := testClientHelper().Ids.RandomAccountObjectIdentifier() + + connection1, connectionCleanup1 := testClientHelper().Connection.Create(t, id1) + t.Cleanup(connectionCleanup1) + + connection2, connectionCleanup2 := testClientHelper().Connection.Create(t, id2) + t.Cleanup(connectionCleanup2) + + returnedConnections, err := client.Connections.Show(ctx, sdk.NewShowConnectionRequest(). + WithLike(sdk.Like{ + Pattern: sdk.String(id1.Name()), + })) + require.NoError(t, err) + require.Contains(t, returnedConnections, *connection1) + require.NotContains(t, returnedConnections, *connection2) + }) + + t.Run("ShowByID", func(t *testing.T) { + // TODO: [SNOW-1002023]: Unskip; Business Critical Snowflake Edition needed + _ = testenvs.GetOrSkipTest(t, testenvs.TestFailoverGroups) + + id := testClientHelper().Ids.RandomAccountObjectIdentifier() + + _, connectionCleanup := testClientHelper().Connection.Create(t, id) + t.Cleanup(connectionCleanup) + + returnedConnection, err := client.Connections.ShowByID(ctx, id) + require.NoError(t, err) + require.Equal(t, id, returnedConnection.ID()) + require.Equal(t, id.Name(), returnedConnection.Name) + }) +}