diff --git a/VERSION b/VERSION
index 0be1fc7..aaaff91 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.8.0
\ No newline at end of file
+3.8.1
\ No newline at end of file
diff --git a/docs/data-sources/policy.md b/docs/data-sources/policy.md
index 0ca5b20..53f79e5 100644
--- a/docs/data-sources/policy.md
+++ b/docs/data-sources/policy.md
@@ -17,13 +17,16 @@ The policy data source.
### Required
+- `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}
- `type` (String) The policy type.
### Optional
+- `data_source_query_policy` (Block List, Max: 1) Restrict querying admin data sources (see [below for nested schema](#nestedblock--data_source_query_policy))
+- `disable_copy_data_policy` (Block List, Max: 1) Restrict data copying in SQL Editor (Admins/DBAs allowed) (see [below for nested schema](#nestedblock--disable_copy_data_policy))
- `global_masking_policy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--global_masking_policy))
- `masking_exception_policy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--masking_exception_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}
+- `rollout_policy` (Block List, Max: 1) Control issue rollout. Learn more: https://docs.bytebase.com/administration/environment-policy/rollout-policy (see [below for nested schema](#nestedblock--rollout_policy))
### Read-Only
@@ -32,6 +35,24 @@ The policy data source.
- `inherit_from_parent` (Boolean) Decide if the policy should inherit from the parent.
- `name` (String) The policy full name
+
+### Nested Schema for `data_source_query_policy`
+
+Optional:
+
+- `disallow_ddl` (Boolean) Disallow running DDL statements in the SQL editor.
+- `disallow_dml` (Boolean) Disallow running DML statements in the SQL editor.
+- `restriction` (String) RESTRICTION_UNSPECIFIED means no restriction; FALLBACK will allows to query admin data sources when there is no read-only data source; DISALLOW will always disallow to query admin data sources.
+
+
+
+### Nested Schema for `disable_copy_data_policy`
+
+Required:
+
+- `enable` (Boolean) Restrict data copying
+
+
### Nested Schema for `global_masking_policy`
@@ -74,3 +95,13 @@ Optional:
- `table` (String)
+
+
+### Nested Schema for `rollout_policy`
+
+Optional:
+
+- `automatic` (Boolean) If all check pass, the change will be rolled out and executed automatically.
+- `roles` (Set of String) If any roles are specified, Bytebase requires users with those roles to manually roll out the change.
+
+
diff --git a/docs/data-sources/setting.md b/docs/data-sources/setting.md
index 3bc8fdb..fdf13ba 100644
--- a/docs/data-sources/setting.md
+++ b/docs/data-sources/setting.md
@@ -22,7 +22,7 @@ The setting data source.
### Optional
- `classification` (Block List, Max: 1) Classification for data masking. Require ENTERPRISE subscription. (see [below for nested schema](#nestedblock--classification))
-- `semantic_types` (Block Set) Semantic types for data masking. Require ENTERPRISE subscription. (see [below for nested schema](#nestedblock--semantic_types))
+- `semantic_types` (Block List) 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))
### Read-Only
@@ -38,7 +38,7 @@ Required:
- `classifications` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--classification--classifications))
- `id` (String) The classification unique uuid.
-- `levels` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--classification--levels))
+- `levels` (Block List, Min: 1) (see [below for nested schema](#nestedblock--classification--levels))
- `title` (String) The classification title. Optional.
Optional:
diff --git a/docs/resources/environment.md b/docs/resources/environment.md
index 1350776..5e76198 100644
--- a/docs/resources/environment.md
+++ b/docs/resources/environment.md
@@ -17,13 +17,13 @@ The environment resource.
### Required
-- `order` (Number) The environment sorting order.
- `resource_id` (String) The environment unique id.
- `title` (String) The environment display name.
### Optional
- `color` (String) The environment color.
+- `order` (Number) The environment sorting order.
- `protected` (Boolean) The environment is protected or not.
### Read-Only
diff --git a/docs/resources/policy.md b/docs/resources/policy.md
index 7e57836..d57c986 100644
--- a/docs/resources/policy.md
+++ b/docs/resources/policy.md
@@ -22,16 +22,37 @@ The policy resource.
### Optional
+- `data_source_query_policy` (Block List, Max: 1) Restrict querying admin data sources (see [below for nested schema](#nestedblock--data_source_query_policy))
+- `disable_copy_data_policy` (Block List, Max: 1) Restrict data copying in SQL Editor (Admins/DBAs allowed) (see [below for nested schema](#nestedblock--disable_copy_data_policy))
- `enforce` (Boolean) Decide if the policy is enforced.
- `global_masking_policy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--global_masking_policy))
- `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))
+- `rollout_policy` (Block List, Max: 1) Control issue rollout. Learn more: https://docs.bytebase.com/administration/environment-policy/rollout-policy (see [below for nested schema](#nestedblock--rollout_policy))
### Read-Only
- `id` (String) The ID of this resource.
- `name` (String) The policy full name
+
+### Nested Schema for `data_source_query_policy`
+
+Optional:
+
+- `disallow_ddl` (Boolean) Disallow running DDL statements in the SQL editor.
+- `disallow_dml` (Boolean) Disallow running DML statements in the SQL editor.
+- `restriction` (String) RESTRICTION_UNSPECIFIED means no restriction; FALLBACK will allows to query admin data sources when there is no read-only data source; DISALLOW will always disallow to query admin data sources.
+
+
+
+### Nested Schema for `disable_copy_data_policy`
+
+Required:
+
+- `enable` (Boolean) Restrict data copying
+
+
### Nested Schema for `global_masking_policy`
@@ -74,3 +95,13 @@ Optional:
- `table` (String)
+
+
+### Nested Schema for `rollout_policy`
+
+Optional:
+
+- `automatic` (Boolean) If all check pass, the change will be rolled out and executed automatically.
+- `roles` (Set of String) If any roles are specified, Bytebase requires users with those roles to manually roll out the change.
+
+
diff --git a/docs/resources/setting.md b/docs/resources/setting.md
index 4c9acc4..355791b 100644
--- a/docs/resources/setting.md
+++ b/docs/resources/setting.md
@@ -24,7 +24,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))
+- `semantic_types` (Block List) 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))
### Read-Only
@@ -88,7 +88,7 @@ Required:
- `classifications` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--classification--classifications))
- `id` (String) The classification unique uuid.
-- `levels` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--classification--levels))
+- `levels` (Block List, Min: 1) (see [below for nested schema](#nestedblock--classification--levels))
- `title` (String) The classification title. Optional.
Optional:
diff --git a/examples/database/main.tf b/examples/database/main.tf
index e7ab2c4..2b0e12f 100644
--- a/examples/database/main.tf
+++ b/examples/database/main.tf
@@ -2,7 +2,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.7.2"
+ version = "3.8.1"
# For local development, please use "terraform.local/bytebase/bytebase" instead
source = "registry.terraform.io/bytebase/bytebase"
}
diff --git a/examples/database_group/main.tf b/examples/database_group/main.tf
index c1719e5..1c0a35f 100644
--- a/examples/database_group/main.tf
+++ b/examples/database_group/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.7.2"
+ version = "3.8.1"
# For local development, please use "terraform.local/bytebase/bytebase" instead
source = "registry.terraform.io/bytebase/bytebase"
}
diff --git a/examples/environments/main.tf b/examples/environments/main.tf
index dea81bb..1d9dfb7 100644
--- a/examples/environments/main.tf
+++ b/examples/environments/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.7.2"
+ version = "3.8.1"
# 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 741d0b2..e65cbe9 100644
--- a/examples/groups/main.tf
+++ b/examples/groups/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.7.2"
+ version = "3.8.1"
# 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 d4ec9e2..5b6dac1 100644
--- a/examples/iamPolicy/main.tf
+++ b/examples/iamPolicy/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.7.2"
+ version = "3.8.1"
# 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 18417f7..735e5e5 100644
--- a/examples/instances/main.tf
+++ b/examples/instances/main.tf
@@ -2,7 +2,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.7.2"
+ version = "3.8.1"
# 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 e9adc04..1ae87d0 100644
--- a/examples/policies/main.tf
+++ b/examples/policies/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.7.2"
+ version = "3.8.1"
# 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 24b539e..681c99a 100644
--- a/examples/projects/main.tf
+++ b/examples/projects/main.tf
@@ -2,7 +2,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.7.2"
+ version = "3.8.1"
# For local development, please use "terraform.local/bytebase/bytebase" instead
source = "registry.terraform.io/bytebase/bytebase"
}
diff --git a/examples/risk/main.tf b/examples/risk/main.tf
index 645207d..b892277 100644
--- a/examples/risk/main.tf
+++ b/examples/risk/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.7.2"
+ version = "3.8.1"
# 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 34fd4f7..06c42ba 100644
--- a/examples/roles/main.tf
+++ b/examples/roles/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.7.2"
+ version = "3.8.1"
# 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 20f9f80..a089537 100644
--- a/examples/settings/main.tf
+++ b/examples/settings/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.7.2"
+ version = "3.8.1"
# For local development, please use "terraform.local/bytebase/bytebase" instead
source = "registry.terraform.io/bytebase/bytebase"
}
diff --git a/examples/setup/environment.tf b/examples/setup/environment.tf
index 4242f12..28c141f 100644
--- a/examples/setup/environment.tf
+++ b/examples/setup/environment.tf
@@ -37,3 +37,41 @@ resource "bytebase_environment" "prod" {
order = 1 // change order to 1
protected = true
}
+
+resource "bytebase_policy" "rollout_policy" {
+ depends_on = [bytebase_environment.test]
+ parent = bytebase_environment.test.name
+ type = "ROLLOUT_POLICY"
+
+ rollout_policy {
+ automatic = true
+ roles = [
+ "roles/workspaceAdmin",
+ "roles/projectOwner",
+ "roles/LAST_APPROVER",
+ "roles/CREATOR"
+ ]
+ }
+}
+
+resource "bytebase_policy" "disable_copy_data_policy" {
+ depends_on = [bytebase_environment.test]
+ parent = bytebase_environment.test.name
+ type = "DISABLE_COPY_DATA"
+
+ disable_copy_data_policy {
+ enable = true
+ }
+}
+
+resource "bytebase_policy" "data_source_query_policy" {
+ depends_on = [bytebase_environment.test]
+ parent = bytebase_environment.test.name
+ type = "DATA_SOURCE_QUERY"
+
+ data_source_query_policy {
+ restriction = "FALLBACK"
+ disallow_ddl = false
+ disallow_dml = false
+ }
+}
diff --git a/examples/setup/main.tf b/examples/setup/main.tf
index 88c7059..5f4cfba 100644
--- a/examples/setup/main.tf
+++ b/examples/setup/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.7.2"
+ version = "3.8.1"
# For local development, please use "terraform.local/bytebase/bytebase" instead
source = "registry.terraform.io/bytebase/bytebase"
}
diff --git a/examples/setup/sql_review.tf b/examples/setup/sql_review.tf
index da1cecb..587f558 100644
--- a/examples/setup/sql_review.tf
+++ b/examples/setup/sql_review.tf
@@ -1,6 +1,7 @@
resource "bytebase_review_config" "sample" {
depends_on = [
- bytebase_setting.environments
+ bytebase_setting.environments,
+ bytebase_project.sample_project
]
resource_id = "review-config-sample"
@@ -8,7 +9,8 @@ resource "bytebase_review_config" "sample" {
enabled = true
resources = toset([
bytebase_setting.environments.environment_setting[0].environment[0].name,
- bytebase_setting.environments.environment_setting[0].environment[1].name
+ bytebase_setting.environments.environment_setting[0].environment[1].name,
+ bytebase_project.sample_project.name
])
rules {
type = "column.no-null"
diff --git a/examples/sql_review/main.tf b/examples/sql_review/main.tf
index 05ad7c8..4b38687 100644
--- a/examples/sql_review/main.tf
+++ b/examples/sql_review/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.7.2"
+ version = "3.8.1"
# For local development, please use "terraform.local/bytebase/bytebase" instead
source = "registry.terraform.io/bytebase/bytebase"
}
diff --git a/provider/data_source_iam_policy.go b/provider/data_source_iam_policy.go
index 6c04105..f29e56d 100644
--- a/provider/data_source_iam_policy.go
+++ b/provider/data_source_iam_policy.go
@@ -67,6 +67,9 @@ func getIAMBindingSchema(computed bool) *schema.Schema {
Computed: computed,
Optional: !computed,
Description: "The role full name in roles/{id} format.",
+ ValidateDiagFunc: internal.ResourceNameValidation(
+ fmt.Sprintf("^%s", internal.RoleNamePrefix),
+ ),
},
"members": {
Type: schema.TypeSet,
@@ -75,6 +78,11 @@ func getIAMBindingSchema(computed bool) *schema.Schema {
Description: `A set of memebers. The value can be "allUsers", "user:{email}" or "group:{email}".`,
Elem: &schema.Schema{
Type: schema.TypeString,
+ ValidateDiagFunc: internal.ResourceNameValidation(
+ "allUsers",
+ "^user:",
+ "^group:",
+ ),
},
},
"condition": {
@@ -242,6 +250,12 @@ func bindingHash(rawBinding interface{}) int {
_, _ = buf.WriteString(conditionHash(rawCondition))
}
+ if members, ok := binding["members"].(*schema.Set); ok && members.Len() > 0 {
+ for _, member := range members.List() {
+ _, _ = buf.WriteString(fmt.Sprintf("[member] %s", member))
+ }
+ }
+
return internal.ToHashcodeInt(buf.String())
}
diff --git a/provider/data_source_policy.go b/provider/data_source_policy.go
index 80786d3..254635c 100644
--- a/provider/data_source_policy.go
+++ b/provider/data_source_policy.go
@@ -24,8 +24,7 @@ func dataSourcePolicy() *schema.Resource {
Schema: map[string]*schema.Schema{
"parent": {
Type: schema.TypeString,
- Optional: true,
- Default: "",
+ Required: true,
ValidateDiagFunc: internal.ResourceNameValidation(
// workspace policy
fmt.Sprintf("^%s$", internal.WorkspaceName),
@@ -46,6 +45,9 @@ func dataSourcePolicy() *schema.Resource {
ValidateFunc: validation.StringInSlice([]string{
v1pb.PolicyType_MASKING_EXCEPTION.String(),
v1pb.PolicyType_MASKING_RULE.String(),
+ v1pb.PolicyType_DISABLE_COPY_DATA.String(),
+ v1pb.PolicyType_DATA_SOURCE_QUERY.String(),
+ v1pb.PolicyType_ROLLOUT_POLICY.String(),
}, false),
Description: "The policy type.",
},
@@ -66,6 +68,9 @@ func dataSourcePolicy() *schema.Resource {
},
"masking_exception_policy": getMaskingExceptionPolicySchema(true),
"global_masking_policy": getGlobalMaskingPolicySchema(true),
+ "disable_copy_data_policy": getDisableCopyDataPolicySchema(true),
+ "data_source_query_policy": getDataSourceQueryPolicySchema(true),
+ "rollout_policy": getRolloutPolicySchema(true),
},
}
}
@@ -184,6 +189,104 @@ func getGlobalMaskingPolicySchema(computed bool) *schema.Schema {
}
}
+func getDisableCopyDataPolicySchema(computed bool) *schema.Schema {
+ return &schema.Schema{
+ Computed: computed,
+ Optional: true,
+ Default: nil,
+ Type: schema.TypeList,
+ MinItems: 0,
+ MaxItems: 1,
+ Description: "Restrict data copying in SQL Editor (Admins/DBAs allowed)",
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "enable": {
+ Type: schema.TypeBool,
+ Required: true,
+ Description: "Restrict data copying",
+ },
+ },
+ },
+ }
+}
+
+func getDataSourceQueryPolicySchema(computed bool) *schema.Schema {
+ return &schema.Schema{
+ Computed: computed,
+ Optional: true,
+ Default: nil,
+ Type: schema.TypeList,
+ MinItems: 0,
+ MaxItems: 1,
+ Description: "Restrict querying admin data sources",
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "restriction": {
+ Type: schema.TypeString,
+ Optional: true,
+ ValidateFunc: validation.StringInSlice([]string{
+ v1pb.DataSourceQueryPolicy_FALLBACK.String(),
+ v1pb.DataSourceQueryPolicy_DISALLOW.String(),
+ v1pb.DataSourceQueryPolicy_RESTRICTION_UNSPECIFIED.String(),
+ }, false),
+ Description: "RESTRICTION_UNSPECIFIED means no restriction; FALLBACK will allows to query admin data sources when there is no read-only data source; DISALLOW will always disallow to query admin data sources.",
+ },
+ "disallow_ddl": {
+ Type: schema.TypeBool,
+ Optional: true,
+ Description: "Disallow running DDL statements in the SQL editor.",
+ },
+ "disallow_dml": {
+ Type: schema.TypeBool,
+ Optional: true,
+ Description: "Disallow running DML statements in the SQL editor.",
+ },
+ },
+ },
+ }
+}
+
+const (
+ issueLastApproverRole = "roles/LAST_APPROVER"
+ issueCreatorRole = "roles/CREATOR"
+)
+
+func getRolloutPolicySchema(computed bool) *schema.Schema {
+ return &schema.Schema{
+ Computed: computed,
+ Optional: true,
+ Default: nil,
+ Type: schema.TypeList,
+ MinItems: 0,
+ MaxItems: 1,
+ Description: "Control issue rollout. Learn more: https://docs.bytebase.com/administration/environment-policy/rollout-policy",
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "automatic": {
+ Type: schema.TypeBool,
+ Optional: true,
+ Description: "If all check pass, the change will be rolled out and executed automatically.",
+ },
+ "roles": {
+ Optional: true,
+ Type: schema.TypeSet,
+ MinItems: 0,
+ Description: "If any roles are specified, Bytebase requires users with those roles to manually roll out the change.",
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ Description: fmt.Sprintf(`Role full name in roles/{id} format. You can also use the "%s" for the last approver of the issue, or "%s" for the creator of the issue.`, issueLastApproverRole, issueCreatorRole),
+ ValidateDiagFunc: internal.ResourceNameValidation(
+ fmt.Sprintf("^%s$", issueLastApproverRole),
+ fmt.Sprintf("^%s$", issueCreatorRole),
+ fmt.Sprintf("^%s", internal.RoleNamePrefix),
+ ),
+ },
+ },
+ },
+ },
+ }
+}
+
func dataSourcePolicyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
c := m.(api.Client)
@@ -202,16 +305,13 @@ func dataSourcePolicyRead(ctx context.Context, d *schema.ResourceData, m interfa
}
func setPolicyMessage(d *schema.ResourceData, policy *v1pb.Policy) diag.Diagnostics {
- parent, _, err := internal.GetPolicyParentAndType(policy.Name)
+ _, policyType, err := internal.GetPolicyParentAndType(policy.Name)
if err != nil {
return diag.Errorf("cannot parse name for policy: %s", err.Error())
}
if err := d.Set("name", policy.Name); err != nil {
return diag.Errorf("cannot set name for policy: %s", err.Error())
}
- if err := d.Set("parent", parent); err != nil {
- return diag.Errorf("cannot set parent for policy: %s", err.Error())
- }
if err := d.Set("inherit_from_parent", policy.InheritFromParent); err != nil {
return diag.Errorf("cannot set inherit_from_parent for policy: %s", err.Error())
}
@@ -219,29 +319,80 @@ func setPolicyMessage(d *schema.ResourceData, policy *v1pb.Policy) diag.Diagnost
return diag.Errorf("cannot set enforce for policy: %s", err.Error())
}
- if p := policy.GetMaskingExceptionPolicy(); p != nil {
- exceptionPolicy, err := flattenMaskingExceptionPolicy(p)
- if err != nil {
- return diag.FromErr(err)
+ switch policyType {
+ case v1pb.PolicyType_MASKING_EXCEPTION:
+ 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_exception_policy: %s", err.Error())
+ }
}
- if err := d.Set("masking_exception_policy", exceptionPolicy); err != nil {
- return diag.Errorf("cannot set masking_exception_policy: %s", err.Error())
+ case v1pb.PolicyType_MASKING_RULE:
+ if p := policy.GetMaskingRulePolicy(); p != nil {
+ maskingPolicy, err := flattenGlobalMaskingPolicy(p)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ if err := d.Set("global_masking_policy", maskingPolicy); err != nil {
+ return diag.Errorf("cannot set global_masking_policy: %s", err.Error())
+ }
}
- }
-
- if p := policy.GetMaskingRulePolicy(); p != nil {
- maskingPolicy, err := flattenGlobalMaskingPolicy(p)
- if err != nil {
- return diag.FromErr(err)
+ case v1pb.PolicyType_DISABLE_COPY_DATA:
+ if p := policy.GetDisableCopyDataPolicy(); p != nil {
+ disableCopyDataPolicy := flattenDisableCopyDataPolicy(p)
+ if err := d.Set("disable_copy_data_policy", disableCopyDataPolicy); err != nil {
+ return diag.Errorf("cannot set disable_copy_data_policy: %s", err.Error())
+ }
}
- if err := d.Set("global_masking_policy", maskingPolicy); err != nil {
- return diag.Errorf("cannot set global_masking_policy: %s", err.Error())
+ case v1pb.PolicyType_DATA_SOURCE_QUERY:
+ if p := policy.GetDataSourceQueryPolicy(); p != nil {
+ dataSourceQueryPolicy := flattenDataSourceQueryPolicy(p)
+ if err := d.Set("data_source_query_policy", dataSourceQueryPolicy); err != nil {
+ return diag.Errorf("cannot set data_source_query_policy: %s", err.Error())
+ }
+ }
+ case v1pb.PolicyType_ROLLOUT_POLICY:
+ if p := policy.GetRolloutPolicy(); p != nil {
+ rolloutPolicy := flattenRolloutPolicy(p)
+ if err := d.Set("rollout_policy", rolloutPolicy); err != nil {
+ return diag.Errorf("cannot set rollout_policy: %s", err.Error())
+ }
}
}
return nil
}
+func flattenRolloutPolicy(p *v1pb.RolloutPolicy) []interface{} {
+ roles := []string{}
+ roles = append(roles, p.Roles...)
+ roles = append(roles, p.IssueRoles...)
+ policy := map[string]interface{}{
+ "automatic": p.Automatic,
+ "roles": roles,
+ }
+ return []interface{}{policy}
+}
+
+func flattenDataSourceQueryPolicy(p *v1pb.DataSourceQueryPolicy) []interface{} {
+ policy := map[string]interface{}{
+ "restriction": p.AdminDataSourceRestriction.String(),
+ "disallow_ddl": p.DisallowDdl,
+ "disallow_dml": p.DisallowDml,
+ }
+ return []interface{}{policy}
+}
+
+func flattenDisableCopyDataPolicy(p *v1pb.DisableCopyDataPolicy) []interface{} {
+ policy := map[string]interface{}{
+ "enable": p.Active,
+ }
+ return []interface{}{policy}
+}
+
func flattenGlobalMaskingPolicy(p *v1pb.MaskingRulePolicy) ([]interface{}, error) {
ruleList := []interface{}{}
diff --git a/provider/data_source_setting.go b/provider/data_source_setting.go
index e20a0b3..3433026 100644
--- a/provider/data_source_setting.go
+++ b/provider/data_source_setting.go
@@ -47,7 +47,7 @@ func getSemanticTypesSetting(computed bool) *schema.Schema {
Computed: computed,
Optional: true,
Default: nil,
- Type: schema.TypeSet,
+ Type: schema.TypeList,
Description: "Semantic types for data masking. Require ENTERPRISE subscription.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
@@ -184,7 +184,6 @@ func getSemanticTypesSetting(computed bool) *schema.Schema {
},
},
},
- Set: itemIDHash,
}
}
@@ -217,7 +216,7 @@ func getClassificationSetting(computed bool) *schema.Schema {
},
"levels": {
Required: true,
- Type: schema.TypeSet,
+ Type: schema.TypeList,
MinItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
@@ -239,7 +238,6 @@ func getClassificationSetting(computed bool) *schema.Schema {
},
},
},
- Set: itemIDHash,
},
"classifications": {
Required: true,
@@ -271,7 +269,6 @@ func getClassificationSetting(computed bool) *schema.Schema {
},
},
},
- Set: itemIDHash,
},
},
},
@@ -507,7 +504,7 @@ func setSettingMessage(ctx context.Context, d *schema.ResourceData, client api.C
}
if value := setting.GetValue().GetSemanticTypeSettingValue(); value != nil {
settingVal := flattenSemanticTypesSetting(value)
- if err := d.Set("semantic_types", schema.NewSet(itemIDHash, settingVal)); err != nil {
+ if err := d.Set("semantic_types", settingVal); err != nil {
return diag.Errorf("cannot set semantic_types: %s", err.Error())
}
}
@@ -678,7 +675,7 @@ func flattenClassificationSetting(setting *v1pb.DataClassificationSetting) []int
rawLevel["description"] = level.Description
rawLevels = append(rawLevels, rawLevel)
}
- raw["levels"] = schema.NewSet(itemIDHash, rawLevels)
+ raw["levels"] = rawLevels
rawClassifications := []interface{}{}
for _, classification := range config.GetClassification() {
@@ -689,7 +686,7 @@ func flattenClassificationSetting(setting *v1pb.DataClassificationSetting) []int
rawClassification["level"] = classification.LevelId
rawClassifications = append(rawClassifications, rawClassification)
}
- raw["classifications"] = schema.NewSet(itemIDHash, rawClassifications)
+ raw["classifications"] = rawClassifications
}
return []interface{}{raw}
@@ -769,8 +766,3 @@ func flattenSemanticTypesSetting(setting *v1pb.SemanticTypeSetting) []interface{
return raw
}
-
-func itemIDHash(rawItem interface{}) int {
- item := rawItem.(map[string]interface{})
- return internal.ToHashcodeInt(item["id"].(string))
-}
diff --git a/provider/resource_policy.go b/provider/resource_policy.go
index 21ed0a5..559cd1f 100644
--- a/provider/resource_policy.go
+++ b/provider/resource_policy.go
@@ -52,6 +52,9 @@ func resourcePolicy() *schema.Resource {
ValidateFunc: validation.StringInSlice([]string{
v1pb.PolicyType_MASKING_EXCEPTION.String(),
v1pb.PolicyType_MASKING_RULE.String(),
+ v1pb.PolicyType_DISABLE_COPY_DATA.String(),
+ v1pb.PolicyType_DATA_SOURCE_QUERY.String(),
+ v1pb.PolicyType_ROLLOUT_POLICY.String(),
}, false),
Description: "The policy type.",
},
@@ -74,23 +77,22 @@ func resourcePolicy() *schema.Resource {
},
"masking_exception_policy": getMaskingExceptionPolicySchema(false),
"global_masking_policy": getGlobalMaskingPolicySchema(false),
+ "disable_copy_data_policy": getDisableCopyDataPolicySchema(false),
+ "data_source_query_policy": getDataSourceQueryPolicySchema(false),
+ "rollout_policy": getRolloutPolicySchema(false),
},
}
}
func resourcePolicyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
c := m.(api.Client)
+ policyName := d.Id()
- 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)
}
- d.SetId(policy.Name)
return setPolicyMessage(d, policy)
}
@@ -147,6 +149,39 @@ func resourcePolicyCreate(ctx context.Context, d *schema.ResourceData, m interfa
patch.Policy = &v1pb.Policy_MaskingRulePolicy{
MaskingRulePolicy: maskingRulePolicy,
}
+ case v1pb.PolicyType_DISABLE_COPY_DATA:
+ if !strings.HasPrefix(policyName, internal.EnvironmentNamePrefix) && !strings.HasPrefix(policyName, internal.ProjectNamePrefix) {
+ return diag.Errorf("policy %v only support environment or project resource", policyName)
+ }
+ disableCopyDataPolicy, err := convertToDisableCopyDataPolicy(d)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ patch.Policy = &v1pb.Policy_DisableCopyDataPolicy{
+ DisableCopyDataPolicy: disableCopyDataPolicy,
+ }
+ case v1pb.PolicyType_DATA_SOURCE_QUERY:
+ if !strings.HasPrefix(policyName, internal.EnvironmentNamePrefix) && !strings.HasPrefix(policyName, internal.ProjectNamePrefix) {
+ return diag.Errorf("policy %v only support environment or project resource", policyName)
+ }
+ dataSourceQueryPolicy, err := convertToDataSourceQueryPolicy(d)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ patch.Policy = &v1pb.Policy_DataSourceQueryPolicy{
+ DataSourceQueryPolicy: dataSourceQueryPolicy,
+ }
+ case v1pb.PolicyType_ROLLOUT_POLICY:
+ if !strings.HasPrefix(policyName, internal.EnvironmentNamePrefix) {
+ return diag.Errorf("policy %v only support environment resource", policyName)
+ }
+ rolloutPolicy, err := convertToRolloutPolicy(d)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ patch.Policy = &v1pb.Policy_RolloutPolicy{
+ RolloutPolicy: rolloutPolicy,
+ }
default:
return diag.Errorf("unsupport policy type: %v", policyName)
}
@@ -175,11 +210,16 @@ func resourcePolicyCreate(ctx context.Context, d *schema.ResourceData, m interfa
func resourcePolicyUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
c := m.(api.Client)
policyName := d.Id()
+
_, policyType, err := internal.GetPolicyParentAndType(policyName)
if err != nil {
return diag.FromErr(err)
}
+ if d.HasChange("type") || d.HasChange("parent") {
+ return diag.Errorf("cannot change policy type or parent")
+ }
+
patch := &v1pb.Policy{
Name: policyName,
InheritFromParent: d.Get("inherit_from_parent").(bool),
@@ -215,6 +255,26 @@ func resourcePolicyUpdate(ctx context.Context, d *schema.ResourceData, m interfa
MaskingRulePolicy: maskingRulePolicy,
}
}
+ if d.HasChange("disable_copy_data_policy") {
+ updateMasks = append(updateMasks, "payload")
+ disableCopyDataPolicy, err := convertToDisableCopyDataPolicy(d)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ patch.Policy = &v1pb.Policy_DisableCopyDataPolicy{
+ DisableCopyDataPolicy: disableCopyDataPolicy,
+ }
+ }
+ if d.HasChange("data_source_query_policy") {
+ updateMasks = append(updateMasks, "payload")
+ dataSourceQueryPolicy, err := convertToDataSourceQueryPolicy(d)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ patch.Policy = &v1pb.Policy_DataSourceQueryPolicy{
+ DataSourceQueryPolicy: dataSourceQueryPolicy,
+ }
+ }
var diags diag.Diagnostics
if len(updateMasks) > 0 {
@@ -326,3 +386,59 @@ func convertToMaskingExceptionPolicy(d *schema.ResourceData) (*v1pb.MaskingExcep
}
return policy, nil
}
+
+func convertToRolloutPolicy(d *schema.ResourceData) (*v1pb.RolloutPolicy, error) {
+ rawList, ok := d.Get("rollout_policy").([]interface{})
+ if !ok || len(rawList) != 1 {
+ return nil, errors.Errorf("invalid rollout_policy")
+ }
+
+ raw := rawList[0].(map[string]interface{})
+ policy := &v1pb.RolloutPolicy{
+ Automatic: raw["automatic"].(bool),
+ }
+
+ roles, ok := raw["roles"].(*schema.Set)
+ if !ok {
+ return policy, nil
+ }
+
+ for _, rawRole := range roles.List() {
+ role := rawRole.(string)
+ if role == issueLastApproverRole || role == issueCreatorRole {
+ policy.IssueRoles = append(policy.IssueRoles, role)
+ } else {
+ policy.Roles = append(policy.Roles, role)
+ }
+ }
+
+ return policy, nil
+}
+
+func convertToDisableCopyDataPolicy(d *schema.ResourceData) (*v1pb.DisableCopyDataPolicy, error) {
+ rawList, ok := d.Get("disable_copy_data_policy").([]interface{})
+ if !ok || len(rawList) != 1 {
+ return nil, errors.Errorf("invalid disable_copy_data_policy")
+ }
+
+ raw := rawList[0].(map[string]interface{})
+ return &v1pb.DisableCopyDataPolicy{
+ Active: raw["enable"].(bool),
+ }, nil
+}
+
+func convertToDataSourceQueryPolicy(d *schema.ResourceData) (*v1pb.DataSourceQueryPolicy, error) {
+ rawList, ok := d.Get("data_source_query_policy").([]interface{})
+ if !ok || len(rawList) != 1 {
+ return nil, errors.Errorf("invalid data_source_query_policy")
+ }
+
+ raw := rawList[0].(map[string]interface{})
+ return &v1pb.DataSourceQueryPolicy{
+ AdminDataSourceRestriction: v1pb.DataSourceQueryPolicy_Restriction(
+ v1pb.DataSourceQueryPolicy_Restriction_value[raw["restriction"].(string)],
+ ),
+ DisallowDdl: raw["disallow_ddl"].(bool),
+ DisallowDml: raw["disallow_dml"].(bool),
+ }, nil
+}
diff --git a/provider/resource_review_config.go b/provider/resource_review_config.go
index 5179f36..bdc6d69 100644
--- a/provider/resource_review_config.go
+++ b/provider/resource_review_config.go
@@ -7,6 +7,7 @@ import (
"strings"
v1pb "github.com/bytebase/bytebase/proto/generated-go/v1"
+ "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"
@@ -115,10 +116,12 @@ func resourceReviewConfigRead(ctx context.Context, d *schema.ResourceData, m int
func resourceReviewConfigDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
c := m.(api.Client)
fullName := d.Id()
+ resources := getReviewConfigRelatedResources(d)
// Warning or errors can be collected in a slice type
var diags diag.Diagnostics
+ removeReviewConfigTag(ctx, c, resources)
if err := c.DeleteReviewConfig(ctx, fullName); err != nil {
return diag.FromErr(err)
}
@@ -135,8 +138,18 @@ func resourceReviewConfigUpsert(ctx context.Context, d *schema.ResourceData, m i
reviewID := d.Get("resource_id").(string)
reviewName := fmt.Sprintf("%s%s", internal.ReviewConfigNamePrefix, reviewID)
- if existedName != "" && existedName != reviewName {
- return diag.Errorf("cannot change the resource id")
+ oldAttachedResources := []string{}
+ if existedName != "" {
+ if existedName != reviewName {
+ return diag.Errorf("cannot change the resource id")
+ }
+
+ existedReview, err := c.GetReviewConfig(ctx, existedName)
+ if err != nil {
+ tflog.Debug(ctx, fmt.Sprintf("get review config %s failed with error: %v", existedName, err))
+ } else if existedReview != nil {
+ oldAttachedResources = existedReview.Resources
+ }
}
rules, err := convertToV1RuleList(d)
@@ -159,12 +172,34 @@ func resourceReviewConfigUpsert(ctx context.Context, d *schema.ResourceData, m i
return diag.FromErr(err)
}
+ removeReviewConfigTag(ctx, c, oldAttachedResources)
patchTagPolicy(ctx, c, d, review.Name)
d.SetId(review.Name)
return resourceReviewConfigRead(ctx, d, m)
}
+func getReviewConfigRelatedResources(d *schema.ResourceData) []string {
+ resources := []string{}
+ rawSet, ok := d.Get("resources").(*schema.Set)
+ if !ok || rawSet.Len() == 0 {
+ return resources
+ }
+ for _, raw := range rawSet.List() {
+ resources = append(resources, raw.(string))
+ }
+ return resources
+}
+
+func removeReviewConfigTag(ctx context.Context, client api.Client, resources []string) {
+ for _, resource := range resources {
+ policyName := fmt.Sprintf("%s/%s%s", resource, internal.PolicyNamePrefix, v1pb.PolicyType_TAG.String())
+ if err := client.DeletePolicy(ctx, policyName); err != nil {
+ tflog.Error(ctx, fmt.Sprintf("failed to delete %v policy with error: %v", policyName, err.Error()))
+ }
+ }
+}
+
func patchTagPolicy(ctx context.Context, client api.Client, d *schema.ResourceData, reviewName string) diag.Diagnostics {
rawSet, ok := d.Get("resources").(*schema.Set)
if !ok || rawSet.Len() == 0 {
diff --git a/provider/resource_setting.go b/provider/resource_setting.go
index b10d2c9..e5b470d 100644
--- a/provider/resource_setting.go
+++ b/provider/resource_setting.go
@@ -192,11 +192,11 @@ func convertToV1ClassificationSetting(d *schema.ResourceData) (*v1pb.DataClassif
return nil, errors.Errorf("id is required for classification config")
}
- rawLevels := raw["levels"].(*schema.Set)
+ rawLevels := raw["levels"].([]interface{})
if !ok {
return nil, errors.Errorf("levels is required for classification config")
}
- for _, level := range rawLevels.List() {
+ for _, level := range rawLevels {
rawLevel := level.(map[string]interface{})
classificationLevel := &v1pb.DataClassificationSetting_DataClassificationConfig_Level{
Id: rawLevel["id"].(string),
@@ -212,11 +212,11 @@ func convertToV1ClassificationSetting(d *schema.ResourceData) (*v1pb.DataClassif
dataClassificationConfig.Levels = append(dataClassificationConfig.Levels, classificationLevel)
}
- rawClassificationss := raw["classifications"].(*schema.Set)
+ rawClassificationss := raw["classifications"].([]interface{})
if !ok {
return nil, errors.Errorf("classifications is required for classification config")
}
- for _, classification := range rawClassificationss.List() {
+ for _, classification := range rawClassificationss {
rawClassification := classification.(map[string]interface{})
classificationData := &v1pb.DataClassificationSetting_DataClassificationConfig_DataClassification{
Id: rawClassification["id"].(string),
@@ -347,13 +347,13 @@ func convertToV1EnvironmentSetting(d *schema.ResourceData) (*v1pb.EnvironmentSet
}
func convertToV1SemanticTypeSetting(d *schema.ResourceData) (*v1pb.SemanticTypeSetting, error) {
- set, ok := d.Get("semantic_types").(*schema.Set)
+ set, ok := d.Get("semantic_types").([]interface{})
if !ok {
return nil, errors.Errorf("invalid semantic_types")
}
setting := &v1pb.SemanticTypeSetting{}
- for _, s := range set.List() {
+ for _, s := range set {
rawSemanticType := s.(map[string]interface{})
semanticType := &v1pb.SemanticTypeSetting_SemanticType{
Id: rawSemanticType["id"].(string),
diff --git a/tutorials/0-provider.tf b/tutorials/0-provider.tf
index 488cd8f..f17221d 100644
--- a/tutorials/0-provider.tf
+++ b/tutorials/0-provider.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.8.0"
+ version = "3.8.1"
# For local development, please use "terraform.local/bytebase/bytebase" instead
source = "registry.terraform.io/bytebase/bytebase"
}
@@ -11,5 +11,5 @@ terraform {
provider "bytebase" {
service_account = "tf@service.bytebase.com"
service_key = "bbs_xxxx"
- url = "https://xxx.xxx.xxx"
-}
\ No newline at end of file
+ url = "https://xxx.xxx.xxx"
+}