diff --git a/README.md b/README.md index 41d31df..816fbf4 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ using Terraform Bytebase Provider to prepare those instances ready for applicati - [Go](https://golang.org/doc/install) (1.19 or later) - [Terraform](https://developer.hashicorp.com/terraform/downloads?product_intent=terraform) (1.3.5 or later) -- [Bytebase](https://github.com/bytebase/bytebase) (3.5.0 or later) +- [Bytebase](https://github.com/bytebase/bytebase) (3.6.0 or later) > If you have problems running `terraform` in MacOS with Apple Silicon, you can following https://stackoverflow.com/questions/66281882/how-can-i-get-terraform-init-to-run-on-my-apple-silicon-macbook-pro-for-the-go and use the `tfenv`. diff --git a/VERSION b/VERSION index 1c2de38..321816a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.23 \ No newline at end of file +1.0.24 \ No newline at end of file diff --git a/api/client.go b/api/client.go index 17048eb..63bebcc 100644 --- a/api/client.go +++ b/api/client.go @@ -56,20 +56,6 @@ type Client interface { // GetCaller returns the API caller. GetCaller() *v1pb.User - // Environment - // CreateEnvironment creates the environment. - CreateEnvironment(ctx context.Context, environmentID string, create *v1pb.Environment) (*v1pb.Environment, error) - // GetEnvironment gets the environment by full name. - GetEnvironment(ctx context.Context, environmentName string) (*v1pb.Environment, error) - // ListEnvironment finds all environments. - ListEnvironment(ctx context.Context, showDeleted bool) (*v1pb.ListEnvironmentsResponse, error) - // UpdateEnvironment updates the environment. - UpdateEnvironment(ctx context.Context, patch *v1pb.Environment, updateMask []string) (*v1pb.Environment, error) - // DeleteEnvironment deletes the environment. - DeleteEnvironment(ctx context.Context, environmentName string) error - // UndeleteEnvironment undeletes the environment. - UndeleteEnvironment(ctx context.Context, environmentName string) (*v1pb.Environment, error) - // Instance // ListInstance will return instances. ListInstance(ctx context.Context, filter *InstanceFilter) ([]*v1pb.Instance, error) diff --git a/api/setting.go b/api/setting.go index 9cc1c36..53cc959 100644 --- a/api/setting.go +++ b/api/setting.go @@ -12,6 +12,8 @@ const ( SettingDataClassification SettingName = "bb.workspace.data-classification" // SettingSemanticTypes is the setting name for semantic types. SettingSemanticTypes SettingName = "bb.workspace.semantic-types" + // SettingEnvironment is the setting name for environments. + SettingEnvironment SettingName = "bb.workspace.environment" ) // RiskLevel is the approval risk level. @@ -41,13 +43,3 @@ func (r RiskLevel) Int() int { return 0 } } - -// ApprovalNodeType is the type for approval node. -type ApprovalNodeType string - -const ( - // ApprovalNodeTypeGroup means the approval node is a group. - ApprovalNodeTypeGroup ApprovalNodeType = "GROUP" - // ApprovalNodeTypeRole means the approval node is a role, the value should be role fullname. - ApprovalNodeTypeRole ApprovalNodeType = "ROLE" -) diff --git a/client/environment.go b/client/environment.go deleted file mode 100644 index eb9bbf3..0000000 --- a/client/environment.go +++ /dev/null @@ -1,106 +0,0 @@ -package client - -import ( - "context" - "fmt" - "net/http" - "strings" - - v1pb "github.com/bytebase/bytebase/proto/generated-go/v1" - "google.golang.org/protobuf/encoding/protojson" -) - -// CreateEnvironment creates the environment. -func (c *client) CreateEnvironment(ctx context.Context, environmentID string, create *v1pb.Environment) (*v1pb.Environment, error) { - payload, err := protojson.Marshal(create) - if err != nil { - return nil, err - } - - req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/%s/environments?environmentId=%s", c.url, c.version, environmentID), strings.NewReader(string(payload))) - if err != nil { - return nil, err - } - - body, err := c.doRequest(req) - if err != nil { - return nil, err - } - - var env v1pb.Environment - if err := ProtojsonUnmarshaler.Unmarshal(body, &env); err != nil { - return nil, err - } - - return &env, nil -} - -// GetEnvironment gets the environment by full name. -func (c *client) GetEnvironment(ctx context.Context, environmentName string) (*v1pb.Environment, error) { - body, err := c.getResource(ctx, environmentName) - if err != nil { - return nil, err - } - - var env v1pb.Environment - if err := ProtojsonUnmarshaler.Unmarshal(body, &env); err != nil { - return nil, err - } - - return &env, nil -} - -// ListEnvironment finds all environments. -func (c *client) ListEnvironment(ctx context.Context, showDeleted bool) (*v1pb.ListEnvironmentsResponse, error) { - req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s/environments?showDeleted=%v", c.url, c.version, showDeleted), nil) - if err != nil { - return nil, err - } - - body, err := c.doRequest(req) - if err != nil { - return nil, err - } - - var res v1pb.ListEnvironmentsResponse - if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil { - return nil, err - } - - return &res, nil -} - -// UpdateEnvironment updates the environment. -func (c *client) UpdateEnvironment(ctx context.Context, patch *v1pb.Environment, updateMasks []string) (*v1pb.Environment, error) { - body, err := c.updateResource(ctx, patch.Name, patch, updateMasks, false /* allow missing = false*/) - if err != nil { - return nil, err - } - - var env v1pb.Environment - if err := ProtojsonUnmarshaler.Unmarshal(body, &env); err != nil { - return nil, err - } - - return &env, nil -} - -// DeleteEnvironment deletes the environment. -func (c *client) DeleteEnvironment(ctx context.Context, environmentName string) error { - return c.deleteResource(ctx, environmentName) -} - -// UndeleteEnvironment undeletes the environment. -func (c *client) UndeleteEnvironment(ctx context.Context, environmentName string) (*v1pb.Environment, error) { - body, err := c.undeleteResource(ctx, environmentName) - if err != nil { - return nil, err - } - - var res v1pb.Environment - if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil { - return nil, err - } - - return &res, nil -} diff --git a/docs/data-sources/environment.md b/docs/data-sources/environment.md deleted file mode 100644 index e4e5a64..0000000 --- a/docs/data-sources/environment.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "bytebase_environment Data Source - terraform-provider-bytebase" -subcategory: "" -description: |- - The environment data source. ---- - -# bytebase_environment (Data Source) - -The environment data source. - - - - -## Schema - -### Required - -- `resource_id` (String) The environment unique resource id. - -### Read-Only - -- `environment_tier_policy` (String) If marked as PROTECTED, developers cannot execute any query on this environment's databases using SQL Editor by default. -- `id` (String) The ID of this resource. -- `name` (String) The environment full name in environments/{resource id} format. -- `order` (Number) The environment sorting order. -- `title` (String) The environment unique name. - - diff --git a/docs/data-sources/environment_list.md b/docs/data-sources/environment_list.md deleted file mode 100644 index 4c120c0..0000000 --- a/docs/data-sources/environment_list.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "bytebase_environment_list Data Source - terraform-provider-bytebase" -subcategory: "" -description: |- - The environment data source list. ---- - -# bytebase_environment_list (Data Source) - -The environment data source list. - - - - -## Schema - -### Optional - -- `show_deleted` (Boolean) Including removed instance in the response. - -### Read-Only - -- `environments` (List of Object) (see [below for nested schema](#nestedatt--environments)) -- `id` (String) The ID of this resource. - - -### Nested Schema for `environments` - -Read-Only: - -- `environment_tier_policy` (String) -- `name` (String) -- `order` (Number) -- `resource_id` (String) -- `title` (String) - - diff --git a/docs/data-sources/policy.md b/docs/data-sources/policy.md index 0f576cd..0ca5b20 100644 --- a/docs/data-sources/policy.md +++ b/docs/data-sources/policy.md @@ -42,7 +42,7 @@ Optional: ### Nested Schema for `global_masking_policy.rules` -Optional: +Required: - `condition` (String) The condition expression - `id` (String) The unique rule id @@ -60,13 +60,16 @@ Optional: ### Nested Schema for `masking_exception_policy.exceptions` -Optional: +Required: - `action` (String) -- `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 - `member` (String) The member in user:{email} or group:{email} format. + +Optional: + +- `column` (String) +- `expire_timestamp` (String) The expiration timestamp in YYYY-MM-DDThh:mm:ss.000Z format - `schema` (String) - `table` (String) diff --git a/docs/data-sources/setting.md b/docs/data-sources/setting.md index 8d0a8fd..89cc515 100644 --- a/docs/data-sources/setting.md +++ b/docs/data-sources/setting.md @@ -28,39 +28,49 @@ The setting data source. ### Read-Only - `approval_flow` (Block List) Configure risk level and approval flow for different tasks. Require ENTERPRISE subscription. (see [below for nested schema](#nestedblock--approval_flow)) +- `environment_setting` (Block List) The environment (see [below for nested schema](#nestedblock--environment_setting)) - `id` (String) The ID of this resource. ### Nested Schema for `classification` -Optional: +Required: -- `classification_from_config` (Boolean) If true, we will only store the classification in the config. Otherwise we will get the classification from table/column comment, and write back to the schema metadata. -- `classifications` (Block Set) (see [below for nested schema](#nestedblock--classification--classifications)) +- `classifications` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--classification--classifications)) - `id` (String) The classification unique uuid. -- `levels` (Block Set) (see [below for nested schema](#nestedblock--classification--levels)) +- `levels` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--classification--levels)) - `title` (String) The classification title. Optional. +Optional: + +- `classification_from_config` (Boolean) If true, we will only store the classification in the config. Otherwise we will get the classification from table/column comment, and write back to the schema metadata. + ### Nested Schema for `classification.classifications` +Required: + +- `id` (String) The classification unique id, must in {number}-{number} format. +- `title` (String) The classification title. + Optional: - `description` (String) The classification description. -- `id` (String) The classification unique id, must in {number}-{number} format. - `level` (String) The classification level id. -- `title` (String) The classification title. ### Nested Schema for `classification.levels` -Optional: +Required: -- `description` (String) The classification level description. - `id` (String) The classification level unique uuid. - `title` (String) The classification level title. +Optional: + +- `description` (String) The classification level description. + @@ -184,7 +194,28 @@ Read-Only: Read-Only: -- `node` (String) -- `type` (String) +- `role` (String) + + + + + + +### Nested Schema for `environment_setting` + +Read-Only: + +- `environment` (List of Object) (see [below for nested schema](#nestedatt--environment_setting--environment)) + + +### Nested Schema for `environment_setting.environment` + +Read-Only: + +- `color` (String) +- `id` (String) +- `name` (String) +- `protected` (Boolean) +- `title` (String) diff --git a/docs/resources/environment.md b/docs/resources/environment.md deleted file mode 100644 index d78ac86..0000000 --- a/docs/resources/environment.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "bytebase_environment Resource - terraform-provider-bytebase" -subcategory: "" -description: |- - The environment resource. ---- - -# bytebase_environment (Resource) - -The environment resource. - - - - -## Schema - -### Required - -- `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. - -### Read-Only - -- `id` (String) The ID of this resource. -- `name` (String) The environment full name in environments/{resource id} format. - - diff --git a/docs/resources/policy.md b/docs/resources/policy.md index dde2225..7e57836 100644 --- a/docs/resources/policy.md +++ b/docs/resources/policy.md @@ -42,7 +42,7 @@ Optional: ### Nested Schema for `global_masking_policy.rules` -Optional: +Required: - `condition` (String) The condition expression - `id` (String) The unique rule id @@ -60,13 +60,16 @@ Optional: ### Nested Schema for `masking_exception_policy.exceptions` -Optional: +Required: - `action` (String) -- `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 - `member` (String) The member in user:{email} or group:{email} format. + +Optional: + +- `column` (String) +- `expire_timestamp` (String) The expiration timestamp in YYYY-MM-DDThh:mm:ss.000Z format - `schema` (String) - `table` (String) diff --git a/docs/resources/setting.md b/docs/resources/setting.md index b282a8e..c3de427 100644 --- a/docs/resources/setting.md +++ b/docs/resources/setting.md @@ -23,6 +23,7 @@ The setting resource. - `approval_flow` (Block List) Configure risk level and approval flow for different tasks. Require ENTERPRISE subscription. (see [below for nested schema](#nestedblock--approval_flow)) - `classification` (Block List, Max: 1) Classification for data masking. Require ENTERPRISE subscription. (see [below for nested schema](#nestedblock--classification)) +- `environment_setting` (Block List) The environment (see [below for nested schema](#nestedblock--environment_setting)) - `semantic_types` (Block Set) Semantic types for data masking. Require ENTERPRISE subscription. (see [below for nested schema](#nestedblock--semantic_types)) - `workspace_profile` (Block List, Max: 1) (see [below for nested schema](#nestedblock--workspace_profile)) @@ -66,11 +67,7 @@ Optional: Required: -- `node` (String) - -Optional: - -- `type` (String) +- `role` (String) The role require to review in this step @@ -88,34 +85,69 @@ Optional: ### Nested Schema for `classification` -Optional: +Required: -- `classification_from_config` (Boolean) If true, we will only store the classification in the config. Otherwise we will get the classification from table/column comment, and write back to the schema metadata. -- `classifications` (Block Set) (see [below for nested schema](#nestedblock--classification--classifications)) +- `classifications` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--classification--classifications)) - `id` (String) The classification unique uuid. -- `levels` (Block Set) (see [below for nested schema](#nestedblock--classification--levels)) +- `levels` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--classification--levels)) - `title` (String) The classification title. Optional. +Optional: + +- `classification_from_config` (Boolean) If true, we will only store the classification in the config. Otherwise we will get the classification from table/column comment, and write back to the schema metadata. + ### Nested Schema for `classification.classifications` +Required: + +- `id` (String) The classification unique id, must in {number}-{number} format. +- `title` (String) The classification title. + Optional: - `description` (String) The classification description. -- `id` (String) The classification unique id, must in {number}-{number} format. - `level` (String) The classification level id. -- `title` (String) The classification title. ### Nested Schema for `classification.levels` -Optional: +Required: -- `description` (String) The classification level description. - `id` (String) The classification level unique uuid. - `title` (String) The classification level title. +Optional: + +- `description` (String) The classification level description. + + + + +### Nested Schema for `environment_setting` + +Required: + +- `environment` (Block List, Min: 1) (see [below for nested schema](#nestedblock--environment_setting--environment)) + + +### Nested Schema for `environment_setting.environment` + +Required: + +- `id` (String) The environment unique id. +- `title` (String) The environment display name. + +Optional: + +- `color` (String) The environment color. +- `protected` (Boolean) The environment is protected or not. + +Read-Only: + +- `name` (String) The environment readonly name in environments/{id} format. + diff --git a/examples/database/main.tf b/examples/database/main.tf index 2ef4fe9..014bbb0 100644 --- a/examples/database/main.tf +++ b/examples/database/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { bytebase = { - version = "1.0.23" + version = "1.0.24" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/environments/README.md b/examples/environments/README.md index 19418bb..2f7de87 100644 --- a/examples/environments/README.md +++ b/examples/environments/README.md @@ -7,13 +7,7 @@ You should replace the provider initial variables with your own and exec the [se ## List environment ```terraform -data "bytebase_environment_list" "all" {} -``` - -## Find environment by id - -```terraform -data "bytebase_environment" "find_env" { - resource_id = "" +data "bytebase_setting" "environments" { + name = "bb.workspace.environment" } ``` diff --git a/examples/environments/main.tf b/examples/environments/main.tf index 655382f..b116aad 100644 --- a/examples/environments/main.tf +++ b/examples/environments/main.tf @@ -1,8 +1,7 @@ -# Examples for query the environments terraform { required_providers { bytebase = { - version = "1.0.23" + version = "1.0.24" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } @@ -18,31 +17,11 @@ provider "bytebase" { url = "https://bytebase.example.com" } -locals { - environment_id_test = "test" - environment_id_prod = "prod" -} - # List all environment -data "bytebase_environment_list" "all" {} - -output "all_environments" { - value = data.bytebase_environment_list.all -} - -// Find a specific environment by the name -data "bytebase_environment" "test" { - resource_id = local.environment_id_test +data "bytebase_setting" "environments" { + name = "bb.workspace.environment" } -output "test_environment" { - value = data.bytebase_environment.test -} - -data "bytebase_environment" "prod" { - resource_id = local.environment_id_prod -} - -output "prod_environment" { - value = data.bytebase_environment.prod +output "all_environments" { + value = data.bytebase_setting.environments } diff --git a/examples/groups/main.tf b/examples/groups/main.tf index 306b2ba..606ec19 100644 --- a/examples/groups/main.tf +++ b/examples/groups/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.23" + version = "1.0.24" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/iamPolicy/main.tf b/examples/iamPolicy/main.tf index 8b1f4e0..9b5b8bf 100644 --- a/examples/iamPolicy/main.tf +++ b/examples/iamPolicy/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.23" + version = "1.0.24" # 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 6ee057d..a2bdeec 100644 --- a/examples/instances/main.tf +++ b/examples/instances/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { bytebase = { - version = "1.0.23" + version = "1.0.24" # 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 4660ef3..d2767cf 100644 --- a/examples/policies/main.tf +++ b/examples/policies/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.23" + version = "1.0.24" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/projects/main.tf b/examples/projects/main.tf index f45471a..33d8fb8 100644 --- a/examples/projects/main.tf +++ b/examples/projects/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { bytebase = { - version = "1.0.23" + version = "1.0.24" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/roles/main.tf b/examples/roles/main.tf index b183285..42e3464 100644 --- a/examples/roles/main.tf +++ b/examples/roles/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.23" + version = "1.0.24" # 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 a3cddd2..f79e136 100644 --- a/examples/settings/main.tf +++ b/examples/settings/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.23" + version = "1.0.24" # 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 79201e9..4a4f799 100644 --- a/examples/setup/approval_flow.tf +++ b/examples/setup/approval_flow.tf @@ -3,19 +3,21 @@ resource "bytebase_setting" "approval_flow" { approval_flow { rules { flow { - title = "DBA -> OWNER" - description = "Need DBA and workspace owner approval" + title = "Project Owner -> DBA -> Admin" + description = "Need DBA and workspace admin approval" creator = "users/support@bytebase.com" # Approval flow following the step order. steps { - type = "GROUP" - node = "WORKSPACE_DBA" + role = "roles/projectOwner" } steps { - type = "GROUP" - node = "WORKSPACE_OWNER" + role = "roles/workspaceDBA" + } + + steps { + role = "roles/workspaceAdmin" } } diff --git a/examples/setup/data_masking.tf b/examples/setup/data_masking.tf index 7aead21..af07e2c 100644 --- a/examples/setup/data_masking.tf +++ b/examples/setup/data_masking.tf @@ -129,10 +129,10 @@ resource "bytebase_policy" "masking_exception_policy" { resource "bytebase_policy" "global_masking_policy" { depends_on = [ bytebase_instance.prod, - bytebase_environment.test + bytebase_setting.environments ] - parent = "" + parent = "workspaces/-" type = "MASKING_RULE" enforce = true inherit_from_parent = false diff --git a/examples/setup/database.tf b/examples/setup/database.tf index b8183a3..fbb54c8 100644 --- a/examples/setup/database.tf +++ b/examples/setup/database.tf @@ -2,12 +2,12 @@ resource "bytebase_database" "database" { depends_on = [ bytebase_instance.test, bytebase_project.sample_project, - bytebase_environment.test + bytebase_setting.environments ] name = "instances/test-sample-instance/databases/employee" project = bytebase_project.sample_project.name - environment = bytebase_environment.test.name + environment = bytebase_setting.environments.environment_setting[0].environment[0].name catalog { schemas { diff --git a/examples/setup/environment.tf b/examples/setup/environment.tf index 92b8d07..c175259 100644 --- a/examples/setup/environment.tf +++ b/examples/setup/environment.tf @@ -1,15 +1,16 @@ -# Create a new environment named "Test" -resource "bytebase_environment" "test" { - resource_id = local.environment_id_test - title = "Test" - order = 0 - environment_tier_policy = "UNPROTECTED" -} +resource "bytebase_setting" "environments" { + name = "bb.workspace.environment" + environment_setting { + environment { + id = local.environment_id_test + title = "Test" + protected = false + } -# Create another environment named "Prod" -resource "bytebase_environment" "prod" { - resource_id = local.environment_id_prod - title = "Prod" - order = 1 - environment_tier_policy = "PROTECTED" + environment { + id = local.environment_id_prod + title = "Prod" + protected = true + } + } } diff --git a/examples/setup/instance.tf b/examples/setup/instance.tf index 5494ef4..b624b0b 100644 --- a/examples/setup/instance.tf +++ b/examples/setup/instance.tf @@ -3,10 +3,11 @@ # You can replace the parameters with your real instance resource "bytebase_instance" "test" { depends_on = [ - bytebase_environment.test + bytebase_setting.environments ] + resource_id = local.instance_id_test - environment = bytebase_environment.test.name + environment = bytebase_setting.environments.environment_setting[0].environment[0].name title = "test instance" engine = "MYSQL" activation = true @@ -44,11 +45,11 @@ resource "bytebase_instance" "test" { # Create a new instance named "prod instance" resource "bytebase_instance" "prod" { depends_on = [ - bytebase_environment.prod + bytebase_setting.environments ] resource_id = local.instance_id_prod - environment = bytebase_environment.prod.name + environment = bytebase_setting.environments.environment_setting[0].environment[1].name title = "prod instance" engine = "POSTGRES" diff --git a/examples/setup/sql_review.tf b/examples/setup/sql_review.tf index 786d4c3..c017046 100644 --- a/examples/setup/sql_review.tf +++ b/examples/setup/sql_review.tf @@ -1,15 +1,14 @@ resource "bytebase_review_config" "sample" { depends_on = [ - bytebase_environment.test, - bytebase_environment.prod + bytebase_setting.environments ] resource_id = "review-config-sample" title = "Sample SQL Review Config" enabled = true resources = toset([ - bytebase_environment.test.name, - bytebase_environment.prod.name + bytebase_setting.environments.environment_setting[0].environment[0].name, + bytebase_setting.environments.environment_setting[0].environment[1].name ]) rules { type = "column.no-null" diff --git a/examples/sql_review/main.tf b/examples/sql_review/main.tf index 4328808..96e8edf 100644 --- a/examples/sql_review/main.tf +++ b/examples/sql_review/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.23" + version = "1.0.24" # 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 5206a3f..15acb14 100644 --- a/examples/users/main.tf +++ b/examples/users/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { bytebase = { - version = "1.0.23" + version = "1.0.24" # 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 87066bd..e471b0c 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,9 @@ module github.com/bytebase/terraform-provider-bytebase -go 1.24.0 - -toolchain go1.24.1 +go 1.24.2 require ( - github.com/bytebase/bytebase v0.0.0-20250313084449-2ed26990a507 + github.com/bytebase/bytebase v0.0.0-20250424073126-d57cbba37d61 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 @@ -13,7 +11,7 @@ require ( github.com/pkg/errors v0.9.1 google.golang.org/genproto v0.0.0-20250311190419-81fb87f6b8bf google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf - google.golang.org/protobuf v1.36.5 + google.golang.org/protobuf v1.36.6 ) require ( @@ -49,7 +47,7 @@ require ( github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.13 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/cli v1.1.4 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -66,10 +64,10 @@ 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.36.0 // indirect - golang.org/x/net v0.37.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf // indirect google.golang.org/grpc v1.71.0 // indirect diff --git a/go.sum b/go.sum index cc30097..118c4c1 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,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-20250313084449-2ed26990a507 h1:JMBYOPpRjoeWZt8QsFvzKe/JvelebOGhEkAJapkT1NM= -github.com/bytebase/bytebase v0.0.0-20250313084449-2ed26990a507/go.mod h1:yGEPeD42x5lqzB5FwA2H87/eWPZKfU7vyMN8xF+HYC0= +github.com/bytebase/bytebase v0.0.0-20250424073126-d57cbba37d61 h1:+ptgPqM2aSzlPjeauecETOwbuGcoSKd5wUsNLLtMuCQ= +github.com/bytebase/bytebase v0.0.0-20250424073126-d57cbba37d61/go.mod h1:Gu5A9lSsc8OMJ5nUbKAxOn5X8gDj1Rxuzy0NwxVt90k= 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= @@ -156,12 +156,11 @@ github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlW github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/cli v1.1.4 h1:qj8czE26AU4PbiaPXK5uVmMSM+V5BYsFBiM9HhGRLUA= @@ -254,8 +253,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.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 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= @@ -266,8 +265,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.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 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= @@ -287,17 +286,16 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 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= @@ -312,8 +310,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 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_environment.go b/provider/data_source_environment.go deleted file mode 100644 index f6734e4..0000000 --- a/provider/data_source_environment.go +++ /dev/null @@ -1,61 +0,0 @@ -package provider - -import ( - "context" - "fmt" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - "github.com/bytebase/terraform-provider-bytebase/api" - "github.com/bytebase/terraform-provider-bytebase/provider/internal" -) - -func dataSourceEnvironment() *schema.Resource { - return &schema.Resource{ - Description: "The environment data source.", - ReadContext: dataSourceEnvironmentRead, - Schema: map[string]*schema.Schema{ - "resource_id": { - Type: schema.TypeString, - Required: true, - ValidateFunc: internal.ResourceIDValidation, - Description: "The environment unique resource id.", - }, - "name": { - Type: schema.TypeString, - Computed: true, - Description: "The environment full name in environments/{resource id} format.", - }, - "title": { - Type: schema.TypeString, - Computed: true, - Description: "The environment unique name.", - }, - "order": { - Type: schema.TypeInt, - Computed: true, - Description: "The environment sorting order.", - }, - "environment_tier_policy": { - Type: schema.TypeString, - Computed: true, - Description: "If marked as PROTECTED, developers cannot execute any query on this environment's databases using SQL Editor by default.", - }, - }, - } -} - -func dataSourceEnvironmentRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - c := m.(api.Client) - environmentName := fmt.Sprintf("%s%s", internal.EnvironmentNamePrefix, d.Get("resource_id").(string)) - - environment, err := c.GetEnvironment(ctx, environmentName) - if err != nil { - return diag.FromErr(err) - } - - d.SetId(environment.Name) - - return setEnvironment(d, environment) -} diff --git a/provider/data_source_environment_list.go b/provider/data_source_environment_list.go deleted file mode 100644 index 6940413..0000000 --- a/provider/data_source_environment_list.go +++ /dev/null @@ -1,99 +0,0 @@ -package provider - -import ( - "context" - "strconv" - "time" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - "github.com/bytebase/terraform-provider-bytebase/api" - "github.com/bytebase/terraform-provider-bytebase/provider/internal" -) - -func dataSourceEnvironmentList() *schema.Resource { - return &schema.Resource{ - Description: "The environment data source list.", - ReadContext: dataSourceEnvironmentListRead, - Schema: map[string]*schema.Schema{ - "show_deleted": { - Type: schema.TypeBool, - Optional: true, - Default: false, - Description: "Including removed instance in the response.", - }, - "environments": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "resource_id": { - Type: schema.TypeString, - Computed: true, - Description: "The environment unique resource id.", - }, - "name": { - Type: schema.TypeString, - Computed: true, - Description: "The environment full name in environments/{resource id} format.", - }, - "title": { - Type: schema.TypeString, - Computed: true, - Description: "The environment unique name.", - }, - "order": { - Type: schema.TypeInt, - Computed: true, - Description: "The environment sorting order.", - }, - "environment_tier_policy": { - Type: schema.TypeString, - Computed: true, - Description: "If marked as PROTECTED, developers cannot execute any query on this environment's databases using SQL Editor by default.", - }, - }, - }, - }, - }, - } -} - -func dataSourceEnvironmentListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - c := m.(api.Client) - - // Warning or errors can be collected in a slice type - var diags diag.Diagnostics - - response, err := c.ListEnvironment(ctx, d.Get("show_deleted").(bool)) - if err != nil { - return diag.FromErr(err) - } - - environments := []map[string]interface{}{} - for _, environment := range response.Environments { - envID, err := internal.GetEnvironmentID(environment.Name) - if err != nil { - return diag.FromErr(err) - } - - env := make(map[string]interface{}) - env["resource_id"] = envID - env["title"] = environment.Title - env["name"] = environment.Name - env["order"] = environment.Order - env["environment_tier_policy"] = environment.Tier.String() - - environments = append(environments, env) - } - - if err := d.Set("environments", environments); err != nil { - return diag.FromErr(err) - } - - // always refresh - d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) - - return diags -} diff --git a/provider/data_source_environment_list_test.go b/provider/data_source_environment_list_test.go deleted file mode 100644 index 708bf70..0000000 --- a/provider/data_source_environment_list_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package provider - -import ( - "fmt" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - - "github.com/bytebase/terraform-provider-bytebase/provider/internal" -) - -func TestAccEnvironmentListDataSource(t *testing.T) { - identifier := "new-environment" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - }, - Providers: testAccProviders, - CheckDestroy: testAccCheckEnvironmentDestroy, - Steps: []resource.TestStep{ - internal.GetTestStepForDataSourceList( - "", - "", - "bytebase_environment_list", - "before", - "environments", - 0, - ), - internal.GetTestStepForDataSourceList( - testAccCheckEnvironmentResource(identifier, "test", 1), - fmt.Sprintf("bytebase_environment.%s", identifier), - "bytebase_environment_list", - "after", - "environments", - 1, - ), - }, - }) -} diff --git a/provider/data_source_environment_test.go b/provider/data_source_environment_test.go deleted file mode 100644 index ac1f2f0..0000000 --- a/provider/data_source_environment_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package provider - -import ( - "fmt" - "regexp" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - - "github.com/bytebase/terraform-provider-bytebase/provider/internal" -) - -func TestAccEnvironmentDataSource(t *testing.T) { - identifier := "test" - resourceName := fmt.Sprintf("bytebase_environment.%s", identifier) - title := "test" - order := 1 - - resource.Test(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - }, - Providers: testAccProviders, - CheckDestroy: testAccCheckEnvironmentDestroy, - Steps: []resource.TestStep{ - // get single environment test - { - Config: testAccCheckEnvironmentDataSource( - testAccCheckEnvironmentResource(identifier, title, order), - resourceName, - identifier, - ), - Check: resource.ComposeTestCheckFunc( - internal.TestCheckResourceExists(fmt.Sprintf("data.%s", resourceName)), - resource.TestCheckResourceAttr(fmt.Sprintf("data.%s", resourceName), "title", title), - resource.TestCheckResourceAttr(fmt.Sprintf("data.%s", resourceName), "order", fmt.Sprintf("%d", order)), - resource.TestCheckResourceAttr(fmt.Sprintf("data.%s", resourceName), "environment_tier_policy", "PROTECTED"), - ), - }, - }, - }) -} - -func TestAccEnvironmentDataSource_NotFound(t *testing.T) { - identifier := "dev-notfound" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - }, - Providers: testAccProviders, - CheckDestroy: testAccCheckEnvironmentDestroy, - Steps: []resource.TestStep{ - { - Config: testAccCheckEnvironmentDataSource( - "", - "", - identifier, - ), - ExpectError: regexp.MustCompile("Cannot found environment"), - }, - }, - }) -} - -func testAccCheckEnvironmentDataSource( - resource, - dependsOn, - identifier string, -) string { - return fmt.Sprintf(` - %s - - data "bytebase_environment" "%s" { - resource_id = "%s" - depends_on = [ - %s - ] - } - `, resource, identifier, identifier, dependsOn) -} diff --git a/provider/data_source_policy.go b/provider/data_source_policy.go index 699cd9a..829dab9 100644 --- a/provider/data_source_policy.go +++ b/provider/data_source_policy.go @@ -29,7 +29,7 @@ func dataSourcePolicy() *schema.Resource { Default: "", ValidateDiagFunc: internal.ResourceNameValidation( // workspace policy - regexp.MustCompile("^workspaces/-$"), + regexp.MustCompile(fmt.Sprintf("^%s$", internal.WorkspaceName)), // environment policy regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.EnvironmentNamePrefix, internal.ResourceIDPattern)), // instance policy @@ -91,8 +91,7 @@ func getMaskingExceptionPolicySchema(computed bool) *schema.Schema { Schema: map[string]*schema.Schema{ "database": { Type: schema.TypeString, - Computed: computed, - Optional: true, + Required: true, ValidateFunc: validation.StringIsNotEmpty, Description: "The database full name in instances/{instance resource id}/databases/{database name} format", }, @@ -115,15 +114,13 @@ func getMaskingExceptionPolicySchema(computed bool) *schema.Schema { }, "member": { Type: schema.TypeString, - Computed: computed, - Optional: true, + Required: true, ValidateFunc: validation.StringIsNotEmpty, Description: "The member in user:{email} or group:{email} format.", }, "action": { Type: schema.TypeString, - Computed: computed, - Optional: true, + Required: true, ValidateFunc: validation.StringInSlice([]string{ v1pb.MaskingExceptionPolicy_MaskingException_QUERY.String(), v1pb.MaskingExceptionPolicy_MaskingException_EXPORT.String(), @@ -164,22 +161,19 @@ func getGlobalMaskingPolicySchema(computed bool) *schema.Schema { Schema: map[string]*schema.Schema{ "id": { Type: schema.TypeString, - Computed: computed, - Optional: true, + Required: true, ValidateFunc: validation.StringIsNotEmpty, Description: "The unique rule id", }, "semantic_type": { Type: schema.TypeString, - Computed: computed, - Optional: true, + Required: true, ValidateFunc: validation.StringIsNotEmpty, Description: "The semantic type id", }, "condition": { Type: schema.TypeString, - Computed: computed, - Optional: true, + Required: true, ValidateFunc: validation.StringIsNotEmpty, Description: "The condition expression", }, @@ -195,6 +189,10 @@ func dataSourcePolicyRead(ctx context.Context, d *schema.ResourceData, m interfa c := m.(api.Client) policyName := fmt.Sprintf("%s/%s%s", d.Get("parent").(string), internal.PolicyNamePrefix, d.Get("type").(string)) + if strings.HasPrefix(policyName, internal.WorkspaceName) { + policyName = strings.TrimPrefix(policyName, fmt.Sprintf("%s/", internal.WorkspaceName)) + } + policy, err := c.GetPolicy(ctx, policyName) if err != nil { return diag.FromErr(err) diff --git a/provider/data_source_policy_list.go b/provider/data_source_policy_list.go index b4d3a5f..cf51cf8 100644 --- a/provider/data_source_policy_list.go +++ b/provider/data_source_policy_list.go @@ -27,7 +27,7 @@ func dataSourcePolicyList() *schema.Resource { Default: "", ValidateDiagFunc: internal.ResourceNameValidation( // workspace policy - regexp.MustCompile("^workspaces/-$"), + regexp.MustCompile(fmt.Sprintf("^%s$", internal.WorkspaceName)), // environment policy regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.EnvironmentNamePrefix, internal.ResourceIDPattern)), // instance policy @@ -76,7 +76,12 @@ func dataSourcePolicyList() *schema.Resource { func dataSourcePolicyListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(api.Client) - response, err := c.ListPolicies(ctx, d.Get("parent").(string)) + parent := d.Get("parent").(string) + if parent == internal.WorkspaceName { + parent = "" + } + + response, err := c.ListPolicies(ctx, parent) if err != nil { return diag.FromErr(err) } diff --git a/provider/data_source_setting.go b/provider/data_source_setting.go index 65dae93..2c409be 100644 --- a/provider/data_source_setting.go +++ b/provider/data_source_setting.go @@ -3,6 +3,7 @@ package provider import ( "context" "fmt" + "regexp" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -29,12 +30,14 @@ func dataSourceSetting() *schema.Resource { string(api.SettingWorkspaceProfile), string(api.SettingDataClassification), string(api.SettingSemanticTypes), + string(api.SettingEnvironment), }, false), }, - "approval_flow": getWorkspaceApprovalSetting(true), - "workspace_profile": getWorkspaceProfileSetting(true), - "classification": getClassificationSetting(true), - "semantic_types": getSemanticTypesSetting(true), + "approval_flow": getWorkspaceApprovalSetting(true), + "workspace_profile": getWorkspaceProfileSetting(true), + "classification": getClassificationSetting(true), + "semantic_types": getSemanticTypesSetting(true), + "environment_setting": getEnvironmentSetting(true), }, } } @@ -198,14 +201,12 @@ func getClassificationSetting(computed bool) *schema.Schema { Schema: map[string]*schema.Schema{ "id": { Type: schema.TypeString, - Computed: computed, - Optional: true, + Required: true, Description: "The classification unique uuid.", }, "title": { Type: schema.TypeString, - Computed: computed, - Optional: true, + Required: true, Description: "The classification title. Optional.", }, "classification_from_config": { @@ -215,22 +216,19 @@ func getClassificationSetting(computed bool) *schema.Schema { Description: "If true, we will only store the classification in the config. Otherwise we will get the classification from table/column comment, and write back to the schema metadata.", }, "levels": { - Computed: computed, - Optional: true, + Required: true, Type: schema.TypeSet, MinItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "id": { Type: schema.TypeString, - Computed: computed, - Optional: true, + Required: true, Description: "The classification level unique uuid.", }, "title": { Type: schema.TypeString, - Computed: computed, - Optional: true, + Required: true, Description: "The classification level title.", }, "description": { @@ -244,22 +242,19 @@ func getClassificationSetting(computed bool) *schema.Schema { Set: itemIDHash, }, "classifications": { - Computed: computed, - Optional: true, + Required: true, Type: schema.TypeSet, MinItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "id": { Type: schema.TypeString, - Computed: computed, - Optional: true, + Required: true, Description: "The classification unique id, must in {number}-{number} format.", }, "title": { Type: schema.TypeString, - Computed: computed, - Optional: true, + Required: true, Description: "The classification title.", }, "description": { @@ -331,7 +326,55 @@ func getWorkspaceProfileSetting(computed bool) *schema.Schema { } } -// TODO(ed): API changed. +func getEnvironmentSetting(computed bool) *schema.Schema { + return &schema.Schema{ + Computed: computed, + Optional: true, + Default: nil, + Type: schema.TypeList, + Description: "The environment", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "environment": { + Type: schema.TypeList, + Computed: computed, + Required: !computed, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: internal.ResourceIDValidation, + Description: "The environment unique id.", + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: "The environment readonly name in environments/{id} format.", + }, + "title": { + Type: schema.TypeString, + Required: true, + Description: "The environment display name.", + }, + "color": { + Type: schema.TypeString, + Optional: true, + Description: "The environment color.", + }, + "protected": { + Type: schema.TypeBool, + Optional: true, + Description: "The environment is protected or not.", + }, + }, + }, + }, + }, + }, + } +} + func getWorkspaceApprovalSetting(computed bool) *schema.Schema { return &schema.Schema{ Computed: computed, @@ -376,20 +419,13 @@ func getWorkspaceApprovalSetting(computed bool) *schema.Schema { Description: "Approval flow following the step order.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Computed: computed, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - string(api.ApprovalNodeTypeGroup), - string(api.ApprovalNodeTypeRole), - }, false), - }, - "node": { - Required: !computed, - Default: nil, - Computed: computed, + "role": { Type: schema.TypeString, + Required: true, + ValidateDiagFunc: internal.ResourceNameValidation( + regexp.MustCompile(fmt.Sprintf("^%s", internal.RoleNamePrefix)), + ), + Description: "The role require to review in this step", }, }, }, @@ -454,7 +490,7 @@ func dataSourceSettingRead(ctx context.Context, d *schema.ResourceData, m interf } func setSettingMessage(ctx context.Context, d *schema.ResourceData, client api.Client, setting *v1pb.Setting) diag.Diagnostics { - if value := setting.Value.GetWorkspaceApprovalSettingValue(); value != nil { + if value := setting.GetValue().GetWorkspaceApprovalSettingValue(); value != nil { settingVal, err := flattenWorkspaceApprovalSetting(ctx, client, value) if err != nil { return diag.Errorf("failed to parse workspace_approval_setting: %s", err.Error()) @@ -463,28 +499,54 @@ func setSettingMessage(ctx context.Context, d *schema.ResourceData, client api.C return diag.Errorf("cannot set workspace_approval_setting: %s", err.Error()) } } - if value := setting.Value.GetWorkspaceProfileSettingValue(); value != nil { + if value := setting.GetValue().GetWorkspaceProfileSettingValue(); value != nil { settingVal := flattenWorkspaceProfileSetting(value) if err := d.Set("workspace_profile", settingVal); err != nil { return diag.Errorf("cannot set workspace_profile: %s", err.Error()) } } - if value := setting.Value.GetDataClassificationSettingValue(); value != nil { + if value := setting.GetValue().GetDataClassificationSettingValue(); value != nil { settingVal := flattenClassificationSetting(value) if err := d.Set("classification", settingVal); err != nil { return diag.Errorf("cannot set classification: %s", err.Error()) } } - if value := setting.Value.GetSemanticTypeSettingValue(); value != nil { + if value := setting.GetValue().GetSemanticTypeSettingValue(); value != nil { settingVal := flattenSemanticTypesSetting(value) if err := d.Set("semantic_types", schema.NewSet(itemIDHash, settingVal)); err != nil { return diag.Errorf("cannot set semantic_types: %s", err.Error()) } } + if value := setting.GetValue().GetEnvironmentSetting(); value != nil { + settingVal := flattenEnvironmentSetting(value) + if err := d.Set("environment_setting", settingVal); err != nil { + return diag.Errorf("cannot set environment_setting: %s", err.Error()) + } + } return nil } +func flattenEnvironmentSetting(setting *v1pb.EnvironmentSetting) []interface{} { + environmentList := []interface{}{} + + for _, environment := range setting.GetEnvironments() { + raw := map[string]interface{}{ + "id": environment.Id, + "name": environment.Name, + "color": environment.Color, + "title": environment.Title, + "protected": environment.Tags["protected"] == "protected", + } + environmentList = append(environmentList, raw) + } + + environmentSetting := map[string]interface{}{ + "environment": environmentList, + } + return []interface{}{environmentSetting} +} + func parseApprovalExpression(callExpr *v1alpha1.Expr_Call) ([]map[string]interface{}, error) { if callExpr == nil { return nil, errors.Errorf("failed to parse the expression") @@ -552,18 +614,12 @@ func flattenWorkspaceApprovalSetting(ctx context.Context, client api.Client, set for _, rule := range setting.Rules { stepList := []interface{}{} for _, step := range rule.Template.Flow.Steps { + rawStep := map[string]interface{}{} for _, node := range step.Nodes { - rawNode := map[string]interface{}{} - switch payload := node.Payload.(type) { - case *v1pb.ApprovalNode_Role: - rawNode["type"] = string(api.ApprovalNodeTypeRole) - rawNode["node"] = payload.Role - case *v1pb.ApprovalNode_GroupValue_: - rawNode["type"] = string(api.ApprovalNodeTypeGroup) - rawNode["node"] = payload.GroupValue.String() - } - stepList = append(stepList, rawNode) + rawStep["role"] = node.Role + break } + stepList = append(stepList, rawStep) } conditionList := []map[string]interface{}{} diff --git a/provider/internal/mock_client.go b/provider/internal/mock_client.go index 8b8a277..4dc84e1 100644 --- a/provider/internal/mock_client.go +++ b/provider/internal/mock_client.go @@ -14,7 +14,6 @@ import ( v1alpha1 "google.golang.org/genproto/googleapis/api/expr/v1alpha1" ) -var environmentMap map[string]*v1pb.Environment var instanceMap map[string]*v1pb.Instance var policyMap map[string]*v1pb.Policy var projectMap map[string]*v1pb.Project @@ -27,7 +26,6 @@ var roleMap map[string]*v1pb.Role var groupMap map[string]*v1pb.Group func init() { - environmentMap = map[string]*v1pb.Environment{} instanceMap = map[string]*v1pb.Instance{} policyMap = map[string]*v1pb.Policy{} projectMap = map[string]*v1pb.Project{} @@ -41,7 +39,6 @@ func init() { } type mockClient struct { - environmentMap map[string]*v1pb.Environment instanceMap map[string]*v1pb.Instance policyMap map[string]*v1pb.Policy projectMap map[string]*v1pb.Project @@ -58,7 +55,6 @@ type mockClient struct { // newMockClient returns the new Bytebase API mock client. func newMockClient(_, _, _ string) (api.Client, error) { return &mockClient{ - environmentMap: environmentMap, instanceMap: instanceMap, policyMap: policyMap, projectMap: projectMap, @@ -81,96 +77,6 @@ func (*mockClient) GetCaller() *v1pb.User { } } -// CreateEnvironment creates the environment. -func (c *mockClient) CreateEnvironment(_ context.Context, environmentID string, create *v1pb.Environment) (*v1pb.Environment, error) { - env := &v1pb.Environment{ - Name: fmt.Sprintf("%s%s", EnvironmentNamePrefix, environmentID), - Order: create.Order, - Title: create.Title, - State: v1pb.State_ACTIVE, - Tier: create.Tier, - } - - if _, ok := c.environmentMap[env.Name]; ok { - return nil, errors.Errorf("Environment %s already exists", env.Name) - } - - c.environmentMap[env.Name] = env - - return env, nil -} - -// GetEnvironment gets the environment by id. -func (c *mockClient) GetEnvironment(_ context.Context, environmentName string) (*v1pb.Environment, error) { - env, ok := c.environmentMap[environmentName] - if !ok { - return nil, errors.Errorf("Cannot found environment %s", environmentName) - } - - return env, nil -} - -// ListEnvironment finds all environments. -func (c *mockClient) ListEnvironment(_ context.Context, showDeleted bool) (*v1pb.ListEnvironmentsResponse, error) { - environments := make([]*v1pb.Environment, 0) - for _, env := range c.environmentMap { - if env.State == v1pb.State_DELETED && !showDeleted { - continue - } - environments = append(environments, env) - } - - return &v1pb.ListEnvironmentsResponse{ - Environments: environments, - }, nil -} - -// UpdateEnvironment updates the environment. -func (c *mockClient) UpdateEnvironment(ctx context.Context, patch *v1pb.Environment, updateMasks []string) (*v1pb.Environment, error) { - env, err := c.GetEnvironment(ctx, patch.Name) - if err != nil { - return nil, err - } - - if slices.Contains(updateMasks, "title") { - env.Title = patch.Title - } - if slices.Contains(updateMasks, "order") { - env.Order = patch.Order - } - if slices.Contains(updateMasks, "tier") { - env.Tier = patch.Tier - } - - c.environmentMap[env.Name] = env - - return env, nil -} - -// DeleteEnvironment deletes the environment. -func (c *mockClient) DeleteEnvironment(ctx context.Context, environmentName string) error { - env, err := c.GetEnvironment(ctx, environmentName) - if err != nil { - return err - } - - env.State = v1pb.State_DELETED - c.environmentMap[env.Name] = env - return nil -} - -// UndeleteEnvironment undeletes the environment. -func (c *mockClient) UndeleteEnvironment(ctx context.Context, environmentName string) (*v1pb.Environment, error) { - env, err := c.GetEnvironment(ctx, environmentName) - if err != nil { - return nil, err - } - - env.State = v1pb.State_ACTIVE - c.environmentMap[env.Name] = env - return env, nil -} - // ListInstance will return instances in environment. func (c *mockClient) ListInstance(_ context.Context, filter *api.InstanceFilter) ([]*v1pb.Instance, error) { instances := make([]*v1pb.Instance, 0) diff --git a/provider/internal/utils.go b/provider/internal/utils.go index 24a9e8d..0df7d06 100644 --- a/provider/internal/utils.go +++ b/provider/internal/utils.go @@ -43,14 +43,17 @@ const ( DatabaseCatalogNameSuffix = "/catalog" // ResourceIDPattern is the pattern for resource id. ResourceIDPattern = "[a-z]([a-z0-9-]{0,61}[a-z0-9])?" + // WorkspaceName is the name for workspace resource. + WorkspaceName = "workspaces/-" ) var ( - resourceIDRegex = regexp.MustCompile(fmt.Sprintf("^%s$", ResourceIDPattern)) + // ResourceIDRegex is the regex for resource id. + ResourceIDRegex = regexp.MustCompile(fmt.Sprintf("^%s$", ResourceIDPattern)) ) // ResourceIDValidation is the resource id regexp validation. -var ResourceIDValidation = validation.StringMatch(resourceIDRegex, fmt.Sprintf("resource id must matches %v", resourceIDRegex)) +var ResourceIDValidation = validation.StringMatch(ResourceIDRegex, fmt.Sprintf("resource id must matches %v", ResourceIDRegex)) // ResourceNameValidation validate the resource name with prefix. func ResourceNameValidation(regexs ...*regexp.Regexp) schema.SchemaValidateDiagFunc { diff --git a/provider/provider.go b/provider/provider.go index 5d39023..04bf561 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -51,8 +51,6 @@ func NewProvider() *schema.Provider { DataSourcesMap: map[string]*schema.Resource{ "bytebase_instance": dataSourceInstance(), "bytebase_instance_list": dataSourceInstanceList(), - "bytebase_environment": dataSourceEnvironment(), - "bytebase_environment_list": dataSourceEnvironmentList(), "bytebase_policy": dataSourcePolicy(), "bytebase_policy_list": dataSourcePolicyList(), "bytebase_project": dataSourceProject(), @@ -71,7 +69,6 @@ func NewProvider() *schema.Provider { "bytebase_iam_policy": dataSourceIAMPolicy(), }, ResourcesMap: map[string]*schema.Resource{ - "bytebase_environment": resourceEnvironment(), "bytebase_instance": resourceInstance(), "bytebase_policy": resourcePolicy(), "bytebase_project": resourceProjct(), diff --git a/provider/resource_environment.go b/provider/resource_environment.go deleted file mode 100644 index 9a017b3..0000000 --- a/provider/resource_environment.go +++ /dev/null @@ -1,275 +0,0 @@ -package provider - -import ( - "context" - "fmt" - "regexp" - - "github.com/hashicorp/terraform-plugin-log/tflog" - "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" - - v1pb "github.com/bytebase/bytebase/proto/generated-go/v1" - - "github.com/bytebase/terraform-provider-bytebase/api" - "github.com/bytebase/terraform-provider-bytebase/provider/internal" -) - -var environmentTitleRegex = regexp.MustCompile(`^[a-zA-Z0-9]+$`) - -// TODO(ed): API changed. -func resourceEnvironment() *schema.Resource { - return &schema.Resource{ - Description: "The environment resource.", - CreateContext: resourceEnvironmentCreate, - ReadContext: resourceEnvironmentRead, - UpdateContext: resourceEnvironmentUpdate, - DeleteContext: resourceEnvironmentDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - Schema: map[string]*schema.Schema{ - "resource_id": { - Type: schema.TypeString, - Required: true, - ValidateFunc: internal.ResourceIDValidation, - Description: "The environment unique resource id.", - }, - "title": { - Type: schema.TypeString, - Required: true, - Description: "The environment title.", - ValidateFunc: validation.StringMatch(environmentTitleRegex, fmt.Sprintf("environment title must matches %v", environmentTitleRegex)), - }, - "name": { - Type: schema.TypeString, - Computed: true, - Description: "The environment full name in environments/{resource id} format.", - }, - "order": { - Type: schema.TypeInt, - Required: true, - Description: "The environment sorting order.", - ValidateFunc: validation.IntAtLeast(0), - }, - "environment_tier_policy": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - 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. Require ENTERPRISE subscription.", - }, - }, - } -} - -func resourceEnvironmentCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - c := m.(api.Client) - - environmentID := d.Get("resource_id").(string) - environmentName := fmt.Sprintf("%s%s", internal.EnvironmentNamePrefix, environmentID) - - existedEnv, err := c.GetEnvironment(ctx, environmentName) - if err != nil { - tflog.Debug(ctx, fmt.Sprintf("get environment %s failed with error: %v", environmentName, err)) - } - - title := d.Get("title").(string) - order := d.Get("order").(int) - tier := v1pb.EnvironmentTier(v1pb.EnvironmentTier_value[d.Get("environment_tier_policy").(string)]) - - var diags diag.Diagnostics - if existedEnv != nil && err == nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: "Environment already exists", - Detail: fmt.Sprintf("Environment %s already exists, try to exec the update operation", environmentID), - }) - - if existedEnv.State == v1pb.State_DELETED { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: "Environment is deleted", - Detail: fmt.Sprintf("Environment %s already deleted, try to undelete the environment", environmentID), - }) - if _, err := c.UndeleteEnvironment(ctx, environmentName); err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to undelete environment", - Detail: fmt.Sprintf("Undelete environment %s failed, error: %v", environmentName, err), - }) - return diags - } - } - - updateMasks := []string{} - if title != "" && title != existedEnv.Title { - updateMasks = append(updateMasks, "title") - } - if order != int(existedEnv.Order) { - updateMasks = append(updateMasks, "order") - } - if tier != existedEnv.Tier { - updateMasks = append(updateMasks, "tier") - } - - if len(updateMasks) > 0 { - if _, err := c.UpdateEnvironment(ctx, &v1pb.Environment{ - Name: environmentName, - Title: title, - Order: int32(order), - Tier: tier, - State: v1pb.State_ACTIVE, - }, updateMasks); err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to update environment", - Detail: fmt.Sprintf("Update environment %s failed, error: %v", environmentName, err), - }) - return diags - } - } - } else { - if _, err := c.CreateEnvironment(ctx, environmentID, &v1pb.Environment{ - Name: environmentName, - Title: title, - Order: int32(order), - Tier: tier, - State: v1pb.State_ACTIVE, - }); err != nil { - return diag.FromErr(err) - } - } - - d.SetId(environmentName) - - diag := resourceEnvironmentRead(ctx, d, m) - if diag != nil { - diags = append(diags, diag...) - } - - return diags -} - -func resourceEnvironmentRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - c := m.(api.Client) - environmentName := d.Id() - - env, err := c.GetEnvironment(ctx, environmentName) - if err != nil { - return diag.FromErr(err) - } - - return setEnvironment(d, env) -} - -func resourceEnvironmentUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - if d.HasChange("resource_id") { - return diag.Errorf("cannot change the resource id") - } - - c := m.(api.Client) - environmentName := d.Id() - - existedEnv, err := c.GetEnvironment(ctx, environmentName) - if err != nil { - return diag.FromErr(err) - } - - var diags diag.Diagnostics - if existedEnv.State == v1pb.State_DELETED { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: "Environment is deleted", - Detail: fmt.Sprintf("Environment %s already deleted, try to undelete the environment", environmentName), - }) - if _, err := c.UndeleteEnvironment(ctx, environmentName); err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to undelete environment", - Detail: fmt.Sprintf("Undelete environment %s failed, error: %v", environmentName, err), - }) - return diags - } - } - - paths := []string{} - if d.HasChange("title") { - paths = append(paths, "title") - } - - if d.HasChange("order") { - paths = append(paths, "order") - } - - if d.HasChange("environment_tier_policy") { - paths = append(paths, "tier") - } - - if len(paths) > 0 { - title := d.Get("title").(string) - order := d.Get("order").(int) - tier := v1pb.EnvironmentTier(v1pb.EnvironmentTier_value[d.Get("environment_tier_policy").(string)]) - - if _, err := c.UpdateEnvironment(ctx, &v1pb.Environment{ - Name: environmentName, - Title: title, - Order: int32(order), - Tier: tier, - State: v1pb.State_ACTIVE, - }, paths); err != nil { - return diag.FromErr(err) - } - } - - diag := resourceEnvironmentRead(ctx, d, m) - if diag != nil { - diags = append(diags, diag...) - } - - return diags -} - -func resourceEnvironmentDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - c := m.(api.Client) - - // Warning or errors can be collected in a slice type - var diags diag.Diagnostics - environmentName := d.Id() - - if err := c.DeleteEnvironment(ctx, environmentName); err != nil { - return diag.FromErr(err) - } - - d.SetId("") - - return diags -} - -func setEnvironment(d *schema.ResourceData, env *v1pb.Environment) diag.Diagnostics { - environmentID, err := internal.GetEnvironmentID(env.Name) - if err != nil { - return diag.FromErr(err) - } - - if err := d.Set("resource_id", environmentID); err != nil { - return diag.Errorf("cannot set resource_id for environment: %s", err.Error()) - } - if err := d.Set("title", env.Title); err != nil { - return diag.Errorf("cannot set title for environment: %s", err.Error()) - } - if err := d.Set("name", env.Name); err != nil { - return diag.Errorf("cannot set name for environment: %s", err.Error()) - } - if err := d.Set("order", env.Order); err != nil { - return diag.Errorf("cannot set order for environment: %s", err.Error()) - } - if err := d.Set("environment_tier_policy", env.Tier.String()); err != nil { - return diag.Errorf("cannot set environment_tier_policy for environment: %s", err.Error()) - } - - return nil -} diff --git a/provider/resource_environment_test.go b/provider/resource_environment_test.go deleted file mode 100644 index 267a9a3..0000000 --- a/provider/resource_environment_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package provider - -import ( - "context" - "fmt" - "regexp" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" - "github.com/pkg/errors" - - "github.com/bytebase/terraform-provider-bytebase/api" - "github.com/bytebase/terraform-provider-bytebase/provider/internal" -) - -func TestAccEnvironment(t *testing.T) { - identifier := "new-environment" - resourceName := fmt.Sprintf("bytebase_environment.%s", identifier) - - title := "test" - order := 1 - titleUpdated := fmt.Sprintf("%supdated", title) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - }, - Providers: testAccProviders, - CheckDestroy: testAccCheckEnvironmentDestroy, - Steps: []resource.TestStep{ - // resource create test - { - Config: testAccCheckEnvironmentResource(identifier, title, order), - Check: resource.ComposeTestCheckFunc( - internal.TestCheckResourceExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "title", title), - resource.TestCheckResourceAttr(resourceName, "order", fmt.Sprintf("%d", order)), - ), - }, - // resource update test - { - Config: testAccCheckEnvironmentResource(identifier, titleUpdated, order+1), - Check: resource.ComposeTestCheckFunc( - internal.TestCheckResourceExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "title", titleUpdated), - resource.TestCheckResourceAttr(resourceName, "order", fmt.Sprintf("%d", order+1)), - ), - }, - }, - }) -} - -func TestAccEnvironment_InvalidInput(t *testing.T) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - }, - Providers: testAccProviders, - CheckDestroy: testAccCheckEnvironmentDestroy, - Steps: []resource.TestStep{ - // Invalid environment order - { - Config: ` - resource "bytebase_environment" "new_env" { - resource_id = "test" - title = "Test" - environment_tier_policy = "PROTECTED" - } - `, - ExpectError: regexp.MustCompile("The argument \"order\" is required, but no definition was found"), - }, - // Invalid environment name - { - Config: ` - resource "bytebase_environment" "new_env" { - resource_id = "test" - title = "" - order = 1 - environment_tier_policy = "PROTECTED" - } - `, - ExpectError: regexp.MustCompile("environment title must matches"), - }, - // Invalid policy - { - Config: ` - resource "bytebase_environment" "new_env" { - resource_id = "test" - title = "Test" - order = 1 - environment_tier_policy = "UNKNOWN" - } - `, - ExpectError: regexp.MustCompile("expected environment_tier_policy to be one of"), - }, - }, - }) -} - -func testAccCheckEnvironmentDestroy(s *terraform.State) error { - c, ok := testAccProvider.Meta().(api.Client) - if !ok { - return errors.Errorf("cannot get the api client") - } - - for _, rs := range s.RootModule().Resources { - if rs.Type != "bytebase_environment" { - continue - } - - if err := c.DeleteEnvironment(context.Background(), rs.Primary.ID); err != nil { - return err - } - } - - return nil -} - -func testAccCheckEnvironmentResource(identifier, envName string, order int) string { - return fmt.Sprintf(` - resource "bytebase_environment" "%s" { - resource_id = "%s" - title = "%s" - order = %d - environment_tier_policy = "PROTECTED" - } - `, identifier, identifier, envName, order) -} diff --git a/provider/resource_policy.go b/provider/resource_policy.go index d9ad87c..70ada04 100644 --- a/provider/resource_policy.go +++ b/provider/resource_policy.go @@ -35,7 +35,7 @@ func resourcePolicy() *schema.Resource { Required: true, ValidateDiagFunc: internal.ResourceNameValidation( // workspace policy - regexp.MustCompile("^workspaces/-$"), + regexp.MustCompile(fmt.Sprintf("^%s$", internal.WorkspaceName)), // environment policy regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.EnvironmentNamePrefix, internal.ResourceIDPattern)), // instance policy @@ -82,8 +82,11 @@ func resourcePolicy() *schema.Resource { func resourcePolicyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(api.Client) - parent := fmt.Sprintf("%s/%s%s", d.Get("parent").(string), internal.PolicyNamePrefix, d.Get("type").(string)) - policy, err := c.GetPolicy(ctx, parent) + policyName := fmt.Sprintf("%s/%s%s", d.Get("parent").(string), internal.PolicyNamePrefix, d.Get("type").(string)) + if strings.HasPrefix(policyName, internal.WorkspaceName) { + policyName = strings.TrimPrefix(policyName, fmt.Sprintf("%s/", internal.WorkspaceName)) + } + policy, err := c.GetPolicy(ctx, policyName) if err != nil { return diag.FromErr(err) } @@ -109,6 +112,9 @@ func resourcePolicyCreate(ctx context.Context, d *schema.ResourceData, m interfa c := m.(api.Client) policyName := fmt.Sprintf("%s/%s%s", d.Get("parent").(string), internal.PolicyNamePrefix, d.Get("type").(string)) + if strings.HasPrefix(policyName, internal.WorkspaceName) { + policyName = strings.TrimPrefix(policyName, fmt.Sprintf("%s/", internal.WorkspaceName)) + } inheritFromParent := d.Get("inherit_from_parent").(bool) enforce := d.Get("enforce").(bool) diff --git a/provider/resource_setting.go b/provider/resource_setting.go index e5543a1..a6f71a3 100644 --- a/provider/resource_setting.go +++ b/provider/resource_setting.go @@ -36,12 +36,14 @@ func resourceSetting() *schema.Resource { string(api.SettingWorkspaceProfile), string(api.SettingDataClassification), string(api.SettingSemanticTypes), + string(api.SettingEnvironment), }, false), }, - "approval_flow": getWorkspaceApprovalSetting(false), - "workspace_profile": getWorkspaceProfileSetting(false), - "classification": getClassificationSetting(false), - "semantic_types": getSemanticTypesSetting(false), + "approval_flow": getWorkspaceApprovalSetting(false), + "workspace_profile": getWorkspaceProfileSetting(false), + "classification": getClassificationSetting(false), + "semantic_types": getSemanticTypesSetting(false), + "environment_setting": getEnvironmentSetting(false), }, } } @@ -100,6 +102,16 @@ func resourceSettingUpsert(ctx context.Context, d *schema.ResourceData, m interf SemanticTypeSettingValue: classificationSetting, }, } + case api.SettingEnvironment: + environmentSetting, err := convertToV1EnvironmentSetting(d) + if err != nil { + return diag.FromErr(err) + } + setting.Value = &v1pb.Value{ + Value: &v1pb.Value_EnvironmentSetting{ + EnvironmentSetting: environmentSetting, + }, + } default: return diag.FromErr(errors.Errorf("Unsupport setting: %v", name)) } @@ -280,42 +292,19 @@ func convertToV1ApprovalSetting(d *schema.ResourceData) (*v1pb.WorkspaceApproval } for _, step := range stepList { - rawStep := step.(map[string]interface{}) - stepType := api.ApprovalNodeType(rawStep["type"].(string)) - node := rawStep["node"].(string) - - approvalNode := &v1pb.ApprovalNode{ - Type: v1pb.ApprovalNode_ANY_IN_GROUP, - } - switch stepType { - case api.ApprovalNodeTypeRole: - if !strings.HasPrefix(node, "roles/") { - return nil, errors.Errorf("invalid role name: %v, role name should in roles/{role} format", node) - } - approvalNode.Payload = &v1pb.ApprovalNode_Role{ - Role: node, - } - case api.ApprovalNodeTypeGroup: - group, ok := v1pb.ApprovalNode_GroupValue_value[node] - if !ok { - return nil, errors.Errorf( - "invalid group: %v, group should be one of: %s, %s, %s, %s", - node, - v1pb.ApprovalNode_WORKSPACE_OWNER.String(), - v1pb.ApprovalNode_WORKSPACE_DBA.String(), - v1pb.ApprovalNode_PROJECT_OWNER.String(), - v1pb.ApprovalNode_PROJECT_MEMBER.String(), - ) - } - approvalNode.Payload = &v1pb.ApprovalNode_GroupValue_{ - GroupValue: v1pb.ApprovalNode_GroupValue(group), - } + approvalStep := &v1pb.ApprovalStep{ + Type: v1pb.ApprovalStep_ANY, } - approvalStep := &v1pb.ApprovalStep{ - Type: v1pb.ApprovalStep_ANY, - Nodes: []*v1pb.ApprovalNode{approvalNode}, + rawStep := step.(map[string]interface{}) + role := rawStep["role"].(string) + if !strings.HasPrefix(role, "roles/") { + return nil, errors.Errorf("invalid role name: %v, role name should in roles/{role} format", role) } + approvalStep.Nodes = append(approvalStep.Nodes, &v1pb.ApprovalNode{ + Type: v1pb.ApprovalNode_ANY_IN_GROUP, + Role: role, + }) approvalRule.Template.Flow.Steps = append(approvalRule.Template.Flow.Steps, approvalStep) } @@ -326,6 +315,39 @@ func convertToV1ApprovalSetting(d *schema.ResourceData) (*v1pb.WorkspaceApproval return workspaceApprovalSetting, nil } +func convertToV1EnvironmentSetting(d *schema.ResourceData) (*v1pb.EnvironmentSetting, error) { + rawList, ok := d.Get("environment_setting").([]interface{}) + if !ok || len(rawList) != 1 { + return nil, errors.Errorf("invalid environment_setting") + } + + raw := rawList[0].(map[string]interface{}) + environments := raw["environment"].([]interface{}) + + environmentSetting := &v1pb.EnvironmentSetting{} + for _, environment := range environments { + rawEnv := environment.(map[string]interface{}) + id := rawEnv["id"].(string) + protected := rawEnv["protected"].(bool) + if !internal.ResourceIDRegex.MatchString(id) { + return nil, errors.Errorf("invalid environment id") + } + v1Env := &v1pb.EnvironmentSetting_Environment{ + Id: id, + Title: rawEnv["title"].(string), + Color: rawEnv["color"].(string), + Tags: map[string]string{ + "protected": "protected", + }, + } + if !protected { + v1Env.Tags["protected"] = "unprotected" + } + environmentSetting.Environments = append(environmentSetting.Environments, v1Env) + } + return environmentSetting, nil +} + func convertToV1SemanticTypeSetting(d *schema.ResourceData) (*v1pb.SemanticTypeSetting, error) { set, ok := d.Get("semantic_types").(*schema.Set) if !ok {