diff --git a/README.md b/README.md index 5b4e49a116..aed701d47f 100644 --- a/README.md +++ b/README.md @@ -66,59 +66,59 @@ Integration status - indicates if given resource / datasource is using new SDK. | Object Type | SDK migration status | Resource name | Datasource name | Integration status | |-------------------------------------|----------------------|------------------------------------------------|-------------------------------|--------------------| | Account | ✅ | snowflake_account | snowflake_account | ✅ | -| Managed Account | ❌ | snowflake_managed_account | snowflake_managed_account | ❌ | +| Managed Account | 👨‍💻 | snowflake_managed_account | snowflake_managed_account | ❌ | | User | ✅ | snowflake_user | snowflake_user | ✅ | | Database Role | ✅ | snowflake_database_role | snowflake_database_role | ✅ | | Role | ✅ | snowflake_role | snowflake_role | 👨‍💻 | -| Grant Privilege to Application Role | ❌ | snowflake_grant_privileges_to_application_role | snowflake_grants | ❌ | -| Grant Privilege to Database Role | ✅ | snowflake_grant_privileges_to_database_role | snowflake_grants | 👨‍💻 | -| Grant Privilege to Role | ❌ | snowflake_grant_privileges_to_role | snowflake_grants | ✅ | -| Grant Role | ❌ | snowflake_grant_role | snowflake_grants | ❌ | -| Grant Database Role | ✅ | snowflake_grant_database_role | snowflake_grants | ❌ | -| Grant Application Role | ❌ | snowflake_grant_application_role | snowflake_grants | ❌ | +| Grant Privilege to Application Role | ✅ | snowflake_grant_privileges_to_application_role | snowflake_grants | ❌ | +| Grant Privilege to Database Role | ✅ | snowflake_grant_privileges_to_database_role | snowflake_grants | ✅ | +| Grant Privilege to Role | ✅ | snowflake_grant_privileges_to_role | snowflake_grants | ✅ | +| Grant Role | ✅ | snowflake_grant_role | snowflake_grants | 👨‍💻 | +| Grant Database Role | ✅ | snowflake_grant_database_role | snowflake_grants | 👨‍💻 | +| Grant Application Role | ✅ | snowflake_grant_application_role | snowflake_grants | ❌ | | Grant Privilege to Share | ✅ | snowflake_grant_privileges_to_share | snowflake_grants | ❌ | | Grant Ownership | ✅ | snowflake_grant_ownership | snowflake_grants | ❌ | | API Integration | ❌ | snowflake_api_integration | snowflake_integrations | ❌ | | Notification Integration | ❌ | snowflake_notification_integration | snowflake_integrations | ❌ | | Security Integration | ❌ | snowflake_security_integration | snowflake_integrations | ❌ | -| Storage Integration | ❌ | snowflake_storage_integration | snowflake_integrations | ❌ | +| Storage Integration | ✅ | snowflake_storage_integration | snowflake_integrations | ❌ | | Network Policy | ✅ | snowflake_network_policy | snowflake_network_policy | ✅ | | Password Policy | ✅ | snowflake_password_policy | snowflake_password_policy | ✅ | | Session Policy | ✅ | snowflake_session_policy | snowflake_session_policy | ❌ | | Replication Group | ❌ | snowflake_replication_group | snowflake_replication_group | ❌ | | Failover Group | ✅ | snowflake_failover_group | snowflake_failover_group | ✅ | | Connection | ❌ | snowflake_connection | snowflake_connection | ❌ | -| Account Parameters | ✅ | snowflake_account_parameter | snowflake_parameters | ❌ | -| Session Parameters | ✅ | snowflake_session_parameter | snowflake_parameters | ❌ | -| Object Parameters | ✅ | snowflake_object_parameter | snowflake_parameters | ❌ | +| Account Parameters | ✅ | snowflake_account_parameter | snowflake_parameters | ✅ | +| Session Parameters | ✅ | snowflake_session_parameter | snowflake_parameters | ✅ | +| Object Parameters | ✅ | snowflake_object_parameter | snowflake_parameters | ✅ | | Warehouse | ✅ | snowflake_warehouse | snowflake_warehouse | 🟨 | | Resource Monitor | ✅ | snowflake_resource_monitor | snowflake_resource_monitor | ✅ | | Database | ✅ | snowflake_database | snowflake_database | ✅ | | Schema | ✅ | snowflake_schema | snowflake_schema | ✅ | | Share | ✅ | snowflake_share | snowflake_share | ✅ | -| Table | 👨‍💻 | snowflake_table | snowflake_table | ❌ | -| Dynamic Table | ✅ | snowflake_dynamic_table | snowflake_dynamic_table | ❌ | -| External Table | ✅ | snowflake_external_table | snowflake_external_table | ❌ | -| Event Table | ❌ | snowflake_event_table | snowflake_event_table | ❌ | -| View | ❌ | snowflake_view | snowflake_view | ❌ | +| Table | ✅ | snowflake_table | snowflake_table | ❌ | +| Dynamic Table | ✅ | snowflake_dynamic_table | snowflake_dynamic_table | ✅ | +| External Table | ✅ | snowflake_external_table | snowflake_external_table | ✅ | +| Event Table | ✅ | snowflake_event_table | snowflake_event_table | ❌ | +| View | ✅ | snowflake_view | snowflake_view | ❌ | | Materialized View | ❌ | snowflake_materialized_view | snowflake_materialized_view | ❌ | | Sequence | ❌ | snowflake_sequence | snowflake_sequence | ❌ | -| Function | ❌ | snowflake_function | snowflake_function | ❌ | -| External Function | ❌ | snowflake_external_function | snowflake_external_function | ❌ | -| Stored Procedure | ❌ | snowflake_stored_procedure | snowflake_stored_procedure | ❌ | +| Function | ✅ | snowflake_function | snowflake_function | ❌ | +| External Function | ✅ | snowflake_external_function | snowflake_external_function | ❌ | +| Stored Procedure | ✅ | snowflake_stored_procedure | snowflake_stored_procedure | ❌ | | Stream | ✅ | snowflake_stream | snowflake_stream | ✅ | -| Task | ✅ | snowflake_task | snowflake_task | ❌ | +| Task | ✅ | snowflake_task | snowflake_task | ✅ | | Masking Policy | ✅ | snowflake_masking_policy | snowflake_masking_policy | ✅ | -| Row Access Policy | ❌ | snowflake_row_access_policy | snowflake_row_access_policy | ❌ | +| Row Access Policy | 👨‍💻 | snowflake_row_access_policy | snowflake_row_access_policy | ❌ | | Tag | ✅ | snowflake_tag | snowflake_tag | ❌ | | Secret | ❌ | snowflake_secret | snowflake_secret | ❌ | -| Stage | ❌ | snowflake_stage | snowflake_stage | ❌ | +| Stage | 🟨 | snowflake_stage | snowflake_stage | ❌ | | File Format | ✅ | snowflake_file_format | snowflake_file_format | ✅ | | Pipe | ✅ | snowflake_pipe | snowflake_pipe | ✅ | | Alert | ✅ | snowflake_alert | snowflake_alert | ✅ | -| Application | ❌ | snowflake_application | snowflake_application | ❌ | -| Application Package | ❌ | snowflake_application_package | snowflake_application_package | ❌ | -| Application Role | ❌ | snowflake_application_role | snowflake_application_role | ❌ | +| Application | 👨‍💻 | snowflake_application | snowflake_application | ❌ | +| Application Package | 👨‍💻 | snowflake_application_package | snowflake_application_package | ❌ | +| Application Role | ✅ | snowflake_application_role | snowflake_application_role | ❌ | | Streamlit | ❌ | snowflake_streamlit | snowflake_streamlit | ❌ | | Versioned Schema | ❌ | snowflake_versioned_schema | snowflake_versioned_schema | ❌ | | Tag Association | ❌ | snowflake_tag_association | snowflake_tag_association | ❌ | diff --git a/pkg/sdk/client.go b/pkg/sdk/client.go index b8170d81e8..896a3cccd7 100644 --- a/pkg/sdk/client.go +++ b/pkg/sdk/client.go @@ -38,39 +38,40 @@ type Client struct { ReplicationFunctions ReplicationFunctions // DDL Commands - Accounts Accounts - Alerts Alerts - ApplicationRoles ApplicationRoles - Comments Comments - DatabaseRoles DatabaseRoles - Databases Databases - DynamicTables DynamicTables - ExternalTables ExternalTables - EventTables EventTables - FailoverGroups FailoverGroups - FileFormats FileFormats - Functions Functions - Grants Grants - MaskingPolicies MaskingPolicies - NetworkPolicies NetworkPolicies - Parameters Parameters - PasswordPolicies PasswordPolicies - Pipes Pipes - Procedures Procedures - ResourceMonitors ResourceMonitors - Roles Roles - Schemas Schemas - SessionPolicies SessionPolicies - Sessions Sessions - Shares Shares - Stages Stages - Streams Streams - Tables Tables - Tags Tags - Tasks Tasks - Users Users - Views Views - Warehouses Warehouses + Accounts Accounts + Alerts Alerts + ApplicationRoles ApplicationRoles + Comments Comments + DatabaseRoles DatabaseRoles + Databases Databases + DynamicTables DynamicTables + ExternalTables ExternalTables + EventTables EventTables + FailoverGroups FailoverGroups + FileFormats FileFormats + Functions Functions + Grants Grants + MaskingPolicies MaskingPolicies + NetworkPolicies NetworkPolicies + Parameters Parameters + PasswordPolicies PasswordPolicies + Pipes Pipes + Procedures Procedures + ResourceMonitors ResourceMonitors + Roles Roles + Schemas Schemas + SessionPolicies SessionPolicies + Sessions Sessions + Shares Shares + Stages Stages + StorageIntegrations StorageIntegrations + Streams Streams + Tables Tables + Tags Tags + Tasks Tasks + Users Users + Views Views + Warehouses Warehouses } func (c *Client) GetAccountLocator() string { @@ -206,6 +207,7 @@ func (c *Client) initialize() { c.Sessions = &sessions{client: c} c.Shares = &shares{client: c} c.Stages = &stages{client: c} + c.StorageIntegrations = &storageIntegrations{client: c} c.Streams = &streams{client: c} c.SystemFunctions = &systemFunctions{client: c} c.Tables = &tables{client: c} diff --git a/pkg/sdk/poc/generator/db_struct.go b/pkg/sdk/poc/generator/db_struct.go index 03490dc4ae..8e1a53f379 100644 --- a/pkg/sdk/poc/generator/db_struct.go +++ b/pkg/sdk/poc/generator/db_struct.go @@ -29,6 +29,10 @@ func (v *dbStruct) Text(dbName string) *dbStruct { return v.Field(dbName, "string") } +func (v *dbStruct) Time(dbName string) *dbStruct { + return v.Field(dbName, "time.Time") +} + func (v *dbStruct) OptionalText(dbName string) *dbStruct { return v.Field(dbName, "sql.NullString") } diff --git a/pkg/sdk/poc/generator/field_transformers.go b/pkg/sdk/poc/generator/field_transformers.go index f0a3be6513..0178ba829f 100644 --- a/pkg/sdk/poc/generator/field_transformers.go +++ b/pkg/sdk/poc/generator/field_transformers.go @@ -6,6 +6,25 @@ type FieldTransformer interface { Transform(f *Field) *Field } +func StaticOptions() *StaticTransformer { + return new(StaticTransformer) +} + +type StaticTransformer struct { + sqlPrefix string +} + +func (v *StaticTransformer) SQL(sqlPrefix string) *StaticTransformer { + v.sqlPrefix = sqlPrefix + return v +} + +func (v *StaticTransformer) Transform(f *Field) *Field { + addTagIfMissing(f.Tags, "ddl", "static") + addTagIfMissing(f.Tags, "sql", v.sqlPrefix) + return f +} + type KeywordTransformer struct { required bool sqlPrefix string diff --git a/pkg/sdk/poc/generator/plain_struct.go b/pkg/sdk/poc/generator/plain_struct.go index f6fa3eb155..d0e529b38d 100644 --- a/pkg/sdk/poc/generator/plain_struct.go +++ b/pkg/sdk/poc/generator/plain_struct.go @@ -29,6 +29,10 @@ func (v *plainStruct) Text(name string) *plainStruct { return v.Field(name, "string") } +func (v *plainStruct) Time(name string) *plainStruct { + return v.Field(name, "time.Time") +} + func (v *plainStruct) OptionalText(name string) *plainStruct { return v.Field(name, "*string") } diff --git a/pkg/sdk/poc/main.go b/pkg/sdk/poc/main.go index 0d9ebbb5fe..0a4984e5af 100644 --- a/pkg/sdk/poc/main.go +++ b/pkg/sdk/poc/main.go @@ -16,17 +16,18 @@ import ( ) var definitionMapping = map[string]*generator.Interface{ - "database_role_def.go": example.DatabaseRole, - "network_policies_def.go": sdk.NetworkPoliciesDef, - "session_policies_def.go": sdk.SessionPoliciesDef, - "tasks_def.go": sdk.TasksDef, - "streams_def.go": sdk.StreamsDef, - "application_roles_def.go": sdk.ApplicationRolesDef, - "views_def.go": sdk.ViewsDef, - "stages_def.go": sdk.StagesDef, - "functions_def.go": sdk.FunctionsDef, - "procedures_def.go": sdk.ProceduresDef, - "event_tables_def.go": sdk.EventTablesDef, + "database_role_def.go": example.DatabaseRole, + "network_policies_def.go": sdk.NetworkPoliciesDef, + "session_policies_def.go": sdk.SessionPoliciesDef, + "tasks_def.go": sdk.TasksDef, + "streams_def.go": sdk.StreamsDef, + "application_roles_def.go": sdk.ApplicationRolesDef, + "views_def.go": sdk.ViewsDef, + "stages_def.go": sdk.StagesDef, + "functions_def.go": sdk.FunctionsDef, + "procedures_def.go": sdk.ProceduresDef, + "event_tables_def.go": sdk.EventTablesDef, + "storage_integration_def.go": sdk.StorageIntegrationDef, } func main() { diff --git a/pkg/sdk/storage_integration_def.go b/pkg/sdk/storage_integration_def.go new file mode 100644 index 0000000000..e626a6e325 --- /dev/null +++ b/pkg/sdk/storage_integration_def.go @@ -0,0 +1,145 @@ +package sdk + +import g "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/poc/generator" + +//go:generate go run ./poc/main.go + +var StorageLocationDef = g.NewQueryStruct("StorageLocation").Text("Path", g.KeywordOptions().SingleQuotes().Required()) + +var StorageIntegrationDef = g.NewInterface( + "StorageIntegrations", + "StorageIntegration", + g.KindOfT[AccountObjectIdentifier](), +). + CreateOperation( + "https://docs.snowflake.com/en/sql-reference/sql/create-storage-integration", + g.NewQueryStruct("CreateStorageIntegration"). + Create(). + OrReplace(). + SQL("STORAGE INTEGRATION"). + IfNotExists(). + Name(). + PredefinedQueryStructField("externalStageType", "string", g.StaticOptions().SQL("TYPE = EXTERNAL_STAGE")). + OptionalQueryStructField( + "S3StorageProviderParams", + g.NewQueryStruct("S3StorageParams"). + PredefinedQueryStructField("storageProvider", "string", g.StaticOptions().SQL("STORAGE_PROVIDER = 'S3'")). + TextAssignment("STORAGE_AWS_ROLE_ARN", g.ParameterOptions().SingleQuotes().Required()). + OptionalTextAssignment("STORAGE_AWS_OBJECT_ACL", g.ParameterOptions().SingleQuotes()), + g.KeywordOptions(), + ). + OptionalQueryStructField( + "GCSStorageProviderParams", + g.NewQueryStruct("GCSStorageParams"). + PredefinedQueryStructField("storageProvider", "string", g.StaticOptions().SQL("STORAGE_PROVIDER = 'GCS'")), + g.KeywordOptions(), + ). + OptionalQueryStructField( + "AzureStorageProviderParams", + g.NewQueryStruct("AzureStorageParams"). + PredefinedQueryStructField("storageProvider", "string", g.StaticOptions().SQL("STORAGE_PROVIDER = 'AZURE'")). + OptionalTextAssignment("AZURE_TENANT_ID", g.ParameterOptions().SingleQuotes().Required()), + g.KeywordOptions(), + ). + BooleanAssignment("ENABLED", g.ParameterOptions().Required()). + ListAssignment("STORAGE_ALLOWED_LOCATIONS", "StorageLocation", g.ParameterOptions().Parentheses().Required()). + ListAssignment("STORAGE_BLOCKED_LOCATIONS", "StorageLocation", g.ParameterOptions().Parentheses()). + OptionalComment(). + WithValidation(g.ValidIdentifier, "name"). + WithValidation(g.ConflictingFields, "IfNotExists", "OrReplace"). + WithValidation(g.ExactlyOneValueSet, "S3StorageProviderParams", "GCSStorageProviderParams", "AzureStorageProviderParams"), + StorageLocationDef, + ). + AlterOperation( + "https://docs.snowflake.com/en/sql-reference/sql/alter-storage-integration", + g.NewQueryStruct("AlterStorageIntegration"). + Alter(). + SQL("STORAGE INTEGRATION"). + IfExists(). + Name(). + OptionalQueryStructField( + "Set", + g.NewQueryStruct("StorageIntegrationSet"). + OptionalQueryStructField( + "SetS3Params", + g.NewQueryStruct("SetS3StorageParams"). + TextAssignment("STORAGE_AWS_ROLE_ARN", g.ParameterOptions().SingleQuotes().Required()). + OptionalTextAssignment("STORAGE_AWS_OBJECT_ACL", g.ParameterOptions().SingleQuotes()), + g.KeywordOptions(), + ). + OptionalQueryStructField( + "SetAzureParams", + g.NewQueryStruct("SetAzureStorageParams"). + TextAssignment("AZURE_TENANT_ID", g.ParameterOptions().SingleQuotes().Required()), + g.KeywordOptions(), + ). + BooleanAssignment("ENABLED", g.ParameterOptions()). + ListAssignment("STORAGE_ALLOWED_LOCATIONS", "StorageLocation", g.ParameterOptions().Parentheses()). + ListAssignment("STORAGE_BLOCKED_LOCATIONS", "StorageLocation", g.ParameterOptions().Parentheses()). + OptionalComment(), + g.KeywordOptions().SQL("SET"), + ). + OptionalQueryStructField( + "Unset", + g.NewQueryStruct("StorageIntegrationUnset"). + OptionalSQL("ENABLED"). + OptionalSQL("STORAGE_BLOCKED_LOCATIONS"). + OptionalSQL("COMMENT"), + g.ListOptions().SQL("UNSET"), + ). + OptionalSetTags(). + OptionalUnsetTags(). + WithValidation(g.ValidIdentifier, "name"). + WithValidation(g.ConflictingFields, "IfExists", "UnsetTags"). + WithValidation(g.ExactlyOneValueSet, "Set", "Unset", "SetTags", "UnsetTags"), + ). + DropOperation( + "https://docs.snowflake.com/en/sql-reference/sql/drop-integration", + g.NewQueryStruct("DropStorageIntegration"). + Drop(). + SQL("STORAGE INTEGRATION"). + IfExists(). + Name(). + WithValidation(g.ValidIdentifier, "name"), + ). + ShowOperation( + "https://docs.snowflake.com/en/sql-reference/sql/show-integrations", + g.DbStruct("showStorageIntegrationsDbRow"). + Text("name"). + Text("type"). + Text("category"). + Bool("enabled"). + Text("comment"). + Time("created_on"), + g.PlainStruct("StorageIntegration"). + Text("Name"). + Text("StorageType"). + Text("Category"). + Bool("Enabled"). + Text("Comment"). + Time("CreatedOn"), + g.NewQueryStruct("ShowStorageIntegrations"). + Show(). + SQL("STORAGE INTEGRATIONS"). + OptionalLike(), + ). + ShowByIdOperation(). + DescribeOperation( + g.DescriptionMappingKindSlice, + "https://docs.snowflake.com/en/sql-reference/sql/desc-integration", + g.DbStruct("descStorageIntegrationsDbRow"). + Text("property"). + Text("property_type"). + Text("property_value"). + Text("property_default"), + g.PlainStruct("StorageIntegrationProperty"). + Text("Name"). + Text("Type"). + Text("Value"). + Text("Default"), + g.NewQueryStruct("DescribeStorageIntegration"). + Describe(). + SQL("STORAGE INTEGRATION"). + Name(). + WithValidation(g.ValidIdentifier, "name"), + ) diff --git a/pkg/sdk/storage_integration_dto_builders_gen.go b/pkg/sdk/storage_integration_dto_builders_gen.go new file mode 100644 index 0000000000..211c61a426 --- /dev/null +++ b/pkg/sdk/storage_integration_dto_builders_gen.go @@ -0,0 +1,214 @@ +// Code generated by dto builder generator; DO NOT EDIT. + +package sdk + +import () + +func NewCreateStorageIntegrationRequest( + name AccountObjectIdentifier, + Enabled bool, + StorageAllowedLocations []StorageLocation, +) *CreateStorageIntegrationRequest { + s := CreateStorageIntegrationRequest{} + s.name = name + s.Enabled = Enabled + s.StorageAllowedLocations = StorageAllowedLocations + return &s +} + +func (s *CreateStorageIntegrationRequest) WithOrReplace(OrReplace *bool) *CreateStorageIntegrationRequest { + s.OrReplace = OrReplace + return s +} + +func (s *CreateStorageIntegrationRequest) WithIfNotExists(IfNotExists *bool) *CreateStorageIntegrationRequest { + s.IfNotExists = IfNotExists + return s +} + +func (s *CreateStorageIntegrationRequest) WithS3StorageProviderParams(S3StorageProviderParams *S3StorageParamsRequest) *CreateStorageIntegrationRequest { + s.S3StorageProviderParams = S3StorageProviderParams + return s +} + +func (s *CreateStorageIntegrationRequest) WithGCSStorageProviderParams(GCSStorageProviderParams *GCSStorageParamsRequest) *CreateStorageIntegrationRequest { + s.GCSStorageProviderParams = GCSStorageProviderParams + return s +} + +func (s *CreateStorageIntegrationRequest) WithAzureStorageProviderParams(AzureStorageProviderParams *AzureStorageParamsRequest) *CreateStorageIntegrationRequest { + s.AzureStorageProviderParams = AzureStorageProviderParams + return s +} + +func (s *CreateStorageIntegrationRequest) WithStorageBlockedLocations(StorageBlockedLocations []StorageLocation) *CreateStorageIntegrationRequest { + s.StorageBlockedLocations = StorageBlockedLocations + return s +} + +func (s *CreateStorageIntegrationRequest) WithComment(Comment *string) *CreateStorageIntegrationRequest { + s.Comment = Comment + return s +} + +func NewS3StorageParamsRequest( + StorageAwsRoleArn string, +) *S3StorageParamsRequest { + s := S3StorageParamsRequest{} + s.StorageAwsRoleArn = StorageAwsRoleArn + return &s +} + +func (s *S3StorageParamsRequest) WithStorageAwsObjectAcl(StorageAwsObjectAcl *string) *S3StorageParamsRequest { + s.StorageAwsObjectAcl = StorageAwsObjectAcl + return s +} + +func NewGCSStorageParamsRequest() *GCSStorageParamsRequest { + return &GCSStorageParamsRequest{} +} + +func NewAzureStorageParamsRequest( + AzureTenantId *string, +) *AzureStorageParamsRequest { + s := AzureStorageParamsRequest{} + s.AzureTenantId = AzureTenantId + return &s +} + +func NewAlterStorageIntegrationRequest( + name AccountObjectIdentifier, +) *AlterStorageIntegrationRequest { + s := AlterStorageIntegrationRequest{} + s.name = name + return &s +} + +func (s *AlterStorageIntegrationRequest) WithIfExists(IfExists *bool) *AlterStorageIntegrationRequest { + s.IfExists = IfExists + return s +} + +func (s *AlterStorageIntegrationRequest) WithSet(Set *StorageIntegrationSetRequest) *AlterStorageIntegrationRequest { + s.Set = Set + return s +} + +func (s *AlterStorageIntegrationRequest) WithUnset(Unset *StorageIntegrationUnsetRequest) *AlterStorageIntegrationRequest { + s.Unset = Unset + return s +} + +func (s *AlterStorageIntegrationRequest) WithSetTags(SetTags []TagAssociation) *AlterStorageIntegrationRequest { + s.SetTags = SetTags + return s +} + +func (s *AlterStorageIntegrationRequest) WithUnsetTags(UnsetTags []ObjectIdentifier) *AlterStorageIntegrationRequest { + s.UnsetTags = UnsetTags + return s +} + +func NewStorageIntegrationSetRequest() *StorageIntegrationSetRequest { + return &StorageIntegrationSetRequest{} +} + +func (s *StorageIntegrationSetRequest) WithSetS3Params(SetS3Params *SetS3StorageParamsRequest) *StorageIntegrationSetRequest { + s.SetS3Params = SetS3Params + return s +} + +func (s *StorageIntegrationSetRequest) WithSetAzureParams(SetAzureParams *SetAzureStorageParamsRequest) *StorageIntegrationSetRequest { + s.SetAzureParams = SetAzureParams + return s +} + +func (s *StorageIntegrationSetRequest) WithEnabled(Enabled bool) *StorageIntegrationSetRequest { + s.Enabled = Enabled + return s +} + +func (s *StorageIntegrationSetRequest) WithStorageAllowedLocations(StorageAllowedLocations []StorageLocation) *StorageIntegrationSetRequest { + s.StorageAllowedLocations = StorageAllowedLocations + return s +} + +func (s *StorageIntegrationSetRequest) WithStorageBlockedLocations(StorageBlockedLocations []StorageLocation) *StorageIntegrationSetRequest { + s.StorageBlockedLocations = StorageBlockedLocations + return s +} + +func (s *StorageIntegrationSetRequest) WithComment(Comment *string) *StorageIntegrationSetRequest { + s.Comment = Comment + return s +} + +func NewSetS3StorageParamsRequest( + StorageAwsRoleArn string, +) *SetS3StorageParamsRequest { + s := SetS3StorageParamsRequest{} + s.StorageAwsRoleArn = StorageAwsRoleArn + return &s +} + +func (s *SetS3StorageParamsRequest) WithStorageAwsObjectAcl(StorageAwsObjectAcl *string) *SetS3StorageParamsRequest { + s.StorageAwsObjectAcl = StorageAwsObjectAcl + return s +} + +func NewSetAzureStorageParamsRequest( + AzureTenantId string, +) *SetAzureStorageParamsRequest { + s := SetAzureStorageParamsRequest{} + s.AzureTenantId = AzureTenantId + return &s +} + +func NewStorageIntegrationUnsetRequest() *StorageIntegrationUnsetRequest { + return &StorageIntegrationUnsetRequest{} +} + +func (s *StorageIntegrationUnsetRequest) WithEnabled(Enabled *bool) *StorageIntegrationUnsetRequest { + s.Enabled = Enabled + return s +} + +func (s *StorageIntegrationUnsetRequest) WithStorageBlockedLocations(StorageBlockedLocations *bool) *StorageIntegrationUnsetRequest { + s.StorageBlockedLocations = StorageBlockedLocations + return s +} + +func (s *StorageIntegrationUnsetRequest) WithComment(Comment *bool) *StorageIntegrationUnsetRequest { + s.Comment = Comment + return s +} + +func NewDropStorageIntegrationRequest( + name AccountObjectIdentifier, +) *DropStorageIntegrationRequest { + s := DropStorageIntegrationRequest{} + s.name = name + return &s +} + +func (s *DropStorageIntegrationRequest) WithIfExists(IfExists *bool) *DropStorageIntegrationRequest { + s.IfExists = IfExists + return s +} + +func NewShowStorageIntegrationRequest() *ShowStorageIntegrationRequest { + return &ShowStorageIntegrationRequest{} +} + +func (s *ShowStorageIntegrationRequest) WithLike(Like *Like) *ShowStorageIntegrationRequest { + s.Like = Like + return s +} + +func NewDescribeStorageIntegrationRequest( + name AccountObjectIdentifier, +) *DescribeStorageIntegrationRequest { + s := DescribeStorageIntegrationRequest{} + s.name = name + return &s +} diff --git a/pkg/sdk/storage_integration_dto_gen.go b/pkg/sdk/storage_integration_dto_gen.go new file mode 100644 index 0000000000..271ec9905a --- /dev/null +++ b/pkg/sdk/storage_integration_dto_gen.go @@ -0,0 +1,81 @@ +package sdk + +//go:generate go run ./dto-builder-generator/main.go + +var ( + _ optionsProvider[CreateStorageIntegrationOptions] = new(CreateStorageIntegrationRequest) + _ optionsProvider[AlterStorageIntegrationOptions] = new(AlterStorageIntegrationRequest) + _ optionsProvider[DropStorageIntegrationOptions] = new(DropStorageIntegrationRequest) + _ optionsProvider[ShowStorageIntegrationOptions] = new(ShowStorageIntegrationRequest) + _ optionsProvider[DescribeStorageIntegrationOptions] = new(DescribeStorageIntegrationRequest) +) + +type CreateStorageIntegrationRequest struct { + OrReplace *bool + IfNotExists *bool + name AccountObjectIdentifier // required + S3StorageProviderParams *S3StorageParamsRequest + GCSStorageProviderParams *GCSStorageParamsRequest + AzureStorageProviderParams *AzureStorageParamsRequest + Enabled bool // required + StorageAllowedLocations []StorageLocation // required + StorageBlockedLocations []StorageLocation + Comment *string +} + +type S3StorageParamsRequest struct { + StorageAwsRoleArn string // required + StorageAwsObjectAcl *string +} + +type GCSStorageParamsRequest struct{} + +type AzureStorageParamsRequest struct { + AzureTenantId *string // required +} + +type AlterStorageIntegrationRequest struct { + IfExists *bool + name AccountObjectIdentifier // required + Set *StorageIntegrationSetRequest + Unset *StorageIntegrationUnsetRequest + SetTags []TagAssociation + UnsetTags []ObjectIdentifier +} + +type StorageIntegrationSetRequest struct { + SetS3Params *SetS3StorageParamsRequest + SetAzureParams *SetAzureStorageParamsRequest + Enabled bool + StorageAllowedLocations []StorageLocation + StorageBlockedLocations []StorageLocation + Comment *string +} + +type SetS3StorageParamsRequest struct { + StorageAwsRoleArn string // required + StorageAwsObjectAcl *string +} + +type SetAzureStorageParamsRequest struct { + AzureTenantId string // required +} + +type StorageIntegrationUnsetRequest struct { + Enabled *bool + StorageBlockedLocations *bool + Comment *bool +} + +type DropStorageIntegrationRequest struct { + IfExists *bool + name AccountObjectIdentifier // required +} + +type ShowStorageIntegrationRequest struct { + Like *Like +} + +type DescribeStorageIntegrationRequest struct { + name AccountObjectIdentifier // required +} diff --git a/pkg/sdk/storage_integration_gen.go b/pkg/sdk/storage_integration_gen.go new file mode 100644 index 0000000000..9aa15ffa01 --- /dev/null +++ b/pkg/sdk/storage_integration_gen.go @@ -0,0 +1,141 @@ +package sdk + +import ( + "context" + "time" +) + +type StorageIntegrations interface { + Create(ctx context.Context, request *CreateStorageIntegrationRequest) error + Alter(ctx context.Context, request *AlterStorageIntegrationRequest) error + Drop(ctx context.Context, request *DropStorageIntegrationRequest) error + Show(ctx context.Context, request *ShowStorageIntegrationRequest) ([]StorageIntegration, error) + ShowByID(ctx context.Context, id AccountObjectIdentifier) (*StorageIntegration, error) + Describe(ctx context.Context, id AccountObjectIdentifier) ([]StorageIntegrationProperty, error) +} + +// CreateStorageIntegrationOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-storage-integration. +type CreateStorageIntegrationOptions struct { + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + storageIntegration bool `ddl:"static" sql:"STORAGE INTEGRATION"` + IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` + name AccountObjectIdentifier `ddl:"identifier"` + externalStageType string `ddl:"static" sql:"TYPE = EXTERNAL_STAGE"` + S3StorageProviderParams *S3StorageParams `ddl:"keyword"` + GCSStorageProviderParams *GCSStorageParams `ddl:"keyword"` + AzureStorageProviderParams *AzureStorageParams `ddl:"keyword"` + Enabled bool `ddl:"parameter" sql:"ENABLED"` + StorageAllowedLocations []StorageLocation `ddl:"parameter,parentheses" sql:"STORAGE_ALLOWED_LOCATIONS"` + StorageBlockedLocations []StorageLocation `ddl:"parameter,parentheses" sql:"STORAGE_BLOCKED_LOCATIONS"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` +} + +type StorageLocation struct { + Path string `ddl:"keyword,single_quotes"` +} + +type S3StorageParams struct { + storageProvider string `ddl:"static" sql:"STORAGE_PROVIDER = 'S3'"` + StorageAwsRoleArn string `ddl:"parameter,single_quotes" sql:"STORAGE_AWS_ROLE_ARN"` + StorageAwsObjectAcl *string `ddl:"parameter,single_quotes" sql:"STORAGE_AWS_OBJECT_ACL"` +} + +type GCSStorageParams struct { + storageProvider string `ddl:"static" sql:"STORAGE_PROVIDER = 'GCS'"` +} + +type AzureStorageParams struct { + storageProvider string `ddl:"static" sql:"STORAGE_PROVIDER = 'AZURE'"` + AzureTenantId *string `ddl:"parameter,single_quotes" sql:"AZURE_TENANT_ID"` +} + +// AlterStorageIntegrationOptions is based on https://docs.snowflake.com/en/sql-reference/sql/alter-storage-integration. +type AlterStorageIntegrationOptions struct { + alter bool `ddl:"static" sql:"ALTER"` + storageIntegration bool `ddl:"static" sql:"STORAGE INTEGRATION"` + IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` + name AccountObjectIdentifier `ddl:"identifier"` + Set *StorageIntegrationSet `ddl:"keyword" sql:"SET"` + Unset *StorageIntegrationUnset `ddl:"list" sql:"UNSET"` + SetTags []TagAssociation `ddl:"keyword" sql:"SET TAG"` + UnsetTags []ObjectIdentifier `ddl:"keyword" sql:"UNSET TAG"` +} + +type StorageIntegrationSet struct { + SetS3Params *SetS3StorageParams `ddl:"keyword"` + SetAzureParams *SetAzureStorageParams `ddl:"keyword"` + Enabled bool `ddl:"parameter" sql:"ENABLED"` + StorageAllowedLocations []StorageLocation `ddl:"parameter,parentheses" sql:"STORAGE_ALLOWED_LOCATIONS"` + StorageBlockedLocations []StorageLocation `ddl:"parameter,parentheses" sql:"STORAGE_BLOCKED_LOCATIONS"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` +} + +type SetS3StorageParams struct { + StorageAwsRoleArn string `ddl:"parameter,single_quotes" sql:"STORAGE_AWS_ROLE_ARN"` + StorageAwsObjectAcl *string `ddl:"parameter,single_quotes" sql:"STORAGE_AWS_OBJECT_ACL"` +} + +type SetAzureStorageParams struct { + AzureTenantId string `ddl:"parameter,single_quotes" sql:"AZURE_TENANT_ID"` +} + +type StorageIntegrationUnset struct { + Enabled *bool `ddl:"keyword" sql:"ENABLED"` + StorageBlockedLocations *bool `ddl:"keyword" sql:"STORAGE_BLOCKED_LOCATIONS"` + Comment *bool `ddl:"keyword" sql:"COMMENT"` +} + +// DropStorageIntegrationOptions is based on https://docs.snowflake.com/en/sql-reference/sql/drop-integration. +type DropStorageIntegrationOptions struct { + drop bool `ddl:"static" sql:"DROP"` + storageIntegration bool `ddl:"static" sql:"STORAGE INTEGRATION"` + IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` + name AccountObjectIdentifier `ddl:"identifier"` +} + +// ShowStorageIntegrationOptions is based on https://docs.snowflake.com/en/sql-reference/sql/show-integrations. +type ShowStorageIntegrationOptions struct { + show bool `ddl:"static" sql:"SHOW"` + storageIntegrations bool `ddl:"static" sql:"STORAGE INTEGRATIONS"` + Like *Like `ddl:"keyword" sql:"LIKE"` +} + +type showStorageIntegrationsDbRow struct { + Name string `db:"name"` + Type string `db:"type"` + Category string `db:"category"` + Enabled bool `db:"enabled"` + Comment string `db:"comment"` + CreatedOn time.Time `db:"created_on"` +} + +type StorageIntegration struct { + Name string + StorageType string + Category string + Enabled bool + Comment string + CreatedOn time.Time +} + +// DescribeStorageIntegrationOptions is based on https://docs.snowflake.com/en/sql-reference/sql/desc-integration. +type DescribeStorageIntegrationOptions struct { + describe bool `ddl:"static" sql:"DESCRIBE"` + storageIntegration bool `ddl:"static" sql:"STORAGE INTEGRATION"` + name AccountObjectIdentifier `ddl:"identifier"` +} + +type descStorageIntegrationsDbRow struct { + Property string `db:"property"` + PropertyType string `db:"property_type"` + PropertyValue string `db:"property_value"` + PropertyDefault string `db:"property_default"` +} + +type StorageIntegrationProperty struct { + Name string + Type string + Value string + Default string +} diff --git a/pkg/sdk/storage_integration_gen_test.go b/pkg/sdk/storage_integration_gen_test.go new file mode 100644 index 0000000000..a321104d54 --- /dev/null +++ b/pkg/sdk/storage_integration_gen_test.go @@ -0,0 +1,284 @@ +package sdk + +import "testing" + +func TestStorageIntegrations_Create(t *testing.T) { + id := RandomAccountObjectIdentifier() + + // Minimal valid CreateStorageIntegrationOptions + defaultOpts := func() *CreateStorageIntegrationOptions { + return &CreateStorageIntegrationOptions{ + name: id, + S3StorageProviderParams: &S3StorageParams{ + StorageAwsRoleArn: "arn:aws:iam::001234567890:role/role", + }, + Enabled: true, + StorageAllowedLocations: []StorageLocation{{Path: "allowed-loc-1"}, {Path: "allowed-loc-2"}}, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *CreateStorageIntegrationOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: valid identifier for [opts.name]", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewAccountObjectIdentifier("") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: conflicting fields for [opts.IfNotExists opts.OrReplace]", func(t *testing.T) { + opts := defaultOpts() + opts.IfNotExists = Bool(true) + opts.OrReplace = Bool(true) + assertOptsInvalidJoinedErrors(t, opts, errOneOf("CreateStorageIntegrationOptions", "IfNotExists", "OrReplace")) + }) + + t.Run("validation: exactly one field from [opts.S3StorageProviderParams opts.GCSStorageProviderParams opts.AzureStorageProviderParams] should be present - none set", func(t *testing.T) { + opts := defaultOpts() + opts.S3StorageProviderParams = nil + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("CreateStorageIntegrationOptions", "S3StorageProviderParams", "GCSStorageProviderParams", "AzureStorageProviderParams")) + }) + + t.Run("validation: exactly one field from [opts.S3StorageProviderParams opts.GCSStorageProviderParams opts.AzureStorageProviderParams] should be present - two set", func(t *testing.T) { + opts := defaultOpts() + opts.GCSStorageProviderParams = new(GCSStorageParams) + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("CreateStorageIntegrationOptions", "S3StorageProviderParams", "GCSStorageProviderParams", "AzureStorageProviderParams")) + }) + + t.Run("basic", func(t *testing.T) { + opts := defaultOpts() + assertOptsValidAndSQLEquals(t, opts, `CREATE STORAGE INTEGRATION %s TYPE = EXTERNAL_STAGE STORAGE_PROVIDER = 'S3' STORAGE_AWS_ROLE_ARN = 'arn:aws:iam::001234567890:role/role' ENABLED = true STORAGE_ALLOWED_LOCATIONS = ('allowed-loc-1', 'allowed-loc-2')`, id.FullyQualifiedName()) + }) + + t.Run("all options - s3", func(t *testing.T) { + opts := defaultOpts() + opts.IfNotExists = Bool(true) + opts.S3StorageProviderParams = &S3StorageParams{ + StorageAwsRoleArn: "arn:aws:iam::001234567890:role/role", + StorageAwsObjectAcl: String("bucket-owner-full-control"), + } + opts.StorageBlockedLocations = []StorageLocation{{Path: "blocked-loc-1"}, {Path: "blocked-loc-2"}} + opts.Comment = String("some comment") + assertOptsValidAndSQLEquals(t, opts, `CREATE STORAGE INTEGRATION IF NOT EXISTS %s TYPE = EXTERNAL_STAGE STORAGE_PROVIDER = 'S3' STORAGE_AWS_ROLE_ARN = 'arn:aws:iam::001234567890:role/role' STORAGE_AWS_OBJECT_ACL = 'bucket-owner-full-control' ENABLED = true STORAGE_ALLOWED_LOCATIONS = ('allowed-loc-1', 'allowed-loc-2') STORAGE_BLOCKED_LOCATIONS = ('blocked-loc-1', 'blocked-loc-2') COMMENT = 'some comment'`, id.FullyQualifiedName()) + }) + + t.Run("all options - gcs", func(t *testing.T) { + opts := defaultOpts() + opts.OrReplace = Bool(true) + opts.S3StorageProviderParams = nil + opts.GCSStorageProviderParams = new(GCSStorageParams) + opts.StorageBlockedLocations = []StorageLocation{{Path: "blocked-loc-1"}, {Path: "blocked-loc-2"}} + opts.Comment = String("some comment") + assertOptsValidAndSQLEquals(t, opts, `CREATE OR REPLACE STORAGE INTEGRATION %s TYPE = EXTERNAL_STAGE STORAGE_PROVIDER = 'GCS' ENABLED = true STORAGE_ALLOWED_LOCATIONS = ('allowed-loc-1', 'allowed-loc-2') STORAGE_BLOCKED_LOCATIONS = ('blocked-loc-1', 'blocked-loc-2') COMMENT = 'some comment'`, id.FullyQualifiedName()) + }) + + t.Run("all options - azure", func(t *testing.T) { + opts := defaultOpts() + opts.OrReplace = Bool(true) + opts.S3StorageProviderParams = nil + opts.AzureStorageProviderParams = &AzureStorageParams{ + AzureTenantId: String("azure-tenant-id"), + } + opts.StorageBlockedLocations = []StorageLocation{{Path: "blocked-loc-1"}, {Path: "blocked-loc-2"}} + opts.Comment = String("some comment") + assertOptsValidAndSQLEquals(t, opts, `CREATE OR REPLACE STORAGE INTEGRATION %s TYPE = EXTERNAL_STAGE STORAGE_PROVIDER = 'AZURE' AZURE_TENANT_ID = 'azure-tenant-id' ENABLED = true STORAGE_ALLOWED_LOCATIONS = ('allowed-loc-1', 'allowed-loc-2') STORAGE_BLOCKED_LOCATIONS = ('blocked-loc-1', 'blocked-loc-2') COMMENT = 'some comment'`, id.FullyQualifiedName()) + }) +} + +func TestStorageIntegrations_Alter(t *testing.T) { + id := RandomAccountObjectIdentifier() + + // Minimal valid AlterStorageIntegrationOptions + defaultOpts := func() *AlterStorageIntegrationOptions { + return &AlterStorageIntegrationOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *AlterStorageIntegrationOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: valid identifier for [opts.name]", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewAccountObjectIdentifier("") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: conflicting fields for [opts.IfExists opts.UnsetTags]", func(t *testing.T) { + opts := defaultOpts() + opts.IfExists = Bool(true) + opts.UnsetTags = []ObjectIdentifier{ + NewAccountObjectIdentifier("one"), + } + assertOptsInvalidJoinedErrors(t, opts, errOneOf("AlterStorageIntegrationOptions", "IfExists", "UnsetTags")) + }) + + t.Run("validation: exactly one field from [opts.Set opts.Unset opts.SetTags opts.UnsetTags] should be present - none set", func(t *testing.T) { + opts := defaultOpts() + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AlterStorageIntegrationOptions", "Set", "Unset", "SetTags", "UnsetTags")) + }) + + t.Run("validation: exactly one field from [opts.Set opts.Unset opts.SetTags opts.UnsetTags] should be present - two set", func(t *testing.T) { + opts := defaultOpts() + opts.SetTags = []TagAssociation{ + { + Name: NewAccountObjectIdentifier("name"), + Value: "value", + }, + } + opts.UnsetTags = []ObjectIdentifier{ + NewAccountObjectIdentifier("one"), + } + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AlterStorageIntegrationOptions", "Set", "Unset", "SetTags", "UnsetTags")) + }) + + t.Run("set - s3", func(t *testing.T) { + opts := defaultOpts() + opts.Set = &StorageIntegrationSet{ + SetS3Params: &SetS3StorageParams{ + StorageAwsRoleArn: "new-aws-role-arn", + StorageAwsObjectAcl: String("new-aws-object-acl"), + }, + Enabled: false, + StorageAllowedLocations: []StorageLocation{{Path: "new-allowed-location"}}, + StorageBlockedLocations: []StorageLocation{{Path: "new-blocked-location"}}, + Comment: String("changed comment"), + } + assertOptsValidAndSQLEquals(t, opts, "ALTER STORAGE INTEGRATION %s SET STORAGE_AWS_ROLE_ARN = 'new-aws-role-arn' STORAGE_AWS_OBJECT_ACL = 'new-aws-object-acl' ENABLED = false STORAGE_ALLOWED_LOCATIONS = ('new-allowed-location') STORAGE_BLOCKED_LOCATIONS = ('new-blocked-location') COMMENT = 'changed comment'", id.FullyQualifiedName()) + }) + + t.Run("set - azure", func(t *testing.T) { + opts := defaultOpts() + opts.Set = &StorageIntegrationSet{ + SetAzureParams: &SetAzureStorageParams{ + AzureTenantId: "new-azure-tenant-id", + }, + Enabled: false, + StorageAllowedLocations: []StorageLocation{{Path: "new-allowed-location"}}, + StorageBlockedLocations: []StorageLocation{{Path: "new-blocked-location"}}, + Comment: String("changed comment"), + } + assertOptsValidAndSQLEquals(t, opts, "ALTER STORAGE INTEGRATION %s SET AZURE_TENANT_ID = 'new-azure-tenant-id' ENABLED = false STORAGE_ALLOWED_LOCATIONS = ('new-allowed-location') STORAGE_BLOCKED_LOCATIONS = ('new-blocked-location') COMMENT = 'changed comment'", id.FullyQualifiedName()) + }) + + t.Run("set tags", func(t *testing.T) { + opts := defaultOpts() + opts.IfExists = Bool(true) + opts.SetTags = []TagAssociation{ + { + Name: NewAccountObjectIdentifier("name"), + Value: "value", + }, + { + Name: NewAccountObjectIdentifier("second-name"), + Value: "second-value", + }, + } + assertOptsValidAndSQLEquals(t, opts, `ALTER STORAGE INTEGRATION IF EXISTS %s SET TAG "name" = 'value', "second-name" = 'second-value'`, id.FullyQualifiedName()) + }) + + t.Run("unset", func(t *testing.T) { + opts := defaultOpts() + opts.Unset = &StorageIntegrationUnset{ + Enabled: Bool(true), + StorageBlockedLocations: Bool(true), + Comment: Bool(true), + } + assertOptsValidAndSQLEquals(t, opts, "ALTER STORAGE INTEGRATION %s UNSET ENABLED, STORAGE_BLOCKED_LOCATIONS, COMMENT", id.FullyQualifiedName()) + }) + + t.Run("unset tags", func(t *testing.T) { + opts := defaultOpts() + opts.UnsetTags = []ObjectIdentifier{ + NewAccountObjectIdentifier("name"), + NewAccountObjectIdentifier("second-name"), + } + assertOptsValidAndSQLEquals(t, opts, `ALTER STORAGE INTEGRATION %s UNSET TAG "name", "second-name"`, id.FullyQualifiedName()) + }) +} + +func TestStorageIntegrations_Drop(t *testing.T) { + id := RandomAccountObjectIdentifier() + + // Minimal valid DropStorageIntegrationOptions + defaultOpts := func() *DropStorageIntegrationOptions { + return &DropStorageIntegrationOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *DropStorageIntegrationOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: valid identifier for [opts.name]", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewAccountObjectIdentifier("") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("all options", func(t *testing.T) { + opts := defaultOpts() + opts.IfExists = Bool(true) + assertOptsValidAndSQLEquals(t, opts, "DROP STORAGE INTEGRATION IF EXISTS %s", id.FullyQualifiedName()) + }) +} + +func TestStorageIntegrations_Show(t *testing.T) { + id := RandomAccountObjectIdentifier() + + // Minimal valid ShowStorageIntegrationOptions + defaultOpts := func() *ShowStorageIntegrationOptions { + return &ShowStorageIntegrationOptions{} + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *ShowStorageIntegrationOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("basic", func(t *testing.T) { + opts := defaultOpts() + assertOptsValidAndSQLEquals(t, opts, "SHOW STORAGE INTEGRATIONS") + }) + + t.Run("all options", func(t *testing.T) { + opts := defaultOpts() + opts.Like = &Like{ + Pattern: String(id.Name()), + } + assertOptsValidAndSQLEquals(t, opts, "SHOW STORAGE INTEGRATIONS LIKE '%s'", id.Name()) + }) +} + +func TestStorageIntegrations_Describe(t *testing.T) { + id := RandomAccountObjectIdentifier() + + // Minimal valid DescribeStorageIntegrationOptions + defaultOpts := func() *DescribeStorageIntegrationOptions { + return &DescribeStorageIntegrationOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *DescribeStorageIntegrationOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: valid identifier for [opts.name]", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewAccountObjectIdentifier("") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("all options", func(t *testing.T) { + opts := defaultOpts() + assertOptsValidAndSQLEquals(t, opts, "DESCRIBE STORAGE INTEGRATION %s", id.FullyQualifiedName()) + }) +} diff --git a/pkg/sdk/storage_integration_impl_gen.go b/pkg/sdk/storage_integration_impl_gen.go new file mode 100644 index 0000000000..c003747e75 --- /dev/null +++ b/pkg/sdk/storage_integration_impl_gen.go @@ -0,0 +1,166 @@ +package sdk + +import ( + "context" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" +) + +var _ StorageIntegrations = (*storageIntegrations)(nil) + +type storageIntegrations struct { + client *Client +} + +func (v *storageIntegrations) Create(ctx context.Context, request *CreateStorageIntegrationRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *storageIntegrations) Alter(ctx context.Context, request *AlterStorageIntegrationRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *storageIntegrations) Drop(ctx context.Context, request *DropStorageIntegrationRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *storageIntegrations) Show(ctx context.Context, request *ShowStorageIntegrationRequest) ([]StorageIntegration, error) { + opts := request.toOpts() + dbRows, err := validateAndQuery[showStorageIntegrationsDbRow](v.client, ctx, opts) + if err != nil { + return nil, err + } + resultList := convertRows[showStorageIntegrationsDbRow, StorageIntegration](dbRows) + return resultList, nil +} + +func (v *storageIntegrations) ShowByID(ctx context.Context, id AccountObjectIdentifier) (*StorageIntegration, error) { + storageIntegrations, err := v.Show(ctx, NewShowStorageIntegrationRequest().WithLike(&Like{ + Pattern: String(id.Name()), + })) + if err != nil { + return nil, err + } + return collections.FindOne(storageIntegrations, func(r StorageIntegration) bool { return r.Name == id.Name() }) +} + +func (v *storageIntegrations) Describe(ctx context.Context, id AccountObjectIdentifier) ([]StorageIntegrationProperty, error) { + opts := &DescribeStorageIntegrationOptions{ + name: id, + } + rows, err := validateAndQuery[descStorageIntegrationsDbRow](v.client, ctx, opts) + if err != nil { + return nil, err + } + return convertRows[descStorageIntegrationsDbRow, StorageIntegrationProperty](rows), nil +} + +func (r *CreateStorageIntegrationRequest) toOpts() *CreateStorageIntegrationOptions { + opts := &CreateStorageIntegrationOptions{ + OrReplace: r.OrReplace, + IfNotExists: r.IfNotExists, + name: r.name, + + Enabled: r.Enabled, + StorageAllowedLocations: r.StorageAllowedLocations, + StorageBlockedLocations: r.StorageBlockedLocations, + Comment: r.Comment, + } + if r.S3StorageProviderParams != nil { + opts.S3StorageProviderParams = &S3StorageParams{ + StorageAwsRoleArn: r.S3StorageProviderParams.StorageAwsRoleArn, + StorageAwsObjectAcl: r.S3StorageProviderParams.StorageAwsObjectAcl, + } + } + if r.GCSStorageProviderParams != nil { + opts.GCSStorageProviderParams = &GCSStorageParams{} + } + if r.AzureStorageProviderParams != nil { + opts.AzureStorageProviderParams = &AzureStorageParams{ + AzureTenantId: r.AzureStorageProviderParams.AzureTenantId, + } + } + return opts +} + +func (r *AlterStorageIntegrationRequest) toOpts() *AlterStorageIntegrationOptions { + opts := &AlterStorageIntegrationOptions{ + IfExists: r.IfExists, + name: r.name, + + SetTags: r.SetTags, + UnsetTags: r.UnsetTags, + } + if r.Set != nil { + opts.Set = &StorageIntegrationSet{ + Enabled: r.Set.Enabled, + StorageAllowedLocations: r.Set.StorageAllowedLocations, + StorageBlockedLocations: r.Set.StorageBlockedLocations, + Comment: r.Set.Comment, + } + if r.Set.SetS3Params != nil { + opts.Set.SetS3Params = &SetS3StorageParams{ + StorageAwsRoleArn: r.Set.SetS3Params.StorageAwsRoleArn, + StorageAwsObjectAcl: r.Set.SetS3Params.StorageAwsObjectAcl, + } + } + if r.Set.SetAzureParams != nil { + opts.Set.SetAzureParams = &SetAzureStorageParams{ + AzureTenantId: r.Set.SetAzureParams.AzureTenantId, + } + } + } + if r.Unset != nil { + opts.Unset = &StorageIntegrationUnset{ + Enabled: r.Unset.Enabled, + StorageBlockedLocations: r.Unset.StorageBlockedLocations, + Comment: r.Unset.Comment, + } + } + return opts +} + +func (r *DropStorageIntegrationRequest) toOpts() *DropStorageIntegrationOptions { + opts := &DropStorageIntegrationOptions{ + IfExists: r.IfExists, + name: r.name, + } + return opts +} + +func (r *ShowStorageIntegrationRequest) toOpts() *ShowStorageIntegrationOptions { + opts := &ShowStorageIntegrationOptions{ + Like: r.Like, + } + return opts +} + +func (r showStorageIntegrationsDbRow) convert() *StorageIntegration { + return &StorageIntegration{ + Name: r.Name, + StorageType: r.Type, + Category: r.Category, + Enabled: r.Enabled, + Comment: r.Comment, + CreatedOn: r.CreatedOn, + } +} + +func (r *DescribeStorageIntegrationRequest) toOpts() *DescribeStorageIntegrationOptions { + opts := &DescribeStorageIntegrationOptions{ + name: r.name, + } + return opts +} + +func (r descStorageIntegrationsDbRow) convert() *StorageIntegrationProperty { + return &StorageIntegrationProperty{ + Name: r.Property, + Type: r.PropertyType, + Value: r.PropertyValue, + Default: r.PropertyDefault, + } +} diff --git a/pkg/sdk/storage_integration_validations_gen.go b/pkg/sdk/storage_integration_validations_gen.go new file mode 100644 index 0000000000..7290fe7027 --- /dev/null +++ b/pkg/sdk/storage_integration_validations_gen.go @@ -0,0 +1,73 @@ +package sdk + +var ( + _ validatable = new(CreateStorageIntegrationOptions) + _ validatable = new(AlterStorageIntegrationOptions) + _ validatable = new(DropStorageIntegrationOptions) + _ validatable = new(ShowStorageIntegrationOptions) + _ validatable = new(DescribeStorageIntegrationOptions) +) + +func (opts *CreateStorageIntegrationOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if everyValueSet(opts.IfNotExists, opts.OrReplace) { + errs = append(errs, errOneOf("CreateStorageIntegrationOptions", "IfNotExists", "OrReplace")) + } + if !exactlyOneValueSet(opts.S3StorageProviderParams, opts.GCSStorageProviderParams, opts.AzureStorageProviderParams) { + errs = append(errs, errExactlyOneOf("CreateStorageIntegrationOptions", "S3StorageProviderParams", "GCSStorageProviderParams", "AzureStorageProviderParams")) + } + return JoinErrors(errs...) +} + +func (opts *AlterStorageIntegrationOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if everyValueSet(opts.IfExists, opts.UnsetTags) { + errs = append(errs, errOneOf("AlterStorageIntegrationOptions", "IfExists", "UnsetTags")) + } + if !exactlyOneValueSet(opts.Set, opts.Unset, opts.SetTags, opts.UnsetTags) { + errs = append(errs, errExactlyOneOf("AlterStorageIntegrationOptions", "Set", "Unset", "SetTags", "UnsetTags")) + } + return JoinErrors(errs...) +} + +func (opts *DropStorageIntegrationOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + return JoinErrors(errs...) +} + +func (opts *ShowStorageIntegrationOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + return JoinErrors(errs...) +} + +func (opts *DescribeStorageIntegrationOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + return JoinErrors(errs...) +} diff --git a/pkg/sdk/testint/helpers_test.go b/pkg/sdk/testint/helpers_test.go index 8012ad0ed4..ef46e8d767 100644 --- a/pkg/sdk/testint/helpers_test.go +++ b/pkg/sdk/testint/helpers_test.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "os" "path/filepath" "testing" @@ -17,6 +18,26 @@ const ( nycWeatherDataURL = "s3://snowflake-workshop-lab/weather-nyc" ) +var ( + awsBucketUrl, awsBucketUrlIsSet = os.LookupEnv("AWS_EXTERNAL_BUCKET_URL") + awsKeyId, awsKeyIdIsSet = os.LookupEnv("AWS_EXTERNAL_KEY_ID") + awsSecretKey, awsSecretKeyIsSet = os.LookupEnv("AWS_EXTERNAL_SECRET_KEY") + awsRoleARN, awsRoleARNIsSet = os.LookupEnv("AWS_EXTERNAL_ROLE_ARN") + + gcsBucketUrl, gcsBucketUrlIsSet = os.LookupEnv("GCS_EXTERNAL_BUCKET_URL") + + azureBucketUrl, azureBucketUrlIsSet = os.LookupEnv("AZURE_EXTERNAL_BUCKET_URL") + azureTenantId, azureTenantIdIsSet = os.LookupEnv("AZURE_EXTERNAL_TENANT_ID") + + hasExternalEnvironmentVariablesSet = awsBucketUrlIsSet && + awsKeyIdIsSet && + awsSecretKeyIsSet && + awsRoleARNIsSet && + gcsBucketUrlIsSet && + azureBucketUrlIsSet && + azureTenantIdIsSet +) + // there is no direct way to get the account identifier from Snowflake API, but you can get it if you know // the account locator and by filtering the list of accounts in replication accounts by the account locator func getAccountIdentifier(t *testing.T, client *sdk.Client) sdk.AccountIdentifier { diff --git a/pkg/sdk/testint/storage_integration_gen_integration_test.go b/pkg/sdk/testint/storage_integration_gen_integration_test.go new file mode 100644 index 0000000000..4240b993d0 --- /dev/null +++ b/pkg/sdk/testint/storage_integration_gen_integration_test.go @@ -0,0 +1,362 @@ +package testint + +import ( + "strconv" + "strings" + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestInt_StorageIntegrations(t *testing.T) { + client := testClient(t) + ctx := testContext(t) + + if !hasExternalEnvironmentVariablesSet { + t.Skip("Skipping TestInt_StorageIntegrations (External env variables are not set)") + } + + assertStorageIntegrationShowResult := func(t *testing.T, s *sdk.StorageIntegration, name sdk.AccountObjectIdentifier, comment string) { + t.Helper() + assert.Equal(t, name.Name(), s.Name) + assert.Equal(t, true, s.Enabled) + assert.Equal(t, "EXTERNAL_STAGE", s.StorageType) + assert.Equal(t, "STORAGE", s.Category) + assert.Equal(t, comment, s.Comment) + } + + findProp := func(t *testing.T, props []sdk.StorageIntegrationProperty, name string) *sdk.StorageIntegrationProperty { + t.Helper() + prop, err := collections.FindOne(props, func(property sdk.StorageIntegrationProperty) bool { return property.Name == name }) + require.NoError(t, err) + return prop + } + + assertS3StorageIntegrationDescResult := func( + t *testing.T, + props []sdk.StorageIntegrationProperty, + enabled bool, + allowedLocations []sdk.StorageLocation, + blockedLocations []sdk.StorageLocation, + comment string, + ) { + t.Helper() + allowed := make([]string, len(allowedLocations)) + for i, a := range allowedLocations { + allowed[i] = a.Path + } + blocked := make([]string, len(blockedLocations)) + for i, b := range blockedLocations { + blocked[i] = b.Path + } + assert.Equal(t, "Boolean", findProp(t, props, "ENABLED").Type) + assert.Equal(t, strconv.FormatBool(enabled), findProp(t, props, "ENABLED").Value) + assert.Equal(t, "false", findProp(t, props, "ENABLED").Default) + assert.Equal(t, "S3", findProp(t, props, "STORAGE_PROVIDER").Value) + assert.Equal(t, strings.Join(allowed, ","), findProp(t, props, "STORAGE_ALLOWED_LOCATIONS").Value) + assert.Equal(t, strings.Join(blocked, ","), findProp(t, props, "STORAGE_BLOCKED_LOCATIONS").Value) + assert.NotEmpty(t, findProp(t, props, "STORAGE_AWS_IAM_USER_ARN").Value) + assert.NotEmpty(t, findProp(t, props, "STORAGE_AWS_ROLE_ARN").Value) + assert.NotEmpty(t, findProp(t, props, "STORAGE_AWS_EXTERNAL_ID").Value) + assert.Equal(t, comment, findProp(t, props, "COMMENT").Value) + } + + assertGCSStorageIntegrationDescResult := func( + t *testing.T, + props []sdk.StorageIntegrationProperty, + enabled bool, + allowedLocations []sdk.StorageLocation, + blockedLocations []sdk.StorageLocation, + comment string, + ) { + t.Helper() + allowed := make([]string, len(allowedLocations)) + for i, a := range allowedLocations { + allowed[i] = a.Path + } + blocked := make([]string, len(blockedLocations)) + for i, b := range blockedLocations { + blocked[i] = b.Path + } + assert.Equal(t, "Boolean", findProp(t, props, "ENABLED").Type) + assert.Equal(t, strconv.FormatBool(enabled), findProp(t, props, "ENABLED").Value) + assert.Equal(t, "false", findProp(t, props, "ENABLED").Default) + assert.Equal(t, "GCS", findProp(t, props, "STORAGE_PROVIDER").Value) + assert.Equal(t, strings.Join(allowed, ","), findProp(t, props, "STORAGE_ALLOWED_LOCATIONS").Value) + assert.Equal(t, strings.Join(blocked, ","), findProp(t, props, "STORAGE_BLOCKED_LOCATIONS").Value) + assert.NotEmpty(t, findProp(t, props, "STORAGE_GCP_SERVICE_ACCOUNT").Value) + assert.Equal(t, comment, findProp(t, props, "COMMENT").Value) + } + + assertAzureStorageIntegrationDescResult := func( + t *testing.T, + props []sdk.StorageIntegrationProperty, + enabled bool, + allowedLocations []sdk.StorageLocation, + blockedLocations []sdk.StorageLocation, + comment string, + ) { + t.Helper() + allowed := make([]string, len(allowedLocations)) + for i, a := range allowedLocations { + allowed[i] = a.Path + } + blocked := make([]string, len(blockedLocations)) + for i, b := range blockedLocations { + blocked[i] = b.Path + } + assert.Equal(t, "Boolean", findProp(t, props, "ENABLED").Type) + assert.Equal(t, strconv.FormatBool(enabled), findProp(t, props, "ENABLED").Value) + assert.Equal(t, "false", findProp(t, props, "ENABLED").Default) + assert.Equal(t, "AZURE", findProp(t, props, "STORAGE_PROVIDER").Value) + assert.Equal(t, strings.Join(allowed, ","), findProp(t, props, "STORAGE_ALLOWED_LOCATIONS").Value) + assert.Equal(t, strings.Join(blocked, ","), findProp(t, props, "STORAGE_BLOCKED_LOCATIONS").Value) + assert.NotEmpty(t, findProp(t, props, "AZURE_TENANT_ID").Value) + assert.NotEmpty(t, findProp(t, props, "AZURE_CONSENT_URL").Value) + assert.NotEmpty(t, findProp(t, props, "AZURE_MULTI_TENANT_APP_NAME").Value) + assert.Equal(t, comment, findProp(t, props, "COMMENT").Value) + } + + allowedLocations := func(prefix string) []sdk.StorageLocation { + return []sdk.StorageLocation{ + { + Path: prefix + "/allowed-location", + }, + { + Path: prefix + "/allowed-location2", + }, + } + } + s3AllowedLocations := allowedLocations(awsBucketUrl) + gcsAllowedLocations := allowedLocations(gcsBucketUrl) + azureAllowedLocations := allowedLocations(azureBucketUrl) + + blockedLocations := func(prefix string) []sdk.StorageLocation { + return []sdk.StorageLocation{ + { + Path: prefix + "/blocked-location", + }, + { + Path: prefix + "/blocked-location2", + }, + } + } + s3BlockedLocations := blockedLocations(awsBucketUrl) + gcsBlockedLocations := blockedLocations(gcsBucketUrl) + azureBlockedLocations := blockedLocations(azureBucketUrl) + + createS3StorageIntegration := func(t *testing.T) sdk.AccountObjectIdentifier { + t.Helper() + + id := sdk.RandomAccountObjectIdentifier() + req := sdk.NewCreateStorageIntegrationRequest(id, true, s3AllowedLocations). + WithIfNotExists(sdk.Bool(true)). + WithS3StorageProviderParams(sdk.NewS3StorageParamsRequest(awsRoleARN)). + WithStorageBlockedLocations(s3BlockedLocations). + WithComment(sdk.String("some comment")) + + err := client.StorageIntegrations.Create(ctx, req) + require.NoError(t, err) + + t.Cleanup(func() { + err := client.StorageIntegrations.Drop(ctx, sdk.NewDropStorageIntegrationRequest(id)) + require.NoError(t, err) + }) + + return id + } + + createGCSStorageIntegration := func(t *testing.T) sdk.AccountObjectIdentifier { + t.Helper() + + id := sdk.RandomAccountObjectIdentifier() + req := sdk.NewCreateStorageIntegrationRequest(id, true, gcsAllowedLocations). + WithIfNotExists(sdk.Bool(true)). + WithGCSStorageProviderParams(sdk.NewGCSStorageParamsRequest()). + WithStorageBlockedLocations(gcsBlockedLocations). + WithComment(sdk.String("some comment")) + + err := client.StorageIntegrations.Create(ctx, req) + require.NoError(t, err) + + t.Cleanup(func() { + err := client.StorageIntegrations.Drop(ctx, sdk.NewDropStorageIntegrationRequest(id)) + require.NoError(t, err) + }) + + return id + } + + createAzureStorageIntegration := func(t *testing.T) sdk.AccountObjectIdentifier { + t.Helper() + + id := sdk.RandomAccountObjectIdentifier() + req := sdk.NewCreateStorageIntegrationRequest(id, true, azureAllowedLocations). + WithIfNotExists(sdk.Bool(true)). + WithAzureStorageProviderParams(sdk.NewAzureStorageParamsRequest(sdk.String(azureTenantId))). + WithStorageBlockedLocations(azureBlockedLocations). + WithComment(sdk.String("some comment")) + + err := client.StorageIntegrations.Create(ctx, req) + require.NoError(t, err) + + t.Cleanup(func() { + err := client.StorageIntegrations.Drop(ctx, sdk.NewDropStorageIntegrationRequest(id)) + require.NoError(t, err) + }) + + return id + } + + t.Run("Create - S3", func(t *testing.T) { + id := createS3StorageIntegration(t) + + storageIntegration, err := client.StorageIntegrations.ShowByID(ctx, id) + require.NoError(t, err) + + assertStorageIntegrationShowResult(t, storageIntegration, id, "some comment") + }) + + t.Run("Create - GCS", func(t *testing.T) { + id := createGCSStorageIntegration(t) + + storageIntegration, err := client.StorageIntegrations.ShowByID(ctx, id) + require.NoError(t, err) + + assertStorageIntegrationShowResult(t, storageIntegration, id, "some comment") + }) + + t.Run("Create - Azure", func(t *testing.T) { + id := createAzureStorageIntegration(t) + + storageIntegration, err := client.StorageIntegrations.ShowByID(ctx, id) + require.NoError(t, err) + + assertStorageIntegrationShowResult(t, storageIntegration, id, "some comment") + }) + + t.Run("Alter - set - S3", func(t *testing.T) { + id := createS3StorageIntegration(t) + + changedS3AllowedLocations := append([]sdk.StorageLocation{{Path: awsBucketUrl + "/allowed-location3"}}, s3AllowedLocations...) + changedS3BlockedLocations := append([]sdk.StorageLocation{{Path: awsBucketUrl + "/blocked-location3"}}, s3BlockedLocations...) + req := sdk.NewAlterStorageIntegrationRequest(id). + WithSet( + sdk.NewStorageIntegrationSetRequest(). + WithSetS3Params(sdk.NewSetS3StorageParamsRequest(awsRoleARN)). + WithEnabled(true). + WithStorageAllowedLocations(changedS3AllowedLocations). + WithStorageBlockedLocations(changedS3BlockedLocations). + WithComment(sdk.String("changed comment")), + ) + err := client.StorageIntegrations.Alter(ctx, req) + require.NoError(t, err) + + props, err := client.StorageIntegrations.Describe(ctx, id) + require.NoError(t, err) + + assertS3StorageIntegrationDescResult(t, props, true, changedS3AllowedLocations, changedS3BlockedLocations, "changed comment") + }) + + t.Run("Alter - set - Azure", func(t *testing.T) { + id := createAzureStorageIntegration(t) + + changedAzureAllowedLocations := append([]sdk.StorageLocation{{Path: azureBucketUrl + "/allowed-location3"}}, azureAllowedLocations...) + changedAzureBlockedLocations := append([]sdk.StorageLocation{{Path: azureBucketUrl + "/blocked-location3"}}, azureBlockedLocations...) + req := sdk.NewAlterStorageIntegrationRequest(id). + WithSet( + sdk.NewStorageIntegrationSetRequest(). + WithSetAzureParams(sdk.NewSetAzureStorageParamsRequest(azureTenantId)). + WithEnabled(true). + WithStorageAllowedLocations(changedAzureAllowedLocations). + WithStorageBlockedLocations(changedAzureBlockedLocations). + WithComment(sdk.String("changed comment")), + ) + err := client.StorageIntegrations.Alter(ctx, req) + require.NoError(t, err) + + props, err := client.StorageIntegrations.Describe(ctx, id) + require.NoError(t, err) + + assertAzureStorageIntegrationDescResult(t, props, true, changedAzureAllowedLocations, changedAzureBlockedLocations, "changed comment") + }) + + t.Run("Alter - unset", func(t *testing.T) { + id := createS3StorageIntegration(t) + + req := sdk.NewAlterStorageIntegrationRequest(id). + WithUnset( + sdk.NewStorageIntegrationUnsetRequest(). + WithEnabled(sdk.Bool(true)). + WithStorageBlockedLocations(sdk.Bool(true)). + WithComment(sdk.Bool(true)), + ) + err := client.StorageIntegrations.Alter(ctx, req) + require.NoError(t, err) + + props, err := client.StorageIntegrations.Describe(ctx, id) + require.NoError(t, err) + + assertS3StorageIntegrationDescResult(t, props, false, s3AllowedLocations, []sdk.StorageLocation{}, "") + }) + + t.Run("Alter - set and unset tags", func(t *testing.T) { + id := createS3StorageIntegration(t) + + tag, tagCleanup := createTag(t, client, testDb(t), testSchema(t)) + t.Cleanup(tagCleanup) + + err := client.StorageIntegrations.Alter(ctx, sdk.NewAlterStorageIntegrationRequest(id). + WithSetTags([]sdk.TagAssociation{ + { + Name: tag.ID(), + Value: "tag-value", + }, + })) + require.NoError(t, err) + + tagValue, err := client.SystemFunctions.GetTag(ctx, tag.ID(), id, sdk.ObjectTypeIntegration) + require.NoError(t, err) + + assert.Equal(t, "tag-value", tagValue) + + err = client.StorageIntegrations.Alter(ctx, sdk.NewAlterStorageIntegrationRequest(id). + WithUnsetTags([]sdk.ObjectIdentifier{ + tag.ID(), + })) + require.NoError(t, err) + + _, err = client.SystemFunctions.GetTag(ctx, tag.ID(), id, sdk.ObjectTypeIntegration) + require.Error(t, err, sdk.ErrObjectNotExistOrAuthorized) + }) + + t.Run("Describe - S3", func(t *testing.T) { + id := createS3StorageIntegration(t) + + desc, err := client.StorageIntegrations.Describe(ctx, id) + require.NoError(t, err) + + assertS3StorageIntegrationDescResult(t, desc, true, s3AllowedLocations, s3BlockedLocations, "some comment") + }) + + t.Run("Describe - GCS", func(t *testing.T) { + id := createGCSStorageIntegration(t) + + desc, err := client.StorageIntegrations.Describe(ctx, id) + require.NoError(t, err) + + assertGCSStorageIntegrationDescResult(t, desc, true, gcsAllowedLocations, gcsBlockedLocations, "some comment") + }) + + t.Run("Describe - Azure", func(t *testing.T) { + id := createAzureStorageIntegration(t) + + desc, err := client.StorageIntegrations.Describe(ctx, id) + require.NoError(t, err) + + assertAzureStorageIntegrationDescResult(t, desc, true, azureAllowedLocations, azureBlockedLocations, "some comment") + }) +}