diff --git a/examples/data-sources/snowflake_external_functions/data-source.tf b/examples/data-sources/snowflake_external_functions/data-source.tf new file mode 100644 index 0000000000..fc3a9519df --- /dev/null +++ b/examples/data-sources/snowflake_external_functions/data-source.tf @@ -0,0 +1,4 @@ +data "snowflake_external_functions" "current" { + database = "MYDB" + schema = "MYSCHEMA" +} \ No newline at end of file diff --git a/examples/data-sources/snowflake_external_tables/data-source.tf b/examples/data-sources/snowflake_external_tables/data-source.tf new file mode 100644 index 0000000000..d5c67095c7 --- /dev/null +++ b/examples/data-sources/snowflake_external_tables/data-source.tf @@ -0,0 +1,4 @@ +data "snowflake_external_tables" "current" { + database = "MYDB" + schema = "MYSCHEMA" +} \ No newline at end of file diff --git a/examples/data-sources/snowflake_file_formats/data-source.tf b/examples/data-sources/snowflake_file_formats/data-source.tf new file mode 100644 index 0000000000..2d21bbe36e --- /dev/null +++ b/examples/data-sources/snowflake_file_formats/data-source.tf @@ -0,0 +1,4 @@ +data "snowflake_file_formats" "current" { + database = "MYDB" + schema = "MYSCHEMA" +} \ No newline at end of file diff --git a/examples/data-sources/snowflake_masking_policies/data-source.tf b/examples/data-sources/snowflake_masking_policies/data-source.tf new file mode 100644 index 0000000000..eca3077b3b --- /dev/null +++ b/examples/data-sources/snowflake_masking_policies/data-source.tf @@ -0,0 +1,4 @@ +data "snowflake_masking_policies" "current" { + database = "MYDB" + schema = "MYSCHEMA" +} \ No newline at end of file diff --git a/examples/data-sources/snowflake_pipes/data-source.tf b/examples/data-sources/snowflake_pipes/data-source.tf new file mode 100644 index 0000000000..fb63852e81 --- /dev/null +++ b/examples/data-sources/snowflake_pipes/data-source.tf @@ -0,0 +1,4 @@ +data "snowflake_pipes" "current" { + database = "MYDB" + schema = "MYSCHEMA" +} \ No newline at end of file diff --git a/examples/data-sources/snowflake_resource_monitors/data-source.tf b/examples/data-sources/snowflake_resource_monitors/data-source.tf new file mode 100644 index 0000000000..aec8f1fcf6 --- /dev/null +++ b/examples/data-sources/snowflake_resource_monitors/data-source.tf @@ -0,0 +1,2 @@ +data "snowflake_resource_monitors" "current" { +} \ No newline at end of file diff --git a/examples/data-sources/snowflake_sequences/data-source.tf b/examples/data-sources/snowflake_sequences/data-source.tf new file mode 100644 index 0000000000..a822f7a88a --- /dev/null +++ b/examples/data-sources/snowflake_sequences/data-source.tf @@ -0,0 +1,4 @@ +data "snowflake_sequences" "current" { + database = "MYDB" + schema = "MYSCHEMA" +} \ No newline at end of file diff --git a/examples/data-sources/snowflake_stages/data-source.tf b/examples/data-sources/snowflake_stages/data-source.tf new file mode 100644 index 0000000000..1a9829d876 --- /dev/null +++ b/examples/data-sources/snowflake_stages/data-source.tf @@ -0,0 +1,4 @@ +data "snowflake_stages" "current" { + database = "MYDB" + schema = "MYSCHEMA" +} \ No newline at end of file diff --git a/examples/data-sources/snowflake_storage_integrations/data-source.tf b/examples/data-sources/snowflake_storage_integrations/data-source.tf new file mode 100644 index 0000000000..29b61e5c02 --- /dev/null +++ b/examples/data-sources/snowflake_storage_integrations/data-source.tf @@ -0,0 +1,2 @@ +data "snowflake_storage_integrations" "current" { +} \ No newline at end of file diff --git a/examples/data-sources/snowflake_streams/data-source.tf b/examples/data-sources/snowflake_streams/data-source.tf new file mode 100644 index 0000000000..f2015e0e62 --- /dev/null +++ b/examples/data-sources/snowflake_streams/data-source.tf @@ -0,0 +1,4 @@ +data "snowflake_streams" "current" { + database = "MYDB" + schema = "MYSCHEMA" +} \ No newline at end of file diff --git a/examples/data-sources/snowflake_tasks/data-source.tf b/examples/data-sources/snowflake_tasks/data-source.tf new file mode 100644 index 0000000000..8aa5d48e23 --- /dev/null +++ b/examples/data-sources/snowflake_tasks/data-source.tf @@ -0,0 +1,4 @@ +data "snowflake_tasks" "current" { + database = "MYDB" + schema = "MYSCHEMA" +} \ No newline at end of file diff --git a/examples/data-sources/snowflake_warehouses/data-source.tf b/examples/data-sources/snowflake_warehouses/data-source.tf new file mode 100644 index 0000000000..3046f77d4c --- /dev/null +++ b/examples/data-sources/snowflake_warehouses/data-source.tf @@ -0,0 +1,2 @@ +data "snowflake_warehouses" "current" { +} \ No newline at end of file diff --git a/pkg/datasources/external_functions.go b/pkg/datasources/external_functions.go new file mode 100644 index 0000000000..62de91b3fa --- /dev/null +++ b/pkg/datasources/external_functions.go @@ -0,0 +1,96 @@ +package datasources + +import ( + "database/sql" + "fmt" + "log" + + "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var externalFunctionsSchema = map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + Description: "The database from which to return the schemas from.", + }, + "schema": { + Type: schema.TypeString, + Required: true, + Description: "The schema from which to return the external functions from.", + }, + "external_functions": { + Type: schema.TypeList, + Computed: true, + Description: "The external functions in the schema", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "database": { + Type: schema.TypeString, + Computed: true, + }, + "schema": { + Type: schema.TypeString, + Computed: true, + }, + "comment": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "language": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, +} + +func ExternalFunctions() *schema.Resource { + return &schema.Resource{ + Read: ReadExternalFunctions, + Schema: externalFunctionsSchema, + } +} + +func ReadExternalFunctions(d *schema.ResourceData, meta interface{}) error { + db := meta.(*sql.DB) + databaseName := d.Get("database").(string) + schemaName := d.Get("schema").(string) + + currentExternalFunctions, err := snowflake.ListExternalFunctions(databaseName, schemaName, db) + if err == sql.ErrNoRows { + // If not found, mark resource to be removed from statefile during apply or refresh + log.Printf("[DEBUG] external functions in schema (%s) not found", d.Id()) + d.SetId("") + return nil + } else if err != nil { + log.Printf("[DEBUG] unable to parse external functions in schema (%s)", d.Id()) + d.SetId("") + return nil + } + + externalFunctions := []map[string]interface{}{} + + for _, externalFunction := range currentExternalFunctions { + externalFunctionMap := map[string]interface{}{} + + externalFunctionMap["name"] = externalFunction.ExternalFunctionName.String + externalFunctionMap["database"] = externalFunction.DatabaseName.String + externalFunctionMap["schema"] = externalFunction.SchemaName.String + externalFunctionMap["comment"] = externalFunction.Comment.String + externalFunctionMap["language"] = externalFunction.Language.String + + externalFunctions = append(externalFunctions, externalFunctionMap) + } + + d.SetId(fmt.Sprintf(`%v|%v`, databaseName, schemaName)) + return d.Set("external_functions", externalFunctions) +} diff --git a/pkg/datasources/external_functions_acceptance_test.go b/pkg/datasources/external_functions_acceptance_test.go new file mode 100644 index 0000000000..8b53ba1b20 --- /dev/null +++ b/pkg/datasources/external_functions_acceptance_test.go @@ -0,0 +1,79 @@ +package datasources_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccExternalFunctions(t *testing.T) { + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + apiName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + externalFunctionName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + resource.ParallelTest(t, resource.TestCase{ + Providers: providers(), + Steps: []resource.TestStep{ + { + Config: externalFunctions(databaseName, schemaName, apiName, externalFunctionName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.snowflake_external_functions.t", "database", databaseName), + resource.TestCheckResourceAttr("data.snowflake_external_functions.t", "schema", schemaName), + resource.TestCheckResourceAttrSet("data.snowflake_external_functions.t", "external_functions.#"), + resource.TestCheckResourceAttr("data.snowflake_external_functions.t", "external_functions.#", "1"), + resource.TestCheckResourceAttr("data.snowflake_external_functions.t", "external_functions.0.name", externalFunctionName), + ), + }, + }, + }) +} + +func externalFunctions(databaseName string, schemaName string, apiName string, externalFunctionName string) string { + return fmt.Sprintf(` + + resource snowflake_database "test_database" { + name = "%v" + } + + resource snowflake_schema "test_schema"{ + name = "%v" + database = snowflake_database.test_database.name + } + + resource "snowflake_api_integration" "test_api_int" { + name = "%v" + api_provider = "aws_api_gateway" + api_aws_role_arn = "arn:aws:iam::000000000001:/role/test" + api_allowed_prefixes = ["https://123456.execute-api.us-west-2.amazonaws.com/prod/"] + enabled = true + } + + resource "snowflake_external_function" "test_func" { + name = "%v" + database = snowflake_database.test_database.name + schema = snowflake_schema.test_schema.name + arg { + name = "arg1" + type = "varchar" + } + arg { + name = "arg2" + type = "varchar" + } + comment = "Terraform acceptance test" + return_type = "varchar" + return_behavior = "IMMUTABLE" + api_integration = snowflake_api_integration.test_api_int.name + url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func" + } + + data snowflake_external_functions "t" { + database = snowflake_external_function.test_func.database + schema = snowflake_external_function.test_func.schema + depends_on = [snowflake_external_function.test_func] + } + `, databaseName, schemaName, apiName, externalFunctionName) +} diff --git a/pkg/datasources/external_tables.go b/pkg/datasources/external_tables.go new file mode 100644 index 0000000000..f63408425f --- /dev/null +++ b/pkg/datasources/external_tables.go @@ -0,0 +1,90 @@ +package datasources + +import ( + "database/sql" + "fmt" + "log" + + "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var externalTablesSchema = map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + Description: "The database from which to return the schemas from.", + }, + "schema": { + Type: schema.TypeString, + Required: true, + Description: "The schema from which to return the external tables from.", + }, + "external_tables": { + Type: schema.TypeList, + Computed: true, + Description: "The external tables in the schema", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "database": { + Type: schema.TypeString, + Computed: true, + }, + "schema": { + Type: schema.TypeString, + Computed: true, + }, + "comment": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, +} + +func ExternalTables() *schema.Resource { + return &schema.Resource{ + Read: ReadExternalTables, + Schema: externalTablesSchema, + } +} + +func ReadExternalTables(d *schema.ResourceData, meta interface{}) error { + db := meta.(*sql.DB) + databaseName := d.Get("database").(string) + schemaName := d.Get("schema").(string) + + currentExternalTables, err := snowflake.ListExternalTables(databaseName, schemaName, db) + if err == sql.ErrNoRows { + // If not found, mark resource to be removed from statefile during apply or refresh + log.Printf("[DEBUG] external tables in schema (%s) not found", d.Id()) + d.SetId("") + return nil + } else if err != nil { + log.Printf("[DEBUG] unable to parse external tables in schema (%s)", d.Id()) + d.SetId("") + return nil + } + + externalTables := []map[string]interface{}{} + + for _, externalTable := range currentExternalTables { + externalTableMap := map[string]interface{}{} + + externalTableMap["name"] = externalTable.ExternalTableName.String + externalTableMap["database"] = externalTable.DatabaseName.String + externalTableMap["schema"] = externalTable.SchemaName.String + externalTableMap["comment"] = externalTable.Comment.String + + externalTables = append(externalTables, externalTableMap) + } + + d.SetId(fmt.Sprintf(`%v|%v`, databaseName, schemaName)) + return d.Set("external_tables", externalTables) +} diff --git a/pkg/datasources/external_tables_acceptance_test.go b/pkg/datasources/external_tables_acceptance_test.go new file mode 100644 index 0000000000..e74a37829a --- /dev/null +++ b/pkg/datasources/external_tables_acceptance_test.go @@ -0,0 +1,79 @@ +package datasources_test + +import ( + "fmt" + "os" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccExternalTables(t *testing.T) { + if _, ok := os.LookupEnv("SKIP_EXTERNAL_TABLE_TESTS"); ok { + t.Skip("Skipping TestAccExternalTable") + } + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + stageName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + externalTableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + resource.ParallelTest(t, resource.TestCase{ + Providers: providers(), + Steps: []resource.TestStep{ + { + Config: externalTables(databaseName, schemaName, stageName, externalTableName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.snowflake_external_tables.t", "database", databaseName), + resource.TestCheckResourceAttr("data.snowflake_external_tables.t", "schema", schemaName), + resource.TestCheckResourceAttrSet("data.snowflake_external_tables.t", "external_tables.#"), + resource.TestCheckResourceAttr("data.snowflake_external_tables.t", "external_tables.#", "1"), + resource.TestCheckResourceAttr("data.snowflake_external_tables.t", "external_tables.0.name", externalTableName), + ), + }, + }, + }) +} + +func externalTables(databaseName string, schemaName string, stageName string, externalTableName string) string { + return fmt.Sprintf(` + + resource snowflake_database "test" { + name = "%v" + } + + resource snowflake_schema "test"{ + name = "%v" + database = snowflake_database.test.name + } + + resource "snowflake_stage" "test" { + name = "%v" + url = "s3://snowflake-workshop-lab/weather-nyc" + database = snowflake_database.test.name + schema = snowflake_schema.test.name + comment = "Terraform acceptance test" + } + + resource "snowflake_external_table" "test_table" { + database = snowflake_database.test.name + schema = snowflake_schema.test.name + name = "%v" + comment = "Terraform acceptance test" + column { + name = "column1" + type = "STRING" + as = "TO_VARCHAR(TO_TIMESTAMP_NTZ(value:unix_timestamp_property::NUMBER, 3), 'yyyy-mm-dd-hh')" + } + file_format = "TYPE = CSV" + location = "@${snowflake_database.test.name}.${snowflake_schema.test.name}.${snowflake_stage.test.name}" + } + + data snowflake_external_tables "t" { + database = snowflake_external_table.test_table.database + schema = snowflake_external_table.test_table.schema + depends_on = [snowflake_external_table.test_table] + } + `, databaseName, schemaName, stageName, externalTableName) +} diff --git a/pkg/datasources/file_formats.go b/pkg/datasources/file_formats.go new file mode 100644 index 0000000000..71934d5d7b --- /dev/null +++ b/pkg/datasources/file_formats.go @@ -0,0 +1,96 @@ +package datasources + +import ( + "database/sql" + "fmt" + "log" + + "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var fileFormatsSchema = map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + Description: "The database from which to return the schemas from.", + }, + "schema": { + Type: schema.TypeString, + Required: true, + Description: "The schema from which to return the file formats from.", + }, + "file_formats": { + Type: schema.TypeList, + Computed: true, + Description: "The file formats in the schema", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "database": { + Type: schema.TypeString, + Computed: true, + }, + "schema": { + Type: schema.TypeString, + Computed: true, + }, + "comment": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "format_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, +} + +func FileFormats() *schema.Resource { + return &schema.Resource{ + Read: ReadFileFormats, + Schema: fileFormatsSchema, + } +} + +func ReadFileFormats(d *schema.ResourceData, meta interface{}) error { + db := meta.(*sql.DB) + databaseName := d.Get("database").(string) + schemaName := d.Get("schema").(string) + + currentFileFormats, err := snowflake.ListFileFormats(databaseName, schemaName, db) + if err == sql.ErrNoRows { + // If not found, mark resource to be removed from statefile during apply or refresh + log.Printf("[DEBUG] file formats in schema (%s) not found", d.Id()) + d.SetId("") + return nil + } else if err != nil { + log.Printf("[DEBUG] unable to parse file formats in schema (%s)", d.Id()) + d.SetId("") + return nil + } + + fileFormats := []map[string]interface{}{} + + for _, fileFormat := range currentFileFormats { + fileFormatMap := map[string]interface{}{} + + fileFormatMap["name"] = fileFormat.FileFormatName.String + fileFormatMap["database"] = fileFormat.DatabaseName.String + fileFormatMap["schema"] = fileFormat.SchemaName.String + fileFormatMap["comment"] = fileFormat.Comment.String + fileFormatMap["format_type"] = fileFormat.FormatType.String + + fileFormats = append(fileFormats, fileFormatMap) + } + + d.SetId(fmt.Sprintf(`%v|%v`, databaseName, schemaName)) + return d.Set("file_formats", fileFormats) +} diff --git a/pkg/datasources/file_formats_acceptance_test.go b/pkg/datasources/file_formats_acceptance_test.go new file mode 100644 index 0000000000..43ef8daeda --- /dev/null +++ b/pkg/datasources/file_formats_acceptance_test.go @@ -0,0 +1,80 @@ +package datasources_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccFileFormats(t *testing.T) { + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + fileFormatName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + resource.ParallelTest(t, resource.TestCase{ + Providers: providers(), + Steps: []resource.TestStep{ + { + Config: fileFormats(databaseName, schemaName, fileFormatName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.snowflake_file_formats.t", "database", databaseName), + resource.TestCheckResourceAttr("data.snowflake_file_formats.t", "schema", schemaName), + resource.TestCheckResourceAttrSet("data.snowflake_file_formats.t", "file_formats.#"), + resource.TestCheckResourceAttr("data.snowflake_file_formats.t", "file_formats.#", "1"), + resource.TestCheckResourceAttr("data.snowflake_file_formats.t", "file_formats.0.name", fileFormatName), + ), + }, + }, + }) +} + +func fileFormats(databaseName string, schemaName string, fileFormatName string) string { + return fmt.Sprintf(` + + resource snowflake_database "d" { + name = "%v" + } + + resource snowflake_schema "s"{ + name = "%v" + database = snowflake_database.d.name + } + + resource snowflake_file_format "t"{ + name = "%v" + database = snowflake_schema.s.database + schema = snowflake_schema.s.name + format_type = "CSV" + compression = "GZIP" + record_delimiter = "\r" + field_delimiter = ";" + file_extension = ".ssv" + skip_header = 1 + skip_blank_lines = true + date_format = "YYY-MM-DD" + time_format = "HH24:MI" + timestamp_format = "YYYY-MM-DD HH24:MI:SS.FFTZH:TZM" + binary_format = "UTF8" + escape = "\\" + escape_unenclosed_field = "!" + trim_space = true + field_optionally_enclosed_by = "'" + null_if = ["NULL"] + error_on_column_count_mismatch = true + replace_invalid_characters = true + validate_utf8 = false + empty_field_as_null = false + skip_byte_order_mark = false + encoding = "UTF-16" + comment = "Terraform acceptance test" + } + + data snowflake_file_formats "t" { + database = snowflake_file_format.t.database + schema = snowflake_file_format.t.schema + depends_on = [snowflake_file_format.t] + } + `, databaseName, schemaName, fileFormatName) +} diff --git a/pkg/datasources/masking_policies.go b/pkg/datasources/masking_policies.go new file mode 100644 index 0000000000..c48c4ee8bd --- /dev/null +++ b/pkg/datasources/masking_policies.go @@ -0,0 +1,96 @@ +package datasources + +import ( + "database/sql" + "fmt" + "log" + + "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var maskingPoliciesSchema = map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + Description: "The database from which to return the schemas from.", + }, + "schema": { + Type: schema.TypeString, + Required: true, + Description: "The schema from which to return the maskingPolicies from.", + }, + "masking_policies": { + Type: schema.TypeList, + Computed: true, + Description: "The maskingPolicies in the schema", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "database": { + Type: schema.TypeString, + Computed: true, + }, + "schema": { + Type: schema.TypeString, + Computed: true, + }, + "comment": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "kind": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, +} + +func MaskingPolicies() *schema.Resource { + return &schema.Resource{ + Read: ReadMaskingPolicies, + Schema: maskingPoliciesSchema, + } +} + +func ReadMaskingPolicies(d *schema.ResourceData, meta interface{}) error { + db := meta.(*sql.DB) + databaseName := d.Get("database").(string) + schemaName := d.Get("schema").(string) + + currentMaskingPolicies, err := snowflake.ListMaskingPolicies(databaseName, schemaName, db) + if err == sql.ErrNoRows { + // If not found, mark resource to be removed from statefile during apply or refresh + log.Printf("[DEBUG] masking policies in schema (%s) not found", d.Id()) + d.SetId("") + return nil + } else if err != nil { + log.Printf("[DEBUG] unable to parse masking policies in schema (%s)", d.Id()) + d.SetId("") + return nil + } + + maskingPolicies := []map[string]interface{}{} + + for _, maskingPolicy := range currentMaskingPolicies { + maskingPolicyMap := map[string]interface{}{} + + maskingPolicyMap["name"] = maskingPolicy.Name.String + maskingPolicyMap["database"] = maskingPolicy.DatabaseName.String + maskingPolicyMap["schema"] = maskingPolicy.SchemaName.String + maskingPolicyMap["comment"] = maskingPolicy.Comment.String + maskingPolicyMap["kind"] = maskingPolicy.Kind.String + + maskingPolicies = append(maskingPolicies, maskingPolicyMap) + } + + d.SetId(fmt.Sprintf(`%v|%v`, databaseName, schemaName)) + return d.Set("masking_policies", maskingPolicies) +} diff --git a/pkg/datasources/masking_policies_acceptance_test.go b/pkg/datasources/masking_policies_acceptance_test.go new file mode 100644 index 0000000000..abe1dae82b --- /dev/null +++ b/pkg/datasources/masking_policies_acceptance_test.go @@ -0,0 +1,61 @@ +package datasources_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccMaskingPolicies(t *testing.T) { + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + maskingPolicyName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + resource.ParallelTest(t, resource.TestCase{ + Providers: providers(), + Steps: []resource.TestStep{ + { + Config: masking_policies(databaseName, schemaName, maskingPolicyName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.snowflake_masking_policies.t", "database", databaseName), + resource.TestCheckResourceAttr("data.snowflake_masking_policies.t", "schema", schemaName), + resource.TestCheckResourceAttrSet("data.snowflake_masking_policies.t", "masking_policies.#"), + resource.TestCheckResourceAttr("data.snowflake_masking_policies.t", "masking_policies.#", "1"), + resource.TestCheckResourceAttr("data.snowflake_masking_policies.t", "masking_policies.0.name", maskingPolicyName), + ), + }, + }, + }) +} + +func masking_policies(databaseName string, schemaName string, maskingPolicyName string) string { + return fmt.Sprintf(` + + resource snowflake_database "test" { + name = "%v" + } + + resource snowflake_schema "test"{ + name = "%v" + database = snowflake_database.test.name + } + + resource "snowflake_masking_policy" "test" { + name = "%v" + database = snowflake_database.test.name + schema = snowflake_schema.test.name + value_data_type = "VARCHAR" + masking_expression = "case when current_role() in ('ANALYST') then val else sha2(val, 512) end" + return_data_type = "VARCHAR(16777216)" + comment = "Terraform acceptance test" + } + + data snowflake_masking_policies "t" { + database = snowflake_masking_policy.test.database + schema = snowflake_masking_policy.test.schema + depends_on = [snowflake_masking_policy.test] + } + `, databaseName, schemaName, maskingPolicyName) +} diff --git a/pkg/datasources/pipes.go b/pkg/datasources/pipes.go new file mode 100644 index 0000000000..539ee4f6d0 --- /dev/null +++ b/pkg/datasources/pipes.go @@ -0,0 +1,96 @@ +package datasources + +import ( + "database/sql" + "fmt" + "log" + + "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var pipesSchema = map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + Description: "The database from which to return the schemas from.", + }, + "schema": { + Type: schema.TypeString, + Required: true, + Description: "The schema from which to return the pipes from.", + }, + "pipes": { + Type: schema.TypeList, + Computed: true, + Description: "The pipes in the schema", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "database": { + Type: schema.TypeString, + Computed: true, + }, + "schema": { + Type: schema.TypeString, + Computed: true, + }, + "comment": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "integration": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, +} + +func Pipes() *schema.Resource { + return &schema.Resource{ + Read: ReadPipes, + Schema: pipesSchema, + } +} + +func ReadPipes(d *schema.ResourceData, meta interface{}) error { + db := meta.(*sql.DB) + databaseName := d.Get("database").(string) + schemaName := d.Get("schema").(string) + + currentPipes, err := snowflake.ListPipes(databaseName, schemaName, db) + if err == sql.ErrNoRows { + // If not found, mark resource to be removed from statefile during apply or refresh + log.Printf("[DEBUG] pipes in schema (%s) not found", d.Id()) + d.SetId("") + return nil + } else if err != nil { + log.Printf("[DEBUG] unable to parse pipes in schema (%s)", d.Id()) + d.SetId("") + return nil + } + + pipes := []map[string]interface{}{} + + for _, pipe := range currentPipes { + pipeMap := map[string]interface{}{} + + pipeMap["name"] = pipe.Name + pipeMap["database"] = pipe.DatabaseName + pipeMap["schema"] = pipe.SchemaName + pipeMap["comment"] = pipe.Comment + pipeMap["integration"] = pipe.Integration.String + + pipes = append(pipes, pipeMap) + } + + d.SetId(fmt.Sprintf(`%v|%v`, databaseName, schemaName)) + return d.Set("pipes", pipes) +} diff --git a/pkg/datasources/pipes_acceptance_test.go b/pkg/datasources/pipes_acceptance_test.go new file mode 100644 index 0000000000..309e37b533 --- /dev/null +++ b/pkg/datasources/pipes_acceptance_test.go @@ -0,0 +1,87 @@ +package datasources_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccPipes(t *testing.T) { + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + pipeName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + resource.ParallelTest(t, resource.TestCase{ + Providers: providers(), + Steps: []resource.TestStep{ + { + Config: pipes(databaseName, schemaName, pipeName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.snowflake_pipes.t", "database", databaseName), + resource.TestCheckResourceAttr("data.snowflake_pipes.t", "schema", schemaName), + resource.TestCheckResourceAttrSet("data.snowflake_pipes.t", "pipes.#"), + resource.TestCheckResourceAttr("data.snowflake_pipes.t", "pipes.#", "1"), + resource.TestCheckResourceAttr("data.snowflake_pipes.t", "pipes.0.name", pipeName), + ), + }, + }, + }) +} + +func pipes(databaseName string, schemaName string, pipeName string) string { + s := ` +resource "snowflake_database" "test" { + name = "%v" + comment = "Terraform acceptance test" +} + +resource "snowflake_schema" "test" { + name = "%v" + database = snowflake_database.test.name + comment = "Terraform acceptance test" +} + +resource "snowflake_table" "test" { + database = snowflake_database.test.name + schema = snowflake_schema.test.name + name = snowflake_schema.test.name + column { + name = "id" + type = "NUMBER(5,0)" + } + column { + name = "data" + type = "VARCHAR(16)" + } +} + +resource "snowflake_stage" "test" { + name = snowflake_schema.test.name + database = snowflake_database.test.name + schema = snowflake_schema.test.name + comment = "Terraform acceptance test" +} + +data snowflake_pipes "t" { + database = snowflake_pipe.test.database + schema = snowflake_pipe.test.schema + depends_on = [snowflake_pipe.test] +} + +resource "snowflake_pipe" "test" { + database = snowflake_database.test.name + schema = snowflake_schema.test.name + name = "%v" + comment = "Terraform acceptance test" + copy_statement = <