diff --git a/VERSION b/VERSION
index 33f465d..4764627 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.9.1
\ No newline at end of file
+3.9.2
\ No newline at end of file
diff --git a/docs/data-sources/policy.md b/docs/data-sources/policy.md
index 113cba2..b2064bc 100644
--- a/docs/data-sources/policy.md
+++ b/docs/data-sources/policy.md
@@ -87,14 +87,15 @@ Optional:
Required:
-- `action` (String)
-- `member` (String) The member in user:{email} or group:{email} format.
+- `actions` (Set of String)
+- `members` (Set of String)
Optional:
-- `column` (String)
+- `columns` (Set of 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
+- `raw_expression` (String) The raw CEL expression. We will use it as the masking exception and ignore the "database"/"schema"/"table"/"columns"/"expire_timestamp" fields if you provide the raw expression.
- `reason` (String) The reason for the masking exemption
- `schema` (String)
- `table` (String)
diff --git a/docs/data-sources/policy_list.md b/docs/data-sources/policy_list.md
index f66e19e..2d39b8e 100644
--- a/docs/data-sources/policy_list.md
+++ b/docs/data-sources/policy_list.md
@@ -29,13 +29,34 @@ The policy data source list.
Read-Only:
+- `data_source_query_policy` (List of Object) (see [below for nested schema](#nestedobjatt--policies--data_source_query_policy))
+- `disable_copy_data_policy` (List of Object) (see [below for nested schema](#nestedobjatt--policies--disable_copy_data_policy))
- `enforce` (Boolean)
- `global_masking_policy` (List of Object) (see [below for nested schema](#nestedobjatt--policies--global_masking_policy))
- `inherit_from_parent` (Boolean)
- `masking_exception_policy` (List of Object) (see [below for nested schema](#nestedobjatt--policies--masking_exception_policy))
- `name` (String)
+- `rollout_policy` (List of Object) (see [below for nested schema](#nestedobjatt--policies--rollout_policy))
- `type` (String)
+
+### Nested Schema for `policies.data_source_query_policy`
+
+Read-Only:
+
+- `disallow_ddl` (Boolean)
+- `disallow_dml` (Boolean)
+- `restriction` (String)
+
+
+
+### Nested Schema for `policies.disable_copy_data_policy`
+
+Read-Only:
+
+- `enable` (Boolean)
+
+
### Nested Schema for `policies.global_masking_policy`
@@ -67,13 +88,24 @@ Read-Only:
Read-Only:
-- `action` (String)
-- `column` (String)
+- `actions` (Set of String)
+- `columns` (Set of String)
- `database` (String)
- `expire_timestamp` (String)
-- `member` (String)
+- `members` (Set of String)
+- `raw_expression` (String)
- `reason` (String)
- `schema` (String)
- `table` (String)
+
+
+### Nested Schema for `policies.rollout_policy`
+
+Read-Only:
+
+- `automatic` (Boolean)
+- `roles` (Set of String)
+
+
diff --git a/docs/resources/policy.md b/docs/resources/policy.md
index 4d5d1a7..cc20d1a 100644
--- a/docs/resources/policy.md
+++ b/docs/resources/policy.md
@@ -87,14 +87,15 @@ Optional:
Required:
-- `action` (String)
-- `member` (String) The member in user:{email} or group:{email} format.
+- `actions` (Set of String)
+- `members` (Set of String)
Optional:
-- `column` (String)
+- `columns` (Set of 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
+- `raw_expression` (String) The raw CEL expression. We will use it as the masking exception and ignore the "database"/"schema"/"table"/"columns"/"expire_timestamp" fields if you provide the raw expression.
- `reason` (String) The reason for the masking exemption
- `schema` (String)
- `table` (String)
diff --git a/examples/database/main.tf b/examples/database/main.tf
index 4e70469..e9a738a 100644
--- a/examples/database/main.tf
+++ b/examples/database/main.tf
@@ -2,7 +2,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.9.1"
+ version = "3.9.2"
# 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 1d20a56..de8d008 100644
--- a/examples/database_group/main.tf
+++ b/examples/database_group/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.9.1"
+ version = "3.9.2"
# 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 dac313c..15bf112 100644
--- a/examples/environments/main.tf
+++ b/examples/environments/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.9.1"
+ version = "3.9.2"
# 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 059fcac..32981d4 100644
--- a/examples/groups/main.tf
+++ b/examples/groups/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.9.1"
+ version = "3.9.2"
# For local development, please use "terraform.local/bytebase/bytebase" instead
source = "registry.terraform.io/bytebase/bytebase"
}
@@ -30,7 +30,7 @@ data "bytebase_project" "sample_project" {
data "bytebase_group_list" "groups_in_project" {
project = data.bytebase_project.sample_project.name
- query = "Bytebase"
+ query = "Bytebase"
}
output "groups_in_project" {
diff --git a/examples/iamPolicy/main.tf b/examples/iamPolicy/main.tf
index 9fe20ec..67b6c50 100644
--- a/examples/iamPolicy/main.tf
+++ b/examples/iamPolicy/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.9.1"
+ version = "3.9.2"
# 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 ce241f8..aaaa9c4 100644
--- a/examples/instances/main.tf
+++ b/examples/instances/main.tf
@@ -2,7 +2,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.9.1"
+ version = "3.9.2"
# 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 a0b8dd8..39fbfdd 100644
--- a/examples/policies/main.tf
+++ b/examples/policies/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.9.1"
+ version = "3.9.2"
# 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 d534bfa..0c735f7 100644
--- a/examples/projects/main.tf
+++ b/examples/projects/main.tf
@@ -2,7 +2,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.9.1"
+ version = "3.9.2"
# 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 0c69e90..054dbd9 100644
--- a/examples/risk/main.tf
+++ b/examples/risk/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.9.1"
+ version = "3.9.2"
# 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 2b7f9cc..6eaf391 100644
--- a/examples/roles/main.tf
+++ b/examples/roles/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.9.1"
+ version = "3.9.2"
# 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 b6d7e09..544b3ec 100644
--- a/examples/settings/main.tf
+++ b/examples/settings/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.9.1"
+ version = "3.9.2"
# For local development, please use "terraform.local/bytebase/bytebase" instead
source = "registry.terraform.io/bytebase/bytebase"
}
diff --git a/examples/setup/data_masking.tf b/examples/setup/data_masking.tf
index 416ed4b..ce94e7a 100644
--- a/examples/setup/data_masking.tf
+++ b/examples/setup/data_masking.tf
@@ -100,7 +100,9 @@ resource "bytebase_setting" "semantic_types" {
resource "bytebase_policy" "masking_exception_policy" {
depends_on = [
bytebase_project.sample_project,
- bytebase_instance.test
+ bytebase_instance.test,
+ bytebase_user.project_developer,
+ bytebase_user.workspace_dba
]
parent = bytebase_project.sample_project.name
@@ -112,17 +114,33 @@ resource "bytebase_policy" "masking_exception_policy" {
exceptions {
database = "instances/test-sample-instance/databases/employee"
table = "salary"
- column = "amount"
- member = "user:ed@bytebase.com"
- action = "EXPORT"
- reason = "Grant access to ed for export"
+ columns = ["amount", "emp_no"]
+ members = [
+ format("user:%s", bytebase_user.project_developer.email),
+ format("user:%s", bytebase_user.workspace_dba.email),
+ ]
+ actions = ["QUERY", "EXPORT"]
+ reason = "Grant access"
}
+
exceptions {
database = "instances/test-sample-instance/databases/employee"
- table = "salary"
- column = "amount"
- member = "user:ed@bytebase.com"
- action = "QUERY"
+ table = "employee"
+ columns = ["emp_no"]
+ members = [
+ format("user:%s", bytebase_user.workspace_dba.email),
+ ]
+ actions = ["EXPORT"]
+ reason = "Grant export access"
+ }
+
+ exceptions {
+ members = [
+ format("user:%s", bytebase_user.project_developer.email),
+ ]
+ actions = ["QUERY"]
+ reason = "Grant query access"
+ raw_expression = "resource.instance_id == \"test-sample-instance\" && resource.database_name == \"employee\" && resource.table_name == \"employee\" && resource.column_name in [\"first_name\", \"last_name\", \"gender\"]"
}
}
}
diff --git a/examples/setup/main.tf b/examples/setup/main.tf
index 709ed27..a8b4784 100644
--- a/examples/setup/main.tf
+++ b/examples/setup/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.9.1"
+ version = "3.9.2"
# For local development, please use "terraform.local/bytebase/bytebase" instead
source = "registry.terraform.io/bytebase/bytebase"
}
diff --git a/examples/sql_review/main.tf b/examples/sql_review/main.tf
index 72e3484..5352690 100644
--- a/examples/sql_review/main.tf
+++ b/examples/sql_review/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.9.1"
+ version = "3.9.2"
# 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 6d4e02a..9954ec1 100644
--- a/examples/users/main.tf
+++ b/examples/users/main.tf
@@ -2,7 +2,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.9.1"
+ version = "3.9.2"
# For local development, please use "terraform.local/bytebase/bytebase" instead
source = "registry.terraform.io/bytebase/bytebase"
}
diff --git a/provider/data_source_policy.go b/provider/data_source_policy.go
index 0c90ca9..77f1bef 100644
--- a/provider/data_source_policy.go
+++ b/provider/data_source_policy.go
@@ -113,25 +113,40 @@ func getMaskingExceptionPolicySchema(computed bool) *schema.Schema {
Optional: true,
ValidateFunc: validation.StringIsNotEmpty,
},
- "column": {
- Type: schema.TypeString,
- Computed: computed,
- Optional: true,
- ValidateFunc: validation.StringIsNotEmpty,
+ "columns": {
+ Type: schema.TypeSet,
+ Computed: computed,
+ Optional: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ ValidateFunc: validation.StringIsNotEmpty,
+ },
},
- "member": {
- Type: schema.TypeString,
- Required: true,
- ValidateFunc: validation.StringIsNotEmpty,
- Description: "The member in user:{email} or group:{email} format.",
+ "members": {
+ Type: schema.TypeSet,
+ Required: true,
+ MinItems: 1,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ Description: "The member in user:{email} or group:{email} format.",
+ ValidateDiagFunc: internal.ResourceNameValidation(
+ "^user:",
+ "^group:",
+ ),
+ },
},
- "action": {
- Type: schema.TypeString,
+ "actions": {
+ Type: schema.TypeSet,
Required: true,
- ValidateFunc: validation.StringInSlice([]string{
- v1pb.MaskingExceptionPolicy_MaskingException_QUERY.String(),
- v1pb.MaskingExceptionPolicy_MaskingException_EXPORT.String(),
- }, false),
+ MinItems: 1,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ Description: "The action to allow for members. Support QUERY or EXPORT",
+ ValidateFunc: validation.StringInSlice([]string{
+ v1pb.MaskingExceptionPolicy_MaskingException_QUERY.String(),
+ v1pb.MaskingExceptionPolicy_MaskingException_EXPORT.String(),
+ }, false),
+ },
},
"reason": {
Type: schema.TypeString,
@@ -144,6 +159,12 @@ func getMaskingExceptionPolicySchema(computed bool) *schema.Schema {
Optional: true,
Description: "The expiration timestamp in YYYY-MM-DDThh:mm:ss.000Z format",
},
+ "raw_expression": {
+ Type: schema.TypeString,
+ Computed: computed,
+ Optional: true,
+ Description: `The raw CEL expression. We will use it as the masking exception and ignore the "database"/"schema"/"table"/"columns"/"expire_timestamp" fields if you provide the raw expression.`,
+ },
},
},
Set: exceptionHash,
@@ -318,10 +339,6 @@ func dataSourcePolicyRead(ctx context.Context, d *schema.ResourceData, m interfa
}
func setPolicyMessage(d *schema.ResourceData, policy *v1pb.Policy) diag.Diagnostics {
- _, 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())
}
@@ -332,51 +349,57 @@ func setPolicyMessage(d *schema.ResourceData, policy *v1pb.Policy) diag.Diagnost
return diag.Errorf("cannot set enforce for policy: %s", err.Error())
}
+ key, payload, diags := flattenPolicyPayload(policy)
+ if diags != nil {
+ return diags
+ }
+ if err := d.Set(key, payload); err != nil {
+ return diag.Errorf("cannot set %s for policy: %s", key, err.Error())
+ }
+
+ return nil
+}
+
+func flattenPolicyPayload(policy *v1pb.Policy) (string, interface{}, diag.Diagnostics) {
+ _, policyType, err := internal.GetPolicyParentAndType(policy.Name)
+ if err != nil {
+ return "", nil, diag.Errorf("cannot parse name for policy: %s", err.Error())
+ }
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())
+ return "", nil, diag.FromErr(err)
}
+ return "masking_exception_policy", exceptionPolicy, nil
}
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())
+ return "", nil, diag.FromErr(err)
}
+ return "global_masking_policy", maskingPolicy, nil
}
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())
- }
+ return "disable_copy_data_policy", disableCopyDataPolicy, nil
}
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())
- }
+ return "data_source_query_policy", dataSourceQueryPolicy, nil
}
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 "rollout_policy", rolloutPolicy, nil
}
}
- return nil
+ return "", nil, diag.Errorf("unsupported policy: %s", policy.Name)
}
func flattenRolloutPolicy(p *v1pb.RolloutPolicy) []interface{} {
@@ -428,21 +451,49 @@ func flattenGlobalMaskingPolicy(p *v1pb.MaskingRulePolicy) ([]interface{}, error
return []interface{}{policy}, nil
}
+type combineException struct {
+ expression string
+ reason string
+ members []interface{}
+ actions []interface{}
+}
+
func flattenMaskingExceptionPolicy(p *v1pb.MaskingExceptionPolicy) ([]interface{}, error) {
exceptionList := []interface{}{}
- for _, exception := range p.MaskingExceptions {
- raw := map[string]interface{}{}
- raw["member"] = exception.Member
- raw["action"] = exception.Action.String()
+ exceptionMap := map[string]*combineException{}
+ for _, exception := range p.MaskingExceptions {
if exception.Condition == nil || exception.Condition.Expression == "" {
- return nil, errors.Errorf("invalid exception policy condition")
+ // Skip invalid data.
+ continue
}
- raw["reason"] = exception.Condition.Description
- expressions := strings.Split(exception.Condition.Expression, " && ")
+ key := fmt.Sprintf("[expression:%s] [reason:%s]", exception.Condition.Expression, exception.Condition.Description)
+ if _, ok := exceptionMap[key]; !ok {
+ exceptionMap[key] = &combineException{
+ expression: exception.Condition.Expression,
+ reason: exception.Condition.Description,
+ members: []interface{}{},
+ actions: []interface{}{},
+ }
+ }
+ exceptionMap[key].members = append(exceptionMap[key].members, exception.Member)
+ exceptionMap[key].actions = append(exceptionMap[key].actions, exception.Action.String())
+ }
+
+ for _, combine := range exceptionMap {
+ raw := map[string]interface{}{
+ "members": schema.NewSet(schema.HashString, combine.members),
+ "actions": schema.NewSet(schema.HashString, combine.actions),
+ "reason": combine.reason,
+ "raw_expression": combine.expression,
+ }
+
+ expressions := strings.Split(combine.expression, " && ")
instanceID := ""
databaseName := ""
+ columns := []interface{}{}
+
for _, expression := range expressions {
if strings.HasPrefix(expression, "resource.instance_id == ") {
instanceID = strings.TrimSuffix(
@@ -469,10 +520,10 @@ func flattenMaskingExceptionPolicy(p *v1pb.MaskingExceptionPolicy) ([]interface{
)
}
if strings.HasPrefix(expression, "resource.column_name == ") {
- raw["column"] = strings.TrimSuffix(
+ columns = append(columns, strings.TrimSuffix(
strings.TrimPrefix(expression, `resource.column_name == "`),
`"`,
- )
+ ))
}
if strings.HasPrefix(expression, "request.time < ") {
raw["expire_timestamp"] = strings.TrimSuffix(
@@ -480,12 +531,31 @@ func flattenMaskingExceptionPolicy(p *v1pb.MaskingExceptionPolicy) ([]interface{
`")`,
)
}
+ if strings.HasPrefix(expression, "resource.column_name in [") {
+ // rawColumnListString should be: "col1", "col2"
+ rawColumnListString := strings.TrimSuffix(
+ strings.TrimPrefix(expression, `resource.column_name in [`),
+ `]`,
+ )
+ rawColumnList := strings.SplitSeq(rawColumnListString, ",")
+ for rawColumn := range rawColumnList {
+ column := strings.TrimSuffix(
+ strings.TrimPrefix(strings.TrimSpace(rawColumn), `"`),
+ `"`,
+ )
+ columns = append(columns, column)
+ }
+ }
}
if instanceID != "" && databaseName != "" {
raw["database"] = fmt.Sprintf("%s%s/%s%s", internal.InstanceNamePrefix, instanceID, internal.DatabaseIDPrefix, databaseName)
}
+ if len(columns) > 0 {
+ raw["columns"] = schema.NewSet(schema.HashString, columns)
+ }
exceptionList = append(exceptionList, raw)
}
+
policy := map[string]interface{}{
"exceptions": exceptionList,
}
@@ -493,9 +563,11 @@ func flattenMaskingExceptionPolicy(p *v1pb.MaskingExceptionPolicy) ([]interface{
}
func exceptionHash(rawSchema interface{}) int {
- exception, err := convertToV1Exception(rawSchema)
+ exceptions, err := convertToV1Exceptions(rawSchema)
if err != nil {
return 0
}
- return internal.ToHash(exception)
+ return internal.ToHash(&v1pb.MaskingExceptionPolicy{
+ MaskingExceptions: exceptions,
+ })
}
diff --git a/provider/data_source_policy_list.go b/provider/data_source_policy_list.go
index a7832ac..a1ce768 100644
--- a/provider/data_source_policy_list.go
+++ b/provider/data_source_policy_list.go
@@ -65,6 +65,9 @@ func dataSourcePolicyList() *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),
},
},
},
@@ -96,20 +99,11 @@ func dataSourcePolicyListRead(ctx context.Context, d *schema.ResourceData, m int
raw["inherit_from_parent"] = policy.InheritFromParent
raw["enforce"] = policy.Enforce
- if p := policy.GetMaskingExceptionPolicy(); p != nil {
- exceptionPolicy, err := flattenMaskingExceptionPolicy(p)
- if err != nil {
- return diag.FromErr(err)
- }
- raw["masking_exception_policy"] = exceptionPolicy
- }
- if p := policy.GetMaskingRulePolicy(); p != nil {
- maskingPolicy, err := flattenGlobalMaskingPolicy(p)
- if err != nil {
- return diag.FromErr(err)
- }
- raw["global_masking_policy"] = maskingPolicy
+ key, payload, diags := flattenPolicyPayload(policy)
+ if diags != nil {
+ return diags
}
+ raw[key] = payload
policies = append(policies, raw)
}
diff --git a/provider/data_source_policy_test.go b/provider/data_source_policy_test.go
index ab9b391..4072a0f 100644
--- a/provider/data_source_policy_test.go
+++ b/provider/data_source_policy_test.go
@@ -39,7 +39,7 @@ func TestAccPolicyDataSource(t *testing.T) {
resource.TestCheckResourceAttr("data.bytebase_policy.masking_exception_policy", "masking_exception_policy.#", "1"),
resource.TestCheckResourceAttr("data.bytebase_policy.masking_exception_policy", "masking_exception_policy.0.exceptions.#", "1"),
resource.TestCheckResourceAttr("data.bytebase_policy.masking_exception_policy", "masking_exception_policy.0.exceptions.0.table", "salary"),
- resource.TestCheckResourceAttr("data.bytebase_policy.masking_exception_policy", "masking_exception_policy.0.exceptions.0.column", "amount"),
+ resource.TestCheckResourceAttr("data.bytebase_policy.masking_exception_policy", "masking_exception_policy.0.exceptions.0.columns.0", "amount"),
),
},
},
diff --git a/provider/resource_policy.go b/provider/resource_policy.go
index 4d4466c..e908391 100644
--- a/provider/resource_policy.go
+++ b/provider/resource_policy.go
@@ -347,57 +347,85 @@ func convertToMaskingRulePolicy(d *schema.ResourceData) (*v1pb.MaskingRulePolicy
return policy, nil
}
-func convertToV1Exception(rawSchema interface{}) (*v1pb.MaskingExceptionPolicy_MaskingException, error) {
+func convertToV1Exceptions(rawSchema interface{}) ([]*v1pb.MaskingExceptionPolicy_MaskingException, error) {
rawException := rawSchema.(map[string]interface{})
expressions := []string{}
- databaseFullName := rawException["database"].(string)
- if databaseFullName != "" {
- instanceID, databaseName, err := internal.GetInstanceDatabaseID(databaseFullName)
- if err != nil {
- return nil, errors.Wrapf(err, "invalid database full name: %v", databaseFullName)
- }
- expressions = append(
- expressions,
- fmt.Sprintf(`resource.instance_id == "%s"`, instanceID),
- fmt.Sprintf(`resource.database_name == "%s"`, databaseName),
- )
-
- if schema, ok := rawException["schema"].(string); ok && schema != "" {
- expressions = append(expressions, fmt.Sprintf(`resource.schema_name == "%s"`, schema))
- }
- if table, ok := rawException["table"].(string); ok && table != "" {
- expressions = append(expressions, fmt.Sprintf(`resource.table_name == "%s"`, table))
+ rawExpression := rawException["raw_expression"].(string)
+
+ if rawExpression != "" {
+ expressions = append(expressions, rawExpression)
+ } else {
+ databaseFullName := rawException["database"].(string)
+ if databaseFullName != "" {
+ instanceID, databaseName, err := internal.GetInstanceDatabaseID(databaseFullName)
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid database full name: %v", databaseFullName)
+ }
+ expressions = append(
+ expressions,
+ fmt.Sprintf(`resource.instance_id == "%s"`, instanceID),
+ fmt.Sprintf(`resource.database_name == "%s"`, databaseName),
+ )
+
+ if schema, ok := rawException["schema"].(string); ok && schema != "" {
+ expressions = append(expressions, fmt.Sprintf(`resource.schema_name == "%s"`, schema))
+ }
+ if table, ok := rawException["table"].(string); ok && table != "" {
+ expressions = append(expressions, fmt.Sprintf(`resource.table_name == "%s"`, table))
+ }
+
+ if rawColumns, ok := rawException["columns"].(*schema.Set); ok && rawColumns.Len() > 0 {
+ columnNames := []string{}
+ for _, column := range rawColumns.List() {
+ columnNames = append(columnNames, fmt.Sprintf(`"%s"`, column.(string)))
+ }
+ expressions = append(expressions, fmt.Sprintf(`resource.column_name in [%s]`, strings.Join(columnNames, ", ")))
+ }
}
- if column, ok := rawException["column"].(string); ok && column != "" {
- expressions = append(expressions, fmt.Sprintf(`resource.column_name == "%s"`, column))
+
+ if expire, ok := rawException["expire_timestamp"].(string); ok && expire != "" {
+ formattedTime, err := time.Parse(time.RFC3339, expire)
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid time: %v", expire)
+ }
+ expressions = append(expressions, fmt.Sprintf(`request.time < timestamp("%s")`, formattedTime.Format(time.RFC3339)))
}
}
- if expire, ok := rawException["expire_timestamp"].(string); ok && expire != "" {
- formattedTime, err := time.Parse(time.RFC3339, expire)
- if err != nil {
- return nil, errors.Wrapf(err, "invalid time: %v", expire)
- }
- expressions = append(expressions, fmt.Sprintf(`request.time < timestamp("%s")`, formattedTime.Format(time.RFC3339)))
+ exceptions := []*v1pb.MaskingExceptionPolicy_MaskingException{}
+ reason := rawException["reason"].(string)
+
+ rawMembers, ok := rawException["members"].(*schema.Set)
+ if !ok || rawMembers.Len() == 0 {
+ return nil, errors.Errorf("invalid members in masking_exception_policy.exceptions")
}
- member := rawException["member"].(string)
- if member == "allUsers" {
- return nil, errors.Errorf("not support allUsers in masking_exception_policy")
+
+ rawActions, ok := rawException["actions"].(*schema.Set)
+ if !ok || rawActions.Len() == 0 {
+ return nil, errors.Errorf("invalid actions in masking_exception_policy.exceptions")
}
- if err := internal.ValidateMemberBinding(member); err != nil {
- return nil, err
+
+ for _, rawMember := range rawMembers.List() {
+ member := rawMember.(string)
+ if err := internal.ValidateMemberBinding(member); err != nil {
+ return nil, err
+ }
+ for _, action := range rawActions.List() {
+ exceptions = append(exceptions, &v1pb.MaskingExceptionPolicy_MaskingException{
+ Member: member,
+ Action: v1pb.MaskingExceptionPolicy_MaskingException_Action(
+ v1pb.MaskingExceptionPolicy_MaskingException_Action_value[action.(string)],
+ ),
+ Condition: &expr.Expr{
+ Description: reason,
+ Expression: strings.Join(expressions, " && "),
+ },
+ })
+ }
}
- return &v1pb.MaskingExceptionPolicy_MaskingException{
- Member: member,
- Action: v1pb.MaskingExceptionPolicy_MaskingException_Action(
- v1pb.MaskingExceptionPolicy_MaskingException_Action_value[rawException["action"].(string)],
- ),
- Condition: &expr.Expr{
- Description: rawException["reason"].(string),
- Expression: strings.Join(expressions, " && "),
- },
- }, nil
+
+ return exceptions, nil
}
func convertToMaskingExceptionPolicy(d *schema.ResourceData) (*v1pb.MaskingExceptionPolicy, error) {
@@ -415,11 +443,11 @@ func convertToMaskingExceptionPolicy(d *schema.ResourceData) (*v1pb.MaskingExcep
policy := &v1pb.MaskingExceptionPolicy{}
for _, raw := range exceptionList.List() {
- exception, err := convertToV1Exception(raw)
+ exceptions, err := convertToV1Exceptions(raw)
if err != nil {
return nil, err
}
- policy.MaskingExceptions = append(policy.MaskingExceptions, exception)
+ policy.MaskingExceptions = append(policy.MaskingExceptions, exceptions...)
}
return policy, nil
}
diff --git a/provider/resource_policy_test.go b/provider/resource_policy_test.go
index cd1eadd..66dbc25 100644
--- a/provider/resource_policy_test.go
+++ b/provider/resource_policy_test.go
@@ -35,7 +35,7 @@ func TestAccPolicy(t *testing.T) {
resource.TestCheckResourceAttr("bytebase_policy.masking_exception_policy", "masking_exception_policy.#", "1"),
resource.TestCheckResourceAttr("bytebase_policy.masking_exception_policy", "masking_exception_policy.0.exceptions.#", "1"),
resource.TestCheckResourceAttr("bytebase_policy.masking_exception_policy", "masking_exception_policy.0.exceptions.0.table", "salary"),
- resource.TestCheckResourceAttr("bytebase_policy.masking_exception_policy", "masking_exception_policy.0.exceptions.0.column", "amount"),
+ resource.TestCheckResourceAttr("bytebase_policy.masking_exception_policy", "masking_exception_policy.0.exceptions.0.columns.0", "amount"),
),
},
},
@@ -59,9 +59,9 @@ func getMaskingExceptionPolicy(database, table, column string) string {
exceptions {
database = "%s"
table = "%s"
- column = "%s"
- member = "user:ed@bytebase.com"
- action = "QUERY"
+ columns = ["%s"]
+ members = ["user:ed@bytebase.com"]
+ actions = ["QUERY"]
}
}
`, database, table, column)
diff --git a/tutorials/0-provider.tf b/tutorials/0-provider.tf
index d6c867a..0ce6d75 100644
--- a/tutorials/0-provider.tf
+++ b/tutorials/0-provider.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "3.9.1"
+ version = "3.9.2"
# For local development, please use "terraform.local/bytebase/bytebase" instead
source = "registry.terraform.io/bytebase/bytebase"
}
diff --git a/tutorials/8-5-masking-exception.tf b/tutorials/8-5-masking-exception.tf
index ff3275e..9c675e0 100644
--- a/tutorials/8-5-masking-exception.tf
+++ b/tutorials/8-5-masking-exception.tf
@@ -11,21 +11,12 @@ resource "bytebase_policy" "masking_exception_policy" {
masking_exception_policy {
exceptions {
- reason = "Business requirement"
- database = "instances/prod-sample-instance/databases/hr_prod"
- table = "employee"
- column = "birth_date"
- member = "user:admin@example.com"
- action = "QUERY"
- expire_timestamp = "2027-07-30T16:11:49Z"
- }
- exceptions {
- reason = "Export data for analysis"
- database = "instances/prod-sample-instance/databases/hr_prod"
- table = "employee"
- column = "last_name"
- member = "user:admin@example.com"
- action = "EXPORT"
+ reason = "Business requirement"
+ database = "instances/prod-sample-instance/databases/hr_prod"
+ table = "employee"
+ columns = ["birth_date", "last_name"]
+ members = ["user:admin@example.com"]
+ actions = ["QUERY", "EXPORT"]
expire_timestamp = "2027-07-30T16:11:49Z"
}
}