diff --git a/VERSION b/VERSION index 1464c52..ece61c6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.5 \ No newline at end of file +1.0.6 \ No newline at end of file diff --git a/api/client.go b/api/client.go index bb8a57c..56f0864 100644 --- a/api/client.go +++ b/api/client.go @@ -59,6 +59,10 @@ type Client interface { ListDatabase(ctx context.Context, instanceID, filter string) (*v1pb.ListDatabasesResponse, error) // UpdateDatabase patches the database. UpdateDatabase(ctx context.Context, patch *v1pb.Database, updateMasks []string) (*v1pb.Database, error) + // GetDatabaseCatalog gets the database catalog by the database full name. + GetDatabaseCatalog(ctx context.Context, databaseName string) (*v1pb.DatabaseCatalog, error) + // UpdateDatabaseCatalog patches the database catalog. + UpdateDatabaseCatalog(ctx context.Context, patch *v1pb.DatabaseCatalog, updateMasks []string) (*v1pb.DatabaseCatalog, error) // Project // GetProject gets the project by project full name. diff --git a/client/database.go b/client/database.go index 5c2277a..00b29cf 100644 --- a/client/database.go +++ b/client/database.go @@ -63,3 +63,33 @@ func (c *client) UpdateDatabase(ctx context.Context, patch *v1pb.Database, updat return &res, nil } + +// GetDatabaseCatalog gets the database catalog by the database full name. +func (c *client) GetDatabaseCatalog(ctx context.Context, databaseName string) (*v1pb.DatabaseCatalog, error) { + body, err := c.getResource(ctx, fmt.Sprintf("%s/catalog", databaseName)) + if err != nil { + return nil, err + } + + var res v1pb.DatabaseCatalog + if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil { + return nil, err + } + + return &res, nil +} + +// UpdateDatabaseCatalog patches the database catalog. +func (c *client) UpdateDatabaseCatalog(ctx context.Context, patch *v1pb.DatabaseCatalog, updateMasks []string) (*v1pb.DatabaseCatalog, error) { + body, err := c.updateResource(ctx, patch.Name, patch, updateMasks, false /* allow missing = false*/) + if err != nil { + return nil, err + } + + var res v1pb.DatabaseCatalog + if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil { + return nil, err + } + + return &res, nil +} diff --git a/docs/data-sources/database_catalog.md b/docs/data-sources/database_catalog.md new file mode 100644 index 0000000..fa4d185 --- /dev/null +++ b/docs/data-sources/database_catalog.md @@ -0,0 +1,54 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "bytebase_database_catalog Data Source - terraform-provider-bytebase" +subcategory: "" +description: |- + The database catalog data source. +--- + +# bytebase_database_catalog (Data Source) + +The database catalog data source. + + + + +## Schema + +### Required + +- `database` (String) The database full name in instances/{instance}/databases/{database} format + +### Read-Only + +- `id` (String) The ID of this resource. +- `schemas` (List of Object) (see [below for nested schema](#nestedatt--schemas)) + + +### Nested Schema for `schemas` + +Read-Only: + +- `name` (String) +- `tables` (List of Object) (see [below for nested schema](#nestedobjatt--schemas--tables)) + + +### Nested Schema for `schemas.tables` + +Read-Only: + +- `classification` (String) +- `columns` (List of Object) (see [below for nested schema](#nestedobjatt--schemas--tables--columns)) +- `name` (String) + + +### Nested Schema for `schemas.tables.columns` + +Read-Only: + +- `classification` (String) +- `labels` (Map of String) +- `name` (String) +- `semantic_type` (String) + + diff --git a/docs/data-sources/policy.md b/docs/data-sources/policy.md index 029fa32..f99bc66 100644 --- a/docs/data-sources/policy.md +++ b/docs/data-sources/policy.md @@ -22,7 +22,6 @@ The policy data source. ### Optional - `masking_exception_policy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--masking_exception_policy)) -- `masking_policy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--masking_policy)) - `parent` (String) The policy parent name for the policy, support projects/{resource id}, environments/{resource id}, instances/{resource id}, or instances/{resource id}/databases/{database name} ### Read-Only @@ -48,30 +47,8 @@ Optional: - `column` (String) - `database` (String) The database full name in instances/{instance resource id}/databases/{database name} format - `expire_timestamp` (String) The expiration timestamp in YYYY-MM-DDThh:mm:ss.000Z format -- `masking_level` (String) - `member` (String) The member in user:{email} or group:{email} format. - `schema` (String) - `table` (String) - - -### Nested Schema for `masking_policy` - -Optional: - -- `mask_data` (Block List) (see [below for nested schema](#nestedblock--masking_policy--mask_data)) - - -### Nested Schema for `masking_policy.mask_data` - -Optional: - -- `column` (String) -- `full_masking_algorithm_id` (String) -- `masking_level` (String) -- `partial_masking_algorithm_id` (String) -- `schema` (String) -- `table` (String) - - diff --git a/docs/data-sources/policy_list.md b/docs/data-sources/policy_list.md index 90390fd..3b3db7c 100644 --- a/docs/data-sources/policy_list.md +++ b/docs/data-sources/policy_list.md @@ -32,7 +32,6 @@ Read-Only: - `enforce` (Boolean) - `inherit_from_parent` (Boolean) - `masking_exception_policy` (List of Object) (see [below for nested schema](#nestedobjatt--policies--masking_exception_policy)) -- `masking_policy` (List of Object) (see [below for nested schema](#nestedobjatt--policies--masking_policy)) - `name` (String) - `type` (String) @@ -52,30 +51,8 @@ Read-Only: - `column` (String) - `database` (String) - `expire_timestamp` (String) -- `masking_level` (String) - `member` (String) - `schema` (String) - `table` (String) - - -### Nested Schema for `policies.masking_policy` - -Read-Only: - -- `mask_data` (List of Object) (see [below for nested schema](#nestedobjatt--policies--masking_policy--mask_data)) - - -### Nested Schema for `policies.masking_policy.mask_data` - -Read-Only: - -- `column` (String) -- `full_masking_algorithm_id` (String) -- `masking_level` (String) -- `partial_masking_algorithm_id` (String) -- `schema` (String) -- `table` (String) - - diff --git a/docs/data-sources/setting.md b/docs/data-sources/setting.md index 0c9bf2e..1ce3d3d 100644 --- a/docs/data-sources/setting.md +++ b/docs/data-sources/setting.md @@ -21,8 +21,8 @@ The setting data source. ### Read-Only -- `approval_flow` (Block List) (see [below for nested schema](#nestedblock--approval_flow)) -- `external_approval_nodes` (Block List) (see [below for nested schema](#nestedblock--external_approval_nodes)) +- `approval_flow` (Block List) Configure risk level and approval flow for different tasks. Require ENTERPRISE subscription. (see [below for nested schema](#nestedblock--approval_flow)) +- `external_approval_nodes` (Block List) Configure external nodes in the approval flow. Require ENTERPRISE subscription. (see [below for nested schema](#nestedblock--external_approval_nodes)) - `id` (String) The ID of this resource. diff --git a/docs/resources/database_catalog.md b/docs/resources/database_catalog.md new file mode 100644 index 0000000..16f8ead --- /dev/null +++ b/docs/resources/database_catalog.md @@ -0,0 +1,63 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "bytebase_database_catalog Resource - terraform-provider-bytebase" +subcategory: "" +description: |- + The database catalog resource. +--- + +# bytebase_database_catalog (Resource) + +The database catalog resource. + + + + +## Schema + +### Required + +- `database` (String) The database full name in instances/{instance}/databases/{database} format +- `schemas` (Block List, Min: 1) (see [below for nested schema](#nestedblock--schemas)) + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `schemas` + +Required: + +- `tables` (Block List, Min: 1) (see [below for nested schema](#nestedblock--schemas--tables)) + +Optional: + +- `name` (String) + + +### Nested Schema for `schemas.tables` + +Required: + +- `columns` (Block List, Min: 1) (see [below for nested schema](#nestedblock--schemas--tables--columns)) +- `name` (String) + +Optional: + +- `classification` (String) The classification id + + +### Nested Schema for `schemas.tables.columns` + +Required: + +- `name` (String) + +Optional: + +- `classification` (String) The classification id +- `labels` (Map of String) +- `semantic_type` (String) The semantic type id + + diff --git a/docs/resources/environment.md b/docs/resources/environment.md index d9f22d1..d78ac86 100644 --- a/docs/resources/environment.md +++ b/docs/resources/environment.md @@ -17,7 +17,7 @@ The environment resource. ### Required -- `environment_tier_policy` (String) If marked as PROTECTED, developers cannot execute any query on this environment's databases using SQL Editor by default. +- `environment_tier_policy` (String) If marked as PROTECTED, developers cannot execute any query on this environment's databases using SQL Editor by default. Require ENTERPRISE subscription. - `order` (Number) The environment sorting order. - `resource_id` (String) The environment unique resource id. - `title` (String) The environment title. diff --git a/docs/resources/group.md b/docs/resources/group.md index 79f4eca..fa628a4 100644 --- a/docs/resources/group.md +++ b/docs/resources/group.md @@ -3,12 +3,12 @@ page_title: "bytebase_group Resource - terraform-provider-bytebase" subcategory: "" description: |- - The group resource. + The group resource. Workspace domain is required for creating groups. --- # bytebase_group (Resource) -The group resource. +The group resource. Workspace domain is required for creating groups. diff --git a/docs/resources/policy.md b/docs/resources/policy.md index b26f102..dc743a2 100644 --- a/docs/resources/policy.md +++ b/docs/resources/policy.md @@ -25,7 +25,6 @@ The policy resource. - `enforce` (Boolean) Decide if the policy is enforced. - `inherit_from_parent` (Boolean) Decide if the policy should inherit from the parent. - `masking_exception_policy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--masking_exception_policy)) -- `masking_policy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--masking_policy)) ### Read-Only @@ -48,30 +47,8 @@ Optional: - `column` (String) - `database` (String) The database full name in instances/{instance resource id}/databases/{database name} format - `expire_timestamp` (String) The expiration timestamp in YYYY-MM-DDThh:mm:ss.000Z format -- `masking_level` (String) - `member` (String) The member in user:{email} or group:{email} format. - `schema` (String) - `table` (String) - - -### Nested Schema for `masking_policy` - -Optional: - -- `mask_data` (Block List) (see [below for nested schema](#nestedblock--masking_policy--mask_data)) - - -### Nested Schema for `masking_policy.mask_data` - -Optional: - -- `column` (String) -- `full_masking_algorithm_id` (String) -- `masking_level` (String) -- `partial_masking_algorithm_id` (String) -- `schema` (String) -- `table` (String) - - diff --git a/docs/resources/setting.md b/docs/resources/setting.md index 93938a6..4912e6f 100644 --- a/docs/resources/setting.md +++ b/docs/resources/setting.md @@ -21,8 +21,8 @@ The setting resource. ### Optional -- `approval_flow` (Block List) (see [below for nested schema](#nestedblock--approval_flow)) -- `external_approval_nodes` (Block List) (see [below for nested schema](#nestedblock--external_approval_nodes)) +- `approval_flow` (Block List) Configure risk level and approval flow for different tasks. Require ENTERPRISE subscription. (see [below for nested schema](#nestedblock--approval_flow)) +- `external_approval_nodes` (Block List) Configure external nodes in the approval flow. Require ENTERPRISE subscription. (see [below for nested schema](#nestedblock--external_approval_nodes)) ### Read-Only diff --git a/examples/database/main.tf b/examples/database/main.tf new file mode 100644 index 0000000..e139030 --- /dev/null +++ b/examples/database/main.tf @@ -0,0 +1,29 @@ +# Examples for query the environments +terraform { + required_providers { + bytebase = { + version = "1.0.6" + # For local development, please use "terraform.local/bytebase/bytebase" instead + source = "registry.terraform.io/bytebase/bytebase" + } + } +} + +provider "bytebase" { + # You need to replace the account and key with your Bytebase service account. + service_account = "terraform@service.bytebase.com" + service_key = "bbs_BxVIp7uQsARl8nR92ZZV" + # The Bytebase service URL. You can use the external URL in production. + # Check the docs about external URL: https://www.bytebase.com/docs/get-started/install/external-url + url = "https://bytebase.example.com" +} + + + +data "bytebase_database_catalog" "employee" { + database = "instances/test-sample-instance/databases/employee" +} + +output "employee_catalog" { + value = data.bytebase_database_catalog.employee +} diff --git a/examples/environments/main.tf b/examples/environments/main.tf index 114ef21..8929126 100644 --- a/examples/environments/main.tf +++ b/examples/environments/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { bytebase = { - version = "1.0.5" + version = "1.0.6" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/groups/main.tf b/examples/groups/main.tf index b7fa47c..7d0bc8f 100644 --- a/examples/groups/main.tf +++ b/examples/groups/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.5" + version = "1.0.6" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/instances/main.tf b/examples/instances/main.tf index 0a2b238..cac84e7 100644 --- a/examples/instances/main.tf +++ b/examples/instances/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { bytebase = { - version = "1.0.5" + version = "1.0.6" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/policies/main.tf b/examples/policies/main.tf index 416cb5b..3b9b7a4 100644 --- a/examples/policies/main.tf +++ b/examples/policies/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.5" + version = "1.0.6" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } @@ -17,20 +17,11 @@ provider "bytebase" { url = "https://bytebase.example.com" } -data "bytebase_policy" "masking_policy" { - parent = "instances/test-sample-instance/databases/employee" - type = "MASKING" -} - data "bytebase_policy" "masking_exception_policy" { parent = "projects/project-sample" type = "MASKING_EXCEPTION" } -output "masking_policy" { - value = data.bytebase_policy.masking_policy -} - output "masking_exception_policy" { value = data.bytebase_policy.masking_exception_policy } diff --git a/examples/projects/main.tf b/examples/projects/main.tf index 615156a..5421c88 100644 --- a/examples/projects/main.tf +++ b/examples/projects/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { bytebase = { - version = "1.0.5" + version = "1.0.6" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/settings/main.tf b/examples/settings/main.tf index 095fedc..8dc2b66 100644 --- a/examples/settings/main.tf +++ b/examples/settings/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.5" + version = "1.0.6" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/setup/approval_flow.tf b/examples/setup/approval_flow.tf index e69de29..de0bce1 100644 --- a/examples/setup/approval_flow.tf +++ b/examples/setup/approval_flow.tf @@ -0,0 +1,52 @@ + +resource "bytebase_setting" "external_approval" { + name = "bb.workspace.approval.external" + + external_approval_nodes { + nodes { + id = "9e150339-f014-4835-83d7-123aeb1895ba" + title = "Example node" + endpoint = "https://example.com" + } + + nodes { + id = "49a976be-50de-4541-b2d3-f2e32f8e41ef" + title = "Example node 2" + endpoint = "https://example.com" + } + } +} + +resource "bytebase_setting" "approval_flow" { + name = "bb.workspace.approval" + approval_flow { + rules { + flow { + title = "DBA -> OWNER" + description = "Need DBA and workspace owner approval" + creator = "users/support@bytebase.com" + + # Approval flow following the step order. + steps { + type = "GROUP" + node = "WORKSPACE_DBA" + } + + steps { + type = "GROUP" + node = "WORKSPACE_OWNER" + } + } + + # Match any condition will trigger this approval flow. + conditions { + source = "DML" + level = "MODERATE" + } + conditions { + source = "DDL" + level = "HIGH" + } + } + } +} diff --git a/examples/setup/data_masking.tf b/examples/setup/data_masking.tf index 0a52d1a..74be919 100644 --- a/examples/setup/data_masking.tf +++ b/examples/setup/data_masking.tf @@ -1,30 +1,34 @@ -resource "bytebase_policy" "masking_policy" { +resource "bytebase_database_catalog" "employee_catalog" { depends_on = [ bytebase_instance.test ] - parent = "instances/test-sample-instance/databases/employee" - type = "MASKING" - enforce = true - inherit_from_parent = false + database = "instances/test-sample-instance/databases/employee" - masking_policy { - mask_data { - table = "salary" - column = "amount" - masking_level = "FULL" - } - mask_data { - table = "salary" - column = "emp_no" - masking_level = "NONE" + schemas { + tables { + name = "salary" + columns { + name = "amount" + semantic_type = "default" + classification = "1-1-1" + } + columns { + name = "emp_no" + semantic_type = "default-partial" + labels = { + tenant = "example" + region = "asia" + } + } } } } resource "bytebase_policy" "masking_exception_policy" { depends_on = [ - bytebase_project.sample_project + bytebase_project.sample_project, + bytebase_instance.test ] parent = bytebase_project.sample_project.name @@ -34,20 +38,18 @@ resource "bytebase_policy" "masking_exception_policy" { masking_exception_policy { exceptions { - database = "instances/test-sample-instance/databases/employee" - table = "salary" - column = "amount" - masking_level = "NONE" - member = "user:ed@bytebase.com" - action = "EXPORT" + database = "instances/test-sample-instance/databases/employee" + table = "salary" + column = "amount" + member = "user:ed@bytebase.com" + action = "EXPORT" } exceptions { - database = "instances/test-sample-instance/databases/employee" - table = "salary" - column = "amount" - masking_level = "NONE" - member = "user:ed@bytebase.com" - action = "QUERY" + database = "instances/test-sample-instance/databases/employee" + table = "salary" + column = "amount" + member = "user:ed@bytebase.com" + action = "QUERY" } } } diff --git a/examples/setup/main.tf b/examples/setup/main.tf index 36a58d6..580fef4 100644 --- a/examples/setup/main.tf +++ b/examples/setup/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.5" + version = "1.0.6" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/users/main.tf b/examples/users/main.tf index 2baadf6..a571ee7 100644 --- a/examples/users/main.tf +++ b/examples/users/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.5" + version = "1.0.6" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/vcs/main.tf b/examples/vcs/main.tf index 4e5b9fc..5d9c9f7 100644 --- a/examples/vcs/main.tf +++ b/examples/vcs/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.5" + version = "1.0.6" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/go.mod b/go.mod index 449b615..b740a5f 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,17 @@ module github.com/bytebase/terraform-provider-bytebase -go 1.23.2 +go 1.23.4 require ( - github.com/bytebase/bytebase v0.0.0-20241205093738-38cba35b1547 + github.com/bytebase/bytebase v0.0.0-20250106034445-3306b7f8a2cb github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-docs v0.13.0 github.com/hashicorp/terraform-plugin-log v0.7.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.0 github.com/pkg/errors v0.9.1 - google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 - google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583 - google.golang.org/protobuf v1.35.2 + google.golang.org/genproto v0.0.0-20241230172942-26aa7a208def + google.golang.org/genproto/googleapis/api v0.0.0-20241230172942-26aa7a208def + google.golang.org/protobuf v1.36.1 ) require ( @@ -27,7 +27,7 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -64,11 +64,11 @@ require ( github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect github.com/vmihailenco/tagparser v0.1.1 // indirect github.com/zclconf/go-cty v1.11.0 // indirect - golang.org/x/crypto v0.29.0 // indirect - golang.org/x/net v0.31.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect - google.golang.org/grpc v1.68.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241230172942-26aa7a208def // indirect + google.golang.org/grpc v1.69.2 // indirect ) diff --git a/go.sum b/go.sum index 7e3736f..4077d7c 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,8 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bytebase/bytebase v0.0.0-20241205093738-38cba35b1547 h1:4iW1KxDfqJkTbebeuiVgzxxrN6X/Ask1ic9MDFmIfno= -github.com/bytebase/bytebase v0.0.0-20241205093738-38cba35b1547/go.mod h1:3eFZBpvlGqEmGwGK6x7sgdUth4I6uNCNTw57fRFuXr8= +github.com/bytebase/bytebase v0.0.0-20250106034445-3306b7f8a2cb h1:wzMO4Lr1bh5YH0+y67zg1xF+AgXaQuj3A1aotSXNS1M= +github.com/bytebase/bytebase v0.0.0-20250106034445-3306b7f8a2cb/go.mod h1:VW4jbd0t2jCPazRYDVBFrLCNa+Q8PHqPQV8hP2g0OiU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -53,6 +53,10 @@ github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -70,8 +74,8 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -213,8 +217,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= @@ -230,6 +234,16 @@ github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uU github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0= github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= +go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -237,8 +251,8 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -249,8 +263,8 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -272,31 +286,31 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= -google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= -google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583 h1:v+j+5gpj0FopU0KKLDGfDo9ZRRpKdi5UBrCP0f76kuY= -google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= -google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= -google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/genproto v0.0.0-20241230172942-26aa7a208def h1:uz2w9bZTljGBXc3ugqrL/KOsVhQuODYyLNYXUTKrh6M= +google.golang.org/genproto v0.0.0-20241230172942-26aa7a208def/go.mod h1:zNtPaqLK0Wbf5PaSZDYDR+1t5rQoBAIMh06tpzkjvY8= +google.golang.org/genproto/googleapis/api v0.0.0-20241230172942-26aa7a208def h1:0Km0hi+g2KXbXL0+riZzSCKz23f4MmwicuEb00JeonI= +google.golang.org/genproto/googleapis/api v0.0.0-20241230172942-26aa7a208def/go.mod h1:u2DoMSpCXjrzqLdobRccQMc9wrnMAJ1DLng0a2yqM2Q= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241230172942-26aa7a208def h1:4P81qv5JXI/sDNae2ClVx88cgDDA6DPilADkG9tYKz8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241230172942-26aa7a208def/go.mod h1:bdAgzvd4kFrpykc5/AC2eLUiegK9T/qxZHD4hXYf/ho= +google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= +google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/provider/data_source_database_catalog.go b/provider/data_source_database_catalog.go new file mode 100644 index 0000000..e634e71 --- /dev/null +++ b/provider/data_source_database_catalog.go @@ -0,0 +1,140 @@ +package provider + +import ( + "context" + "fmt" + "regexp" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + v1pb "github.com/bytebase/bytebase/proto/generated-go/v1" + + "github.com/bytebase/terraform-provider-bytebase/api" + "github.com/bytebase/terraform-provider-bytebase/provider/internal" +) + +func dataSourceDatabaseCatalog() *schema.Resource { + return &schema.Resource{ + Description: "The database catalog data source.", + ReadContext: dataSourceDatabaseCatalogRead, + Schema: map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + Description: "The database full name in instances/{instance}/databases/{database} format", + ValidateDiagFunc: internal.ResourceNameValidation( + regexp.MustCompile(fmt.Sprintf("^%s%s/%s%s$", internal.InstanceNamePrefix, internal.ResourceIDPattern, internal.DatabaseIDPrefix, internal.ResourceIDPattern)), + ), + }, + "schemas": { + Computed: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "tables": { + Computed: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "classification": { + Type: schema.TypeString, + Computed: true, + Description: "The classification id", + }, + "columns": { + Computed: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "semantic_type": { + Type: schema.TypeString, + Computed: true, + Description: "The semantic type id", + }, + "classification": { + Type: schema.TypeString, + Computed: true, + Description: "The classification id", + }, + "labels": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func dataSourceDatabaseCatalogRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(api.Client) + database := d.Get("database").(string) + + catalog, err := c.GetDatabaseCatalog(ctx, database) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(catalog.Name) + + return setDatabaseCatalog(d, catalog) +} + +func setDatabaseCatalog(d *schema.ResourceData, catalog *v1pb.DatabaseCatalog) diag.Diagnostics { + database := getDatabaseFullNameFromCatalog(catalog.Name) + if err := d.Set("database", database); err != nil { + return diag.Errorf("cannot set database: %s", err.Error()) + } + + schemaList := []interface{}{} + for _, schema := range catalog.Schemas { + rawSchema := map[string]interface{}{} + + tableList := []interface{}{} + for _, table := range schema.Tables { + rawTable := map[string]interface{}{} + rawTable["name"] = table.Name + rawTable["classification"] = table.Classification + + columnList := []interface{}{} + for _, column := range table.GetColumns().Columns { + rawColumn := map[string]interface{}{} + rawColumn["name"] = column.Name + rawColumn["semantic_type"] = column.SemanticType + rawColumn["classification"] = column.Classification + rawColumn["labels"] = column.Labels + columnList = append(columnList, rawColumn) + } + rawTable["columns"] = columnList + tableList = append(tableList, rawTable) + } + rawSchema["tables"] = tableList + schemaList = append(schemaList, rawSchema) + } + + if err := d.Set("schemas", schemaList); err != nil { + return diag.Errorf("cannot set schemas: %s", err.Error()) + } + return nil +} diff --git a/provider/data_source_policy.go b/provider/data_source_policy.go index 2158dde..51e475f 100644 --- a/provider/data_source_policy.go +++ b/provider/data_source_policy.go @@ -44,7 +44,6 @@ func dataSourcePolicy() *schema.Resource { Type: schema.TypeString, Required: true, ValidateFunc: validation.StringInSlice([]string{ - v1pb.PolicyType_MASKING.String(), v1pb.PolicyType_MASKING_EXCEPTION.String(), }, false), Description: "The policy type.", @@ -64,7 +63,6 @@ func dataSourcePolicy() *schema.Resource { Computed: true, Description: "Decide if the policy is enforced.", }, - "masking_policy": getMaskingPolicySchema(true), "masking_exception_policy": getMaskingExceptionPolicySchema(true), }, } @@ -119,15 +117,6 @@ func getMaskingExceptionPolicySchema(computed bool) *schema.Schema { ValidateFunc: validation.StringIsNotEmpty, Description: "The member in user:{email} or group:{email} format.", }, - "masking_level": { - Type: schema.TypeString, - Computed: computed, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - v1pb.MaskingLevel_NONE.String(), - v1pb.MaskingLevel_PARTIAL.String(), - }, false), - }, "action": { Type: schema.TypeString, Computed: computed, @@ -151,69 +140,6 @@ func getMaskingExceptionPolicySchema(computed bool) *schema.Schema { } } -func getMaskingPolicySchema(computed bool) *schema.Schema { - return &schema.Schema{ - Computed: computed, - Optional: true, - Default: nil, - Type: schema.TypeList, - MinItems: 0, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "mask_data": { - MinItems: 0, - Computed: computed, - Optional: true, - Default: nil, - Type: schema.TypeList, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "schema": { - Type: schema.TypeString, - Computed: computed, - Optional: true, - }, - "table": { - Type: schema.TypeString, - Computed: computed, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "column": { - Type: schema.TypeString, - Computed: computed, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "full_masking_algorithm_id": { - Type: schema.TypeString, - Computed: computed, - Optional: true, - }, - "partial_masking_algorithm_id": { - Type: schema.TypeString, - Computed: computed, - Optional: true, - }, - "masking_level": { - Type: schema.TypeString, - Computed: computed, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - v1pb.MaskingLevel_NONE.String(), - v1pb.MaskingLevel_PARTIAL.String(), - v1pb.MaskingLevel_FULL.String(), - }, false), - }, - }, - }, - }, - }, - }, - } -} - func dataSourcePolicyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(api.Client) @@ -245,49 +171,25 @@ func setPolicyMessage(d *schema.ResourceData, policy *v1pb.Policy) diag.Diagnost return diag.Errorf("cannot set enforce for policy: %s", err.Error()) } - if p := policy.GetMaskingPolicy(); p != nil { - if err := d.Set("masking_policy", flattenMaskingPolicy(p)); err != nil { - return diag.Errorf("cannot set masking_policy: %s", err.Error()) - } - } if p := policy.GetMaskingExceptionPolicy(); p != nil { exceptionPolicy, err := flattenMaskingExceptionPolicy(p) if err != nil { return diag.FromErr(err) } if err := d.Set("masking_exception_policy", exceptionPolicy); err != nil { - return diag.Errorf("cannot set masking_policy: %s", err.Error()) + return diag.Errorf("cannot set masking_exception_policy: %s", err.Error()) } } return nil } -func flattenMaskingPolicy(p *v1pb.MaskingPolicy) []interface{} { - maskDataList := []interface{}{} - for _, maskData := range p.MaskData { - raw := map[string]interface{}{} - raw["schema"] = maskData.Schema - raw["table"] = maskData.Table - raw["column"] = maskData.Column - raw["full_masking_algorithm_id"] = maskData.FullMaskingAlgorithmId - raw["partial_masking_algorithm_id"] = maskData.PartialMaskingAlgorithmId - raw["masking_level"] = maskData.MaskingLevel.String() - maskDataList = append(maskDataList, raw) - } - policy := map[string]interface{}{ - "mask_data": maskDataList, - } - return []interface{}{policy} -} - func flattenMaskingExceptionPolicy(p *v1pb.MaskingExceptionPolicy) ([]interface{}, error) { exceptionList := []interface{}{} for _, exception := range p.MaskingExceptions { raw := map[string]interface{}{} raw["member"] = exception.Member raw["action"] = exception.Action.String() - raw["masking_level"] = exception.MaskingLevel.String() if exception.Condition == nil || exception.Condition.Expression == "" { return nil, errors.Errorf("invalid exception policy condition") diff --git a/provider/data_source_policy_list.go b/provider/data_source_policy_list.go index 10bcf64..6f8548a 100644 --- a/provider/data_source_policy_list.go +++ b/provider/data_source_policy_list.go @@ -33,7 +33,7 @@ func dataSourcePolicyList() *schema.Resource { // project policy regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.ProjectNamePrefix, internal.ResourceIDPattern)), // database policy - regexp.MustCompile(fmt.Sprintf("^%s%s%s%s$", internal.InstanceNamePrefix, internal.ResourceIDPattern, internal.DatabaseIDPrefix, internal.ResourceIDPattern)), + regexp.MustCompile(fmt.Sprintf("^%s%s/%s%s$", internal.InstanceNamePrefix, internal.ResourceIDPattern, internal.DatabaseIDPrefix, internal.ResourceIDPattern)), ), Description: "The policy parent name for the policy, support projects/{resource id}, environments/{resource id}, instances/{resource id}, or instances/{resource id}/databases/{database name}", }, @@ -62,7 +62,6 @@ func dataSourcePolicyList() *schema.Resource { Computed: true, Description: "Decide if the policy is enforced.", }, - "masking_policy": getMaskingPolicySchema(true), "masking_exception_policy": getMaskingExceptionPolicySchema(true), }, }, @@ -87,9 +86,6 @@ func dataSourcePolicyListRead(ctx context.Context, d *schema.ResourceData, m int raw["inherit_from_parent"] = policy.InheritFromParent raw["enforce"] = policy.Enforce - if p := policy.GetMaskingPolicy(); p != nil { - raw["masking_policy"] = flattenMaskingPolicy(p) - } if p := policy.GetMaskingExceptionPolicy(); p != nil { exceptionPolicy, err := flattenMaskingExceptionPolicy(p) if err != nil { diff --git a/provider/data_source_policy_list_test.go b/provider/data_source_policy_list_test.go index 7dfb26b..ae37bb4 100644 --- a/provider/data_source_policy_list_test.go +++ b/provider/data_source_policy_list_test.go @@ -28,12 +28,12 @@ func TestAccPolicyListDataSource(t *testing.T) { ), internal.GetTestStepForDataSourceList( testAccCheckPolicyResource( - "masking_policy", - "instances/test-sample-instance/databases/employee", - getMaskingPolicy("salary", "amount", v1pb.MaskingLevel_FULL), - v1pb.PolicyType_MASKING, + "masking_exception_policy", + "projects/project-sample", + getMaskingExceptionPolicy("instances/test-sample-instance/databases/employee", "salary", "amount"), + v1pb.PolicyType_MASKING_EXCEPTION, ), - "bytebase_policy.masking_policy", + "bytebase_policy.masking_exception_policy", "bytebase_policy_list", "after", "policies", diff --git a/provider/data_source_policy_test.go b/provider/data_source_policy_test.go index 06dedbb..bf1328b 100644 --- a/provider/data_source_policy_test.go +++ b/provider/data_source_policy_test.go @@ -23,24 +23,23 @@ func TestAccPolicyDataSource(t *testing.T) { { Config: testAccCheckPolicyDataSource( testAccCheckPolicyResource( - "masking_policy", - "instances/test-sample-instance/databases/employee", - getMaskingPolicy("salary", "amount", v1pb.MaskingLevel_FULL), - v1pb.PolicyType_MASKING, + "masking_exception_policy", + "projects/project-sample", + getMaskingExceptionPolicy("instances/test-sample-instance/databases/employee", "salary", "amount"), + v1pb.PolicyType_MASKING_EXCEPTION, ), - "masking_policy", - "instances/test-sample-instance/databases/employee", - "bytebase_policy.masking_policy", - v1pb.PolicyType_MASKING, + "masking_exception_policy", + "projects/project-sample", + "bytebase_policy.masking_exception_policy", + v1pb.PolicyType_MASKING_EXCEPTION, ), Check: resource.ComposeTestCheckFunc( - internal.TestCheckResourceExists("data.bytebase_policy.masking_policy"), - resource.TestCheckResourceAttr("data.bytebase_policy.masking_policy", "type", v1pb.PolicyType_MASKING.String()), - resource.TestCheckResourceAttr("data.bytebase_policy.masking_policy", "masking_policy.#", "1"), - resource.TestCheckResourceAttr("data.bytebase_policy.masking_policy", "masking_policy.0.mask_data.#", "1"), - resource.TestCheckResourceAttr("data.bytebase_policy.masking_policy", "masking_policy.0.mask_data.0.table", "salary"), - resource.TestCheckResourceAttr("data.bytebase_policy.masking_policy", "masking_policy.0.mask_data.0.column", "amount"), - resource.TestCheckResourceAttr("data.bytebase_policy.masking_policy", "masking_policy.0.mask_data.0.masking_level", v1pb.MaskingLevel_FULL.String()), + internal.TestCheckResourceExists("data.bytebase_policy.masking_exception_policy"), + resource.TestCheckResourceAttr("data.bytebase_policy.masking_exception_policy", "type", v1pb.PolicyType_MASKING_EXCEPTION.String()), + resource.TestCheckResourceAttr("data.bytebase_policy.masking_exception_policy", "masking_exception_policy.#", "1"), + resource.TestCheckResourceAttr("data.bytebase_policy.masking_exception_policy", "masking_exception_policy.0.exceptions.#", "1"), + resource.TestCheckResourceAttr("data.bytebase_policy.masking_exception_policy", "masking_exception_policy.0.exceptions.0.table", "salary"), + resource.TestCheckResourceAttr("data.bytebase_policy.masking_exception_policy", "masking_exception_policy.0.exceptions.0.column", "amount"), ), }, }, @@ -59,11 +58,11 @@ func TestAccPolicyDataSource_NotFound(t *testing.T) { Config: testAccCheckPolicyDataSource( "", "policy", - "instances/test-sample-instance/databases/employee", + "projects/project-sample", "", - v1pb.PolicyType_MASKING, + v1pb.PolicyType_MASKING_EXCEPTION, ), - ExpectError: regexp.MustCompile("Cannot found policy instances/test-sample-instance/databases/employee/policies/MASKING"), + ExpectError: regexp.MustCompile("Cannot found policy projects/project-sample/policies/MASKING_EXCEPTION"), }, }, }) diff --git a/provider/data_source_setting.go b/provider/data_source_setting.go index 91c8c4d..5b5c6f7 100644 --- a/provider/data_source_setting.go +++ b/provider/data_source_setting.go @@ -38,10 +38,11 @@ func dataSourceSetting() *schema.Resource { func getExternalApprovalSetting(computed bool) *schema.Schema { return &schema.Schema{ - Computed: computed, - Optional: true, - Default: nil, - Type: schema.TypeList, + Computed: computed, + Optional: true, + Default: nil, + Type: schema.TypeList, + Description: "Configure external nodes in the approval flow. Require ENTERPRISE subscription.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "nodes": { @@ -78,10 +79,11 @@ func getExternalApprovalSetting(computed bool) *schema.Schema { func getWorkspaceApprovalSetting(computed bool) *schema.Schema { return &schema.Schema{ - Computed: computed, - Optional: true, - Default: nil, - Type: schema.TypeList, + Computed: computed, + Optional: true, + Default: nil, + Type: schema.TypeList, + Description: "Configure risk level and approval flow for different tasks. Require ENTERPRISE subscription.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "rules": { diff --git a/provider/internal/mock_client.go b/provider/internal/mock_client.go index 3c28658..7502f87 100644 --- a/provider/internal/mock_client.go +++ b/provider/internal/mock_client.go @@ -20,6 +20,7 @@ var policyMap map[string]*v1pb.Policy var projectMap map[string]*v1pb.Project var projectIAMMap map[string]*v1pb.IamPolicy var databaseMap map[string]*v1pb.Database +var databaseCatalogMap map[string]*v1pb.DatabaseCatalog var settingMap map[string]*v1pb.Setting var vcsProviderMap map[string]*v1pb.VCSProvider var vcsConnectorMap map[string]*v1pb.VCSConnector @@ -33,6 +34,7 @@ func init() { projectMap = map[string]*v1pb.Project{} projectIAMMap = map[string]*v1pb.IamPolicy{} databaseMap = map[string]*v1pb.Database{} + databaseCatalogMap = map[string]*v1pb.DatabaseCatalog{} settingMap = map[string]*v1pb.Setting{} vcsProviderMap = map[string]*v1pb.VCSProvider{} vcsConnectorMap = map[string]*v1pb.VCSConnector{} @@ -47,6 +49,7 @@ type mockClient struct { projectMap map[string]*v1pb.Project projectIAMMap map[string]*v1pb.IamPolicy databaseMap map[string]*v1pb.Database + databaseCatalogMap map[string]*v1pb.DatabaseCatalog settingMap map[string]*v1pb.Setting vcsProviderMap map[string]*v1pb.VCSProvider vcsConnectorMap map[string]*v1pb.VCSConnector @@ -64,6 +67,7 @@ func newMockClient(_, _, _ string) (api.Client, error) { projectMap: projectMap, projectIAMMap: projectIAMMap, databaseMap: databaseMap, + databaseCatalogMap: databaseCatalogMap, settingMap: settingMap, vcsProviderMap: vcsProviderMap, vcsConnectorMap: vcsConnectorMap, @@ -327,17 +331,6 @@ func (c *mockClient) UpsertPolicy(_ context.Context, patch *v1pb.Policy, updateM } switch policyType { - case v1pb.PolicyType_MASKING: - if !existed { - if patch.GetMaskingPolicy() == nil { - return nil, errors.Errorf("payload is required to create the policy") - } - } - if v := patch.GetMaskingPolicy(); v != nil { - policy.Policy = &v1pb.Policy_MaskingPolicy{ - MaskingPolicy: v, - } - } case v1pb.PolicyType_MASKING_EXCEPTION: if !existed { if patch.GetMaskingExceptionPolicy() == nil { @@ -419,6 +412,22 @@ func (c *mockClient) UpdateDatabase(ctx context.Context, patch *v1pb.Database, u return db, nil } +// GetDatabaseCatalog gets the database catalog by the database full name. +func (c *mockClient) GetDatabaseCatalog(_ context.Context, databaseName string) (*v1pb.DatabaseCatalog, error) { + db, ok := c.databaseCatalogMap[databaseName] + if !ok { + return nil, errors.Errorf("Cannot found database catalog %s", databaseName) + } + + return db, nil +} + +// UpdateDatabaseCatalog patches the database catalog. +func (c *mockClient) UpdateDatabaseCatalog(_ context.Context, patch *v1pb.DatabaseCatalog, _ []string) (*v1pb.DatabaseCatalog, error) { + c.databaseCatalogMap[patch.Name] = patch + return patch, nil +} + // GetProject gets the project by resource id. func (c *mockClient) GetProject(_ context.Context, projectName string) (*v1pb.Project, error) { proj, ok := c.projectMap[projectName] diff --git a/provider/internal/utils.go b/provider/internal/utils.go index ba466db..5c9d75e 100644 --- a/provider/internal/utils.go +++ b/provider/internal/utils.go @@ -37,6 +37,8 @@ const ( GroupNamePrefix = "groups/" // RoleNamePrefix is the prefix for role name. RoleNamePrefix = "roles/" + // DatabaseCatalogNameSuffix is the suffix for the database catalog name. + DatabaseCatalogNameSuffix = "/catalog" // ResourceIDPattern is the pattern for resource id. ResourceIDPattern = "[a-z]([a-z0-9-]{0,61}[a-z0-9])?" ) diff --git a/provider/provider.go b/provider/provider.go index ed7a5fe..044781c 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -66,17 +66,19 @@ func NewProvider() *schema.Provider { "bytebase_user_list": dataSourceUserList(), "bytebase_group": dataSourceGroup(), "bytebase_group_list": dataSourceGroupList(), + "bytebase_database_catalog": dataSourceDatabaseCatalog(), }, ResourcesMap: map[string]*schema.Resource{ - "bytebase_environment": resourceEnvironment(), - "bytebase_instance": resourceInstance(), - "bytebase_policy": resourcePolicy(), - "bytebase_project": resourceProjct(), - "bytebase_setting": resourceSetting(), - "bytebase_vcs_provider": resourceVCSProvider(), - "bytebase_vcs_connector": resourceVCSConnector(), - "bytebase_user": resourceUser(), - "bytebase_group": resourceGroup(), + "bytebase_environment": resourceEnvironment(), + "bytebase_instance": resourceInstance(), + "bytebase_policy": resourcePolicy(), + "bytebase_project": resourceProjct(), + "bytebase_setting": resourceSetting(), + "bytebase_vcs_provider": resourceVCSProvider(), + "bytebase_vcs_connector": resourceVCSConnector(), + "bytebase_user": resourceUser(), + "bytebase_group": resourceGroup(), + "bytebase_database_catalog": resourceDatabaseCatalog(), }, } } diff --git a/provider/resource_database_catalog.go b/provider/resource_database_catalog.go new file mode 100644 index 0000000..3d83369 --- /dev/null +++ b/provider/resource_database_catalog.go @@ -0,0 +1,241 @@ +package provider + +import ( + "context" + "fmt" + "regexp" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/pkg/errors" + + v1pb "github.com/bytebase/bytebase/proto/generated-go/v1" + + "github.com/bytebase/terraform-provider-bytebase/api" + "github.com/bytebase/terraform-provider-bytebase/provider/internal" +) + +func resourceDatabaseCatalog() *schema.Resource { + return &schema.Resource{ + Description: "The database catalog resource.", + CreateContext: resourceDatabaseCatalogCreate, + ReadContext: resourceDatabaseCatalogRead, + UpdateContext: resourceDatabaseCatalogUpdate, + DeleteContext: resourceDatabaseCatalogDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + Description: "The database full name in instances/{instance}/databases/{database} format", + ValidateDiagFunc: internal.ResourceNameValidation( + regexp.MustCompile(fmt.Sprintf("^%s%s/%s%s$", internal.InstanceNamePrefix, internal.ResourceIDPattern, internal.DatabaseIDPrefix, internal.ResourceIDPattern)), + ), + }, + "schemas": { + Required: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + Default: "", + }, + "tables": { + Required: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "classification": { + Type: schema.TypeString, + Optional: true, + Default: "", + Description: "The classification id", + }, + "columns": { + Required: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "semantic_type": { + Type: schema.TypeString, + Optional: true, + Description: "The semantic type id", + }, + "classification": { + Type: schema.TypeString, + Optional: true, + Description: "The classification id", + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func resourceDatabaseCatalogRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(api.Client) + catelogName := d.Id() + database := getDatabaseFullNameFromCatalog(catelogName) + + catalog, err := c.GetDatabaseCatalog(ctx, database) + if err != nil { + return diag.FromErr(err) + } + + return setDatabaseCatalog(d, catalog) +} + +func getDatabaseFullNameFromCatalog(catalog string) string { + return strings.TrimSuffix(catalog, internal.DatabaseCatalogNameSuffix) +} + +func resourceDatabaseCatalogCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(api.Client) + database := d.Get("database").(string) + + catalog, err := convertToDatabaseCatalog(d) + if err != nil { + return diag.Errorf("failed to convert catalog %v with error: %v", database, err.Error()) + } + + if _, err := c.UpdateDatabaseCatalog(ctx, catalog, []string{}); err != nil { + return diag.Errorf("failed to update catalog %v with error: %v", database, err.Error()) + } + + d.SetId(catalog.Name) + + var diags diag.Diagnostics + diag := resourceDatabaseCatalogRead(ctx, d, m) + if diag != nil { + diags = append(diags, diag...) + } + + return diags +} + +func resourceDatabaseCatalogUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(api.Client) + catelogName := d.Id() + database := getDatabaseFullNameFromCatalog(catelogName) + + catalog, err := convertToDatabaseCatalog(d) + if err != nil { + return diag.Errorf("failed to convert catalog %v with error: %v", database, err.Error()) + } + + if _, err := c.UpdateDatabaseCatalog(ctx, catalog, []string{}); err != nil { + return diag.Errorf("failed to update catalog %v with error: %v", database, err.Error()) + } + + if _, err := c.UpdateDatabaseCatalog(ctx, catalog, []string{}); err != nil { + return diag.Errorf("failed to update catalog %v with error: %v", database, err.Error()) + } + + var diags diag.Diagnostics + diag := resourceDatabaseCatalogRead(ctx, d, m) + if diag != nil { + diags = append(diags, diag...) + } + + return diags +} + +func resourceDatabaseCatalogDelete(_ context.Context, d *schema.ResourceData, _ interface{}) diag.Diagnostics { + d.SetId("") + return nil +} + +func convertToDatabaseCatalog(d *schema.ResourceData) (*v1pb.DatabaseCatalog, error) { + database, ok := d.Get("database").(string) + if !ok || database == "" { + return nil, errors.Errorf("invalid database") + } + rawSchemaList, ok := d.Get("schemas").([]interface{}) + if !ok { + return nil, errors.Errorf("invalid schemas") + } + + catalog := &v1pb.DatabaseCatalog{ + Name: fmt.Sprintf("%s%s", database, internal.DatabaseCatalogNameSuffix), + Schemas: []*v1pb.SchemaCatalog{}, + } + + for _, schema := range rawSchemaList { + rawSchema := schema.(map[string]interface{}) + schema := &v1pb.SchemaCatalog{ + Name: rawSchema["name"].(string), + } + + rawTableList, ok := rawSchema["tables"].([]interface{}) + if !ok { + return nil, errors.Errorf("invalid tables") + } + for _, table := range rawTableList { + rawTable := table.(map[string]interface{}) + table := &v1pb.TableCatalog{ + Name: rawTable["name"].(string), + Classification: rawTable["classification"].(string), + } + + columnList := []*v1pb.ColumnCatalog{} + rawColumnList, ok := rawTable["columns"].([]interface{}) + if !ok { + return nil, errors.Errorf("invalid columns") + } + for _, column := range rawColumnList { + rawColumn := column.(map[string]interface{}) + labels := map[string]string{} + for key, val := range rawColumn["labels"].(map[string]interface{}) { + labels[key] = val.(string) + } + + column := &v1pb.ColumnCatalog{ + Name: rawColumn["name"].(string), + SemanticType: rawColumn["semantic_type"].(string), + Classification: rawColumn["classification"].(string), + Labels: labels, + } + columnList = append(columnList, column) + } + + table.Kind = &v1pb.TableCatalog_Columns_{ + Columns: &v1pb.TableCatalog_Columns{ + Columns: columnList, + }, + } + + schema.Tables = append(schema.Tables, table) + } + + catalog.Schemas = append(catalog.Schemas, schema) + } + + return catalog, nil +} diff --git a/provider/resource_environment.go b/provider/resource_environment.go index e783870..35e08d6 100644 --- a/provider/resource_environment.go +++ b/provider/resource_environment.go @@ -59,7 +59,7 @@ func resourceEnvironment() *schema.Resource { v1pb.EnvironmentTier_PROTECTED.String(), v1pb.EnvironmentTier_UNPROTECTED.String(), }, false), - Description: "If marked as PROTECTED, developers cannot execute any query on this environment's databases using SQL Editor by default.", + Description: "If marked as PROTECTED, developers cannot execute any query on this environment's databases using SQL Editor by default. Require ENTERPRISE subscription.", }, }, } diff --git a/provider/resource_group.go b/provider/resource_group.go index 1056442..8b95c15 100644 --- a/provider/resource_group.go +++ b/provider/resource_group.go @@ -18,7 +18,7 @@ import ( func resourceGroup() *schema.Resource { return &schema.Resource{ - Description: "The group resource.", + Description: "The group resource. Workspace domain is required for creating groups.", ReadContext: resourceGroupRead, DeleteContext: resourceGroupDelete, CreateContext: resourceGroupCreate, diff --git a/provider/resource_policy.go b/provider/resource_policy.go index 0c4c064..ccc211d 100644 --- a/provider/resource_policy.go +++ b/provider/resource_policy.go @@ -51,7 +51,6 @@ func resourcePolicy() *schema.Resource { Type: schema.TypeString, Required: true, ValidateFunc: validation.StringInSlice([]string{ - v1pb.PolicyType_MASKING.String(), v1pb.PolicyType_MASKING_EXCEPTION.String(), }, false), Description: "The policy type.", @@ -73,7 +72,6 @@ func resourcePolicy() *schema.Resource { Default: false, Description: "Decide if the policy should inherit from the parent.", }, - "masking_policy": getMaskingPolicySchema(false), "masking_exception_policy": getMaskingExceptionPolicySchema(false), }, } @@ -125,16 +123,7 @@ func resourcePolicyCreate(ctx context.Context, d *schema.ResourceData, m interfa Type: policyType, } - switch policyType { - case v1pb.PolicyType_MASKING: - maskingPolicy, err := convertToMaskingPolicy(d) - if err != nil { - return diag.FromErr(err) - } - patch.Policy = &v1pb.Policy_MaskingPolicy{ - MaskingPolicy: maskingPolicy, - } - case v1pb.PolicyType_MASKING_EXCEPTION: + if policyType == v1pb.PolicyType_MASKING_EXCEPTION { maskingExceptionPolicy, err := convertToMaskingExceptionPolicy(d) if err != nil { return diag.FromErr(err) @@ -188,16 +177,6 @@ func resourcePolicyUpdate(ctx context.Context, d *schema.ResourceData, m interfa updateMasks = append(updateMasks, "enforce") } - if d.HasChange("masking_policy") { - updateMasks = append(updateMasks, "payload") - maskingPolicy, err := convertToMaskingPolicy(d) - if err != nil { - return diag.FromErr(err) - } - patch.Policy = &v1pb.Policy_MaskingPolicy{ - MaskingPolicy: maskingPolicy, - } - } if d.HasChange("masking_exception_policy") { updateMasks = append(updateMasks, "payload") maskingExceptionPolicy, err := convertToMaskingExceptionPolicy(d) @@ -229,10 +208,6 @@ func resourcePolicyUpdate(ctx context.Context, d *schema.ResourceData, m interfa return diags } -func convertToMaskingLevel(level string) v1pb.MaskingLevel { - return v1pb.MaskingLevel(v1pb.MaskingLevel_value[level]) -} - func convertToMaskingExceptionPolicy(d *schema.ResourceData) (*v1pb.MaskingExceptionPolicy, error) { rawList, ok := d.Get("masking_exception_policy").([]interface{}) if !ok || len(rawList) != 1 { @@ -282,7 +257,6 @@ func convertToMaskingExceptionPolicy(d *schema.ResourceData) (*v1pb.MaskingExcep Action: v1pb.MaskingExceptionPolicy_MaskingException_Action( v1pb.MaskingExceptionPolicy_MaskingException_Action_value[rawException["action"].(string)], ), - MaskingLevel: convertToMaskingLevel(rawException["masking_level"].(string)), Condition: &expr.Expr{ Expression: strings.Join(expressions, " && "), }, @@ -290,29 +264,3 @@ func convertToMaskingExceptionPolicy(d *schema.ResourceData) (*v1pb.MaskingExcep } return policy, nil } - -func convertToMaskingPolicy(d *schema.ResourceData) (*v1pb.MaskingPolicy, error) { - rawList, ok := d.Get("masking_policy").([]interface{}) - if !ok || len(rawList) != 1 { - return nil, errors.Errorf("invalid masking_policy") - } - - raw := rawList[0].(map[string]interface{}) - rawMaskList := raw["mask_data"].([]interface{}) - - policy := &v1pb.MaskingPolicy{} - - for _, maskData := range rawMaskList { - rawMask := maskData.(map[string]interface{}) - policy.MaskData = append(policy.MaskData, &v1pb.MaskData{ - Schema: rawMask["schema"].(string), - Table: rawMask["table"].(string), - Column: rawMask["column"].(string), - FullMaskingAlgorithmId: rawMask["full_masking_algorithm_id"].(string), - PartialMaskingAlgorithmId: rawMask["partial_masking_algorithm_id"].(string), - MaskingLevel: convertToMaskingLevel(rawMask["masking_level"].(string)), - }) - } - - return policy, nil -} diff --git a/provider/resource_policy_test.go b/provider/resource_policy_test.go index e78d951..cc87109 100644 --- a/provider/resource_policy_test.go +++ b/provider/resource_policy_test.go @@ -22,28 +22,11 @@ func TestAccPolicy(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckPolicyDestroy, Steps: []resource.TestStep{ - { - Config: testAccCheckPolicyResource( - "masking_policy", - "instances/test-sample-instance/databases/employee", - getMaskingPolicy("salary", "amount", v1pb.MaskingLevel_FULL), - v1pb.PolicyType_MASKING, - ), - Check: resource.ComposeTestCheckFunc( - internal.TestCheckResourceExists("bytebase_policy.masking_policy"), - resource.TestCheckResourceAttr("bytebase_policy.masking_policy", "type", v1pb.PolicyType_MASKING.String()), - resource.TestCheckResourceAttr("bytebase_policy.masking_policy", "masking_policy.#", "1"), - resource.TestCheckResourceAttr("bytebase_policy.masking_policy", "masking_policy.0.mask_data.#", "1"), - resource.TestCheckResourceAttr("bytebase_policy.masking_policy", "masking_policy.0.mask_data.0.table", "salary"), - resource.TestCheckResourceAttr("bytebase_policy.masking_policy", "masking_policy.0.mask_data.0.column", "amount"), - resource.TestCheckResourceAttr("bytebase_policy.masking_policy", "masking_policy.0.mask_data.0.masking_level", v1pb.MaskingLevel_FULL.String()), - ), - }, { Config: testAccCheckPolicyResource( "masking_exception_policy", "projects/project-sample", - getMaskingExceptionPolicy("instances/test-sample-instance/databases/employee", "salary", "amount", v1pb.MaskingLevel_PARTIAL), + getMaskingExceptionPolicy("instances/test-sample-instance/databases/employee", "salary", "amount"), v1pb.PolicyType_MASKING_EXCEPTION, ), Check: resource.ComposeTestCheckFunc( @@ -53,7 +36,6 @@ func TestAccPolicy(t *testing.T) { resource.TestCheckResourceAttr("bytebase_policy.masking_exception_policy", "masking_exception_policy.0.exceptions.#", "1"), resource.TestCheckResourceAttr("bytebase_policy.masking_exception_policy", "masking_exception_policy.0.exceptions.0.table", "salary"), resource.TestCheckResourceAttr("bytebase_policy.masking_exception_policy", "masking_exception_policy.0.exceptions.0.column", "amount"), - resource.TestCheckResourceAttr("bytebase_policy.masking_exception_policy", "masking_exception_policy.0.exceptions.0.masking_level", v1pb.MaskingLevel_PARTIAL.String()), ), }, }, @@ -71,31 +53,18 @@ func testAccCheckPolicyResource(identifier, parent, payload string, pType v1pb.P `, identifier, parent, pType.String(), payload) } -func getMaskingPolicy(table, column string, level v1pb.MaskingLevel) string { - return fmt.Sprintf(` - masking_policy { - mask_data { - table = "%s" - column = "%s" - masking_level = "%s" - } - } - `, table, column, level.String()) -} - -func getMaskingExceptionPolicy(database, table, column string, level v1pb.MaskingLevel) string { +func getMaskingExceptionPolicy(database, table, column string) string { return fmt.Sprintf(` masking_exception_policy { exceptions { database = "%s" table = "%s" column = "%s" - masking_level = "%s" member = "user:ed@bytebase.com" action = "QUERY" } } - `, database, table, column, level.String()) + `, database, table, column) } func testAccCheckPolicyDestroy(s *terraform.State) error {