diff --git a/README.md b/README.md
index a10bea3..a8ec97e 100644
--- a/README.md
+++ b/README.md
@@ -73,10 +73,10 @@ terraform destory
> This will generate the doc template in the `docs` folder
>
-> Check https://github.com/hashicorp/terraform-plugin-docs for details.
+> Check https://github.com/hashicorp/terraform-plugin-docs and https://github.com/hashicorp/terraform-plugin-docs/issues/141 for details.
```bash
-go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs --provider-name=terraform-provider-bytebase
+GOOS=darwin GOARCH=amd64 go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs --provider-name=terraform-provider-bytebase
```
## Release
diff --git a/VERSION b/VERSION
index e4c0d46..a6a3a43 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.0.3
\ No newline at end of file
+1.0.4
\ No newline at end of file
diff --git a/api/client.go b/api/client.go
index 146cec6..9f742ca 100644
--- a/api/client.go
+++ b/api/client.go
@@ -45,11 +45,11 @@ type Client interface {
// Policy
// ListPolicies lists policies in a specific resource.
- ListPolicies(ctx context.Context, find *PolicyFindMessage) (*ListPolicyMessage, error)
+ ListPolicies(ctx context.Context, parent string) (*v1pb.ListPoliciesResponse, error)
// GetPolicy gets a policy in a specific resource.
- GetPolicy(ctx context.Context, policyName string) (*PolicyMessage, error)
+ GetPolicy(ctx context.Context, policyName string) (*v1pb.Policy, error)
// UpsertPolicy creates or updates the policy.
- UpsertPolicy(ctx context.Context, patch *PolicyPatchMessage) (*PolicyMessage, error)
+ UpsertPolicy(ctx context.Context, patch *v1pb.Policy, updateMasks []string) (*v1pb.Policy, error)
// DeletePolicy deletes the policy.
DeletePolicy(ctx context.Context, policyName string) error
diff --git a/api/deployment.go b/api/deployment.go
deleted file mode 100644
index 2385bf3..0000000
--- a/api/deployment.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package api
-
-// DeploymentType is the type for deployment.
-type DeploymentType string
-
-const (
- // DeploymentTypeDatabaseCreate is the deployment type for creating databases.
- DeploymentTypeDatabaseCreate DeploymentType = "DATABASE_CREATE"
- // DeploymentTypeDatabaseDDL is the deployment type for updating database schemas (DDL).
- DeploymentTypeDatabaseDDL DeploymentType = "DATABASE_DDL"
- // DeploymentTypeDatabaseDDLGhost is the deployment type for updating database schemas using gh-ost.
- DeploymentTypeDatabaseDDLGhost DeploymentType = "DATABASE_DDL_GHOST"
- // DeploymentTypeDatabaseDML is the deployment type for updating database data (DML).
- DeploymentTypeDatabaseDML DeploymentType = "DATABASE_DML"
- // DeploymentTypeDatabaseRestorePITR is the deployment type for performing a Point-in-time Recovery.
- DeploymentTypeDatabaseRestorePITR DeploymentType = "DATABASE_RESTORE_PITR"
- // DeploymentTypeDatabaseDMLRollback is the deployment type for a generated rollback issue.
- DeploymentTypeDatabaseDMLRollback DeploymentType = "DATABASE_DML_ROLLBACK"
-)
diff --git a/api/policy.go b/api/policy.go
deleted file mode 100644
index fa90bb5..0000000
--- a/api/policy.go
+++ /dev/null
@@ -1,130 +0,0 @@
-package api
-
-// PolicyType is the type for the policy.
-type PolicyType string
-
-// ApprovalStrategy is strategy for deployment approval policy.
-type ApprovalStrategy string
-
-// ApprovalGroup is the group for deployment approval policy.
-type ApprovalGroup string
-
-// BackupPlanSchedule is schedule for backup plan policy.
-type BackupPlanSchedule string
-
-// SensitiveDataMaskType is the mask type for sensitive data.
-type SensitiveDataMaskType string
-
-const (
- // PolicyTypeDeploymentApproval is the policy type for deployment approval policy.
- PolicyTypeDeploymentApproval PolicyType = "DEPLOYMENT_APPROVAL"
- // PolicyTypeBackupPlan is the policy type for backup plan policy.
- PolicyTypeBackupPlan PolicyType = "BACKUP_PLAN"
- // PolicyTypeSensitiveData is the policy type for sensitive data policy.
- PolicyTypeSensitiveData PolicyType = "SENSITIVE_DATA"
- // PolicyTypeAccessControl is the policy type for access control policy.
- PolicyTypeAccessControl PolicyType = "ACCESS_CONTROL"
-
- // ApprovalStrategyAutomatic means the pipeline will automatically be approved without user intervention.
- ApprovalStrategyAutomatic ApprovalStrategy = "AUTOMATIC"
- // ApprovalStrategyManual means the pipeline should be manually approved by user to proceed.
- ApprovalStrategyManual ApprovalStrategy = "MANUAL"
-
- // ApprovalGroupDBA means the assignee can be selected from the workspace owners and DBAs.
- ApprovalGroupDBA ApprovalGroup = "APPROVAL_GROUP_DBA"
- // ApprovalGroupOwner means the assignee can be selected from the project owners.
- ApprovalGroupOwner ApprovalGroup = "APPROVAL_GROUP_PROJECT_OWNER"
-
- // BackupPlanScheduleUnset is NEVER backup plan policy value.
- BackupPlanScheduleUnset BackupPlanSchedule = "UNSET"
- // BackupPlanScheduleDaily is DAILY backup plan policy value.
- BackupPlanScheduleDaily BackupPlanSchedule = "DAILY"
- // BackupPlanScheduleWeekly is WEEKLY backup plan policy value.
- BackupPlanScheduleWeekly BackupPlanSchedule = "WEEKLY"
-
- // SensitiveDataMaskTypeDefault is the sensitive data type to hide data with a default method.
- // The default method is subject to change.
- SensitiveDataMaskTypeDefault SensitiveDataMaskType = "DEFAULT"
-)
-
-// DeploymentApprovalPolicy is the policy configuration for deployment approval.
-type DeploymentApprovalPolicy struct {
- DefaultStrategy ApprovalStrategy `json:"defaultStrategy"`
- DeploymentApprovalStrategies []*DeploymentApprovalStrategy `json:"deploymentApprovalStrategies"`
-}
-
-// DeploymentApprovalStrategy is the API message for deployment approval strategy.
-type DeploymentApprovalStrategy struct {
- ApprovalGroup ApprovalGroup `json:"approvalGroup"`
- ApprovalStrategy ApprovalStrategy `json:"approvalStrategy"`
- DeploymentType DeploymentType `json:"deploymentType"`
-}
-
-// BackupPlanPolicy is the policy configuration for backup plan.
-type BackupPlanPolicy struct {
- Schedule BackupPlanSchedule `json:"schedule"`
- // RetentionDuration is the minimum allowed period that backup data is kept for databases in an environment.
- RetentionDuration string `json:"retentionDuration"`
-}
-
-// SensitiveDataPolicy is the API message for sensitive data policy.
-type SensitiveDataPolicy struct {
- SensitiveData []*SensitiveData `json:"sensitiveData"`
-}
-
-// SensitiveData is the API message for sensitive data.
-type SensitiveData struct {
- Schema string `json:"schema"`
- Table string `json:"table"`
- Column string `json:"column"`
- MaskType SensitiveDataMaskType `json:"maskType"`
-}
-
-// AccessControlPolicy is the API message for access control policy.
-type AccessControlPolicy struct {
- DisallowRules []*AccessControlRule `json:"disallowRules"`
-}
-
-// AccessControlRule is the API message for access control rule.
-type AccessControlRule struct {
- FullDatabase bool `json:"fullDatabase"`
-}
-
-// PolicyFindMessage is the API message for finding policies.
-type PolicyFindMessage struct {
- Parent string
- Type *PolicyType
-}
-
-// PolicyMessage is the API message for policy.
-type PolicyMessage struct {
- Name string `json:"name"`
- InheritFromParent bool `json:"inheritFromParent"`
- Type PolicyType `json:"type"`
- Enforce bool `json:"enforce"`
-
- // The policy payload
- DeploymentApprovalPolicy *DeploymentApprovalPolicy `json:"deploymentApprovalPolicy"`
- BackupPlanPolicy *BackupPlanPolicy `json:"backupPlanPolicy"`
- SensitiveDataPolicy *SensitiveDataPolicy `json:"sensitiveDataPolicy"`
- AccessControlPolicy *AccessControlPolicy `json:"accessControlPolicy"`
-}
-
-// PolicyPatchMessage is the API message to patch the policy.
-type PolicyPatchMessage struct {
- Name string `json:"name"`
- InheritFromParent *bool `json:"inheritFromParent"`
- Enforce *bool `json:"enforce"`
-
- // The policy payload
- DeploymentApprovalPolicy *DeploymentApprovalPolicy `json:"deploymentApprovalPolicy"`
- BackupPlanPolicy *BackupPlanPolicy `json:"backupPlanPolicy"`
- SensitiveDataPolicy *SensitiveDataPolicy `json:"sensitiveDataPolicy"`
- AccessControlPolicy *AccessControlPolicy `json:"accessControlPolicy"`
-}
-
-// ListPolicyMessage is the API message for list policy response.
-type ListPolicyMessage struct {
- Policies []*PolicyMessage `json:"policies"`
- NextPageToken string `json:"nextPageToken"`
-}
diff --git a/client/policy.go b/client/policy.go
index a84a89e..23e6e65 100644
--- a/client/policy.go
+++ b/client/policy.go
@@ -2,27 +2,21 @@ package client
import (
"context"
- "encoding/json"
"fmt"
"net/http"
"strings"
- "github.com/pkg/errors"
-
- "github.com/bytebase/terraform-provider-bytebase/api"
+ v1pb "github.com/bytebase/bytebase/proto/generated-go/v1"
+ "google.golang.org/protobuf/encoding/protojson"
)
// ListPolicies lists policies in a specific resource.
-func (c *client) ListPolicies(ctx context.Context, find *api.PolicyFindMessage) (*api.ListPolicyMessage, error) {
- if find.Type != nil {
- return nil, errors.Errorf("invalid request, list policies cannot specific the policy type")
- }
-
+func (c *client) ListPolicies(ctx context.Context, parent string) (*v1pb.ListPoliciesResponse, error) {
var url string
- if find.Parent == "" {
+ if parent == "" {
url = fmt.Sprintf("%s/%s/policies", c.url, c.version)
} else {
- url = fmt.Sprintf("%s/%s/%s/policies", c.url, c.version, find.Parent)
+ url = fmt.Sprintf("%s/%s/%s/policies", c.url, c.version, parent)
}
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
@@ -34,8 +28,8 @@ func (c *client) ListPolicies(ctx context.Context, find *api.PolicyFindMessage)
return nil, err
}
- var res api.ListPolicyMessage
- if err := json.Unmarshal(body, &res); err != nil {
+ var res v1pb.ListPoliciesResponse
+ if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil {
return nil, err
}
@@ -43,7 +37,7 @@ func (c *client) ListPolicies(ctx context.Context, find *api.PolicyFindMessage)
}
// GetPolicy gets a policy in a specific resource.
-func (c *client) GetPolicy(ctx context.Context, policyName string) (*api.PolicyMessage, error) {
+func (c *client) GetPolicy(ctx context.Context, policyName string) (*v1pb.Policy, error) {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s/%s", c.url, c.version, policyName), nil)
if err != nil {
return nil, err
@@ -54,8 +48,8 @@ func (c *client) GetPolicy(ctx context.Context, policyName string) (*api.PolicyM
return nil, err
}
- var res api.PolicyMessage
- if err := json.Unmarshal(body, &res); err != nil {
+ var res v1pb.Policy
+ if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil {
return nil, err
}
@@ -63,24 +57,13 @@ func (c *client) GetPolicy(ctx context.Context, policyName string) (*api.PolicyM
}
// UpsertPolicy creates or updates the policy.
-func (c *client) UpsertPolicy(ctx context.Context, patch *api.PolicyPatchMessage) (*api.PolicyMessage, error) {
- payload, err := json.Marshal(patch)
+func (c *client) UpsertPolicy(ctx context.Context, policy *v1pb.Policy, updateMasks []string) (*v1pb.Policy, error) {
+ payload, err := protojson.Marshal(policy)
if err != nil {
return nil, err
}
- paths := []string{}
- if patch.InheritFromParent != nil {
- paths = append(paths, "inherit_from_parent")
- }
- if patch.DeploymentApprovalPolicy != nil ||
- patch.BackupPlanPolicy != nil ||
- patch.SensitiveDataPolicy != nil ||
- patch.AccessControlPolicy != nil {
- paths = append(paths, "payload")
- }
-
- req, err := http.NewRequestWithContext(ctx, "PATCH", fmt.Sprintf("%s/%s/%s?allow_missing=true&update_mask=%s", c.url, c.version, patch.Name, strings.Join(paths, ",")), strings.NewReader(string(payload)))
+ req, err := http.NewRequestWithContext(ctx, "PATCH", fmt.Sprintf("%s/%s/%s?allow_missing=true&update_mask=%s", c.url, c.version, policy.Name, strings.Join(updateMasks, ",")), strings.NewReader(string(payload)))
if err != nil {
return nil, err
}
@@ -90,8 +73,8 @@ func (c *client) UpsertPolicy(ctx context.Context, patch *api.PolicyPatchMessage
return nil, err
}
- var res api.PolicyMessage
- if err := json.Unmarshal(body, &res); err != nil {
+ var res v1pb.Policy
+ if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil {
return nil, err
}
diff --git a/docs/data-sources/instance.md b/docs/data-sources/instance.md
index b6a81f8..e75e629 100644
--- a/docs/data-sources/instance.md
+++ b/docs/data-sources/instance.md
@@ -37,7 +37,11 @@ Read-Only:
- `database` (String)
- `host` (String)
- `id` (String)
+- `password` (String)
- `port` (String)
+- `ssl_ca` (String)
+- `ssl_cert` (String)
+- `ssl_key` (String)
- `type` (String)
- `username` (String)
diff --git a/docs/data-sources/instance_list.md b/docs/data-sources/instance_list.md
index 9b3dfa0..4608bef 100644
--- a/docs/data-sources/instance_list.md
+++ b/docs/data-sources/instance_list.md
@@ -45,7 +45,11 @@ Read-Only:
- `database` (String)
- `host` (String)
- `id` (String)
+- `password` (String)
- `port` (String)
+- `ssl_ca` (String)
+- `ssl_cert` (String)
+- `ssl_key` (String)
- `type` (String)
- `username` (String)
diff --git a/docs/data-sources/policy.md b/docs/data-sources/policy.md
index 97b1dbd..c519319 100644
--- a/docs/data-sources/policy.md
+++ b/docs/data-sources/policy.md
@@ -21,11 +21,9 @@ The policy data source.
### Optional
-- `access_control_policy` (Block List) (see [below for nested schema](#nestedblock--access_control_policy))
-- `backup_plan_policy` (Block List) (see [below for nested schema](#nestedblock--backup_plan_policy))
-- `deployment_approval_policy` (Block List) (see [below for nested schema](#nestedblock--deployment_approval_policy))
+- `masking_exception_policy` (Block List) (see [below for nested schema](#nestedblock--masking_exception_policy))
+- `masking_policy` (Block List) (see [below for nested schema](#nestedblock--masking_policy))
- `parent` (String) The policy parent name for the policy, support projects/{resource id}, environments/{resource id}, instances/{resource id}, or instances/{resource id}/databases/{database name}
-- `sensitive_data_policy` (Block List) (see [below for nested schema](#nestedblock--sensitive_data_policy))
### Read-Only
@@ -34,64 +32,45 @@ 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 `access_control_policy`
+
+### Nested Schema for `masking_exception_policy`
Optional:
-- `disallow_rules` (Block List) (see [below for nested schema](#nestedblock--access_control_policy--disallow_rules))
+- `exceptions` (Block List) (see [below for nested schema](#nestedblock--masking_exception_policy--exceptions))
-
-### Nested Schema for `access_control_policy.disallow_rules`
+
+### Nested Schema for `masking_exception_policy.exceptions`
Optional:
-- `all_databases` (Boolean)
-
-
-
-
-### Nested Schema for `backup_plan_policy`
-
-Optional:
-
-- `retention_duration` (Number) The minimum allowed seconds that backup data is kept for databases in an environment.
-- `schedule` (String)
-
-
-
-### Nested Schema for `deployment_approval_policy`
-
-Optional:
-
-- `default_strategy` (String)
-- `deployment_approval_strategies` (Block List) (see [below for nested schema](#nestedblock--deployment_approval_policy--deployment_approval_strategies))
-
-
-### Nested Schema for `deployment_approval_policy.deployment_approval_strategies`
-
-Optional:
-
-- `approval_group` (String)
-- `approval_strategy` (String)
-- `deployment_type` (String)
+- `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
+- `masking_level` (String)
+- `member` (String) The member in user:{email} format.
+- `schema` (String)
+- `table` (String)
-
-### Nested Schema for `sensitive_data_policy`
+
+### Nested Schema for `masking_policy`
Optional:
-- `sensitive_data` (Block List) (see [below for nested schema](#nestedblock--sensitive_data_policy--sensitive_data))
+- `mask_data` (Block List) (see [below for nested schema](#nestedblock--masking_policy--mask_data))
-
-### Nested Schema for `sensitive_data_policy.sensitive_data`
+
+### Nested Schema for `masking_policy.mask_data`
Optional:
- `column` (String)
-- `mask_type` (String)
+- `full_masking_algorithm_id` (String)
+- `masking_level` (String)
+- `partial_masking_algorithm_id` (String)
- `schema` (String)
- `table` (String)
diff --git a/docs/data-sources/policy_list.md b/docs/data-sources/policy_list.md
index 2835182..90390fd 100644
--- a/docs/data-sources/policy_list.md
+++ b/docs/data-sources/policy_list.md
@@ -29,73 +29,52 @@ The policy data source list.
Read-Only:
-- `access_control_policy` (List of Object) (see [below for nested schema](#nestedobjatt--policies--access_control_policy))
-- `backup_plan_policy` (List of Object) (see [below for nested schema](#nestedobjatt--policies--backup_plan_policy))
-- `deployment_approval_policy` (List of Object) (see [below for nested schema](#nestedobjatt--policies--deployment_approval_policy))
- `enforce` (Boolean)
- `inherit_from_parent` (Boolean)
+- `masking_exception_policy` (List of Object) (see [below for nested schema](#nestedobjatt--policies--masking_exception_policy))
+- `masking_policy` (List of Object) (see [below for nested schema](#nestedobjatt--policies--masking_policy))
- `name` (String)
-- `sensitive_data_policy` (List of Object) (see [below for nested schema](#nestedobjatt--policies--sensitive_data_policy))
- `type` (String)
-
-### Nested Schema for `policies.access_control_policy`
+
+### Nested Schema for `policies.masking_exception_policy`
Read-Only:
-- `disallow_rules` (List of Object) (see [below for nested schema](#nestedobjatt--policies--access_control_policy--disallow_rules))
+- `exceptions` (List of Object) (see [below for nested schema](#nestedobjatt--policies--masking_exception_policy--exceptions))
-
-### Nested Schema for `policies.access_control_policy.disallow_rules`
+
+### Nested Schema for `policies.masking_exception_policy.exceptions`
Read-Only:
-- `all_databases` (Boolean)
-
-
-
-
-### Nested Schema for `policies.backup_plan_policy`
-
-Read-Only:
-
-- `retention_duration` (Number)
-- `schedule` (String)
-
-
-
-### Nested Schema for `policies.deployment_approval_policy`
-
-Read-Only:
-
-- `default_strategy` (String)
-- `deployment_approval_strategies` (List of Object) (see [below for nested schema](#nestedobjatt--policies--deployment_approval_policy--deployment_approval_strategies))
-
-
-### Nested Schema for `policies.deployment_approval_policy.deployment_approval_strategies`
-
-Read-Only:
-
-- `approval_group` (String)
-- `approval_strategy` (String)
-- `deployment_type` (String)
+- `action` (String)
+- `column` (String)
+- `database` (String)
+- `expire_timestamp` (String)
+- `masking_level` (String)
+- `member` (String)
+- `schema` (String)
+- `table` (String)
-
-### Nested Schema for `policies.sensitive_data_policy`
+
+### Nested Schema for `policies.masking_policy`
Read-Only:
-- `sensitive_data` (List of Object) (see [below for nested schema](#nestedobjatt--policies--sensitive_data_policy--sensitive_data))
+- `mask_data` (List of Object) (see [below for nested schema](#nestedobjatt--policies--masking_policy--mask_data))
-
-### Nested Schema for `policies.sensitive_data_policy.sensitive_data`
+
+### Nested Schema for `policies.masking_policy.mask_data`
Read-Only:
- `column` (String)
-- `mask_type` (String)
+- `full_masking_algorithm_id` (String)
+- `masking_level` (String)
+- `partial_masking_algorithm_id` (String)
- `schema` (String)
- `table` (String)
diff --git a/docs/data-sources/setting.md b/docs/data-sources/setting.md
new file mode 100644
index 0000000..0c9bf2e
--- /dev/null
+++ b/docs/data-sources/setting.md
@@ -0,0 +1,90 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "bytebase_setting Data Source - terraform-provider-bytebase"
+subcategory: ""
+description: |-
+ The setting data source.
+---
+
+# bytebase_setting (Data Source)
+
+The setting data source.
+
+
+
+
+## Schema
+
+### Required
+
+- `name` (String)
+
+### Read-Only
+
+- `approval_flow` (Block List) (see [below for nested schema](#nestedblock--approval_flow))
+- `external_approval_nodes` (Block List) (see [below for nested schema](#nestedblock--external_approval_nodes))
+- `id` (String) The ID of this resource.
+
+
+### Nested Schema for `approval_flow`
+
+Read-Only:
+
+- `rules` (List of Object) (see [below for nested schema](#nestedatt--approval_flow--rules))
+
+
+### Nested Schema for `approval_flow.rules`
+
+Read-Only:
+
+- `conditions` (List of Object) (see [below for nested schema](#nestedobjatt--approval_flow--rules--conditions))
+- `flow` (List of Object) (see [below for nested schema](#nestedobjatt--approval_flow--rules--flow))
+
+
+### Nested Schema for `approval_flow.rules.conditions`
+
+Read-Only:
+
+- `level` (String)
+- `source` (String)
+
+
+
+### Nested Schema for `approval_flow.rules.flow`
+
+Read-Only:
+
+- `creator` (String)
+- `description` (String)
+- `steps` (List of Object) (see [below for nested schema](#nestedobjatt--approval_flow--rules--flow--steps))
+- `title` (String)
+
+
+### Nested Schema for `approval_flow.rules.flow.title`
+
+Read-Only:
+
+- `node` (String)
+- `type` (String)
+
+
+
+
+
+
+### Nested Schema for `external_approval_nodes`
+
+Read-Only:
+
+- `nodes` (List of Object) (see [below for nested schema](#nestedatt--external_approval_nodes--nodes))
+
+
+### Nested Schema for `external_approval_nodes.nodes`
+
+Read-Only:
+
+- `endpoint` (String)
+- `id` (String)
+- `title` (String)
+
+
diff --git a/docs/resources/instance.md b/docs/resources/instance.md
index d81c1df..e0a052e 100644
--- a/docs/resources/instance.md
+++ b/docs/resources/instance.md
@@ -45,10 +45,10 @@ Required:
Optional:
- `database` (String) The database for the instance, you can set this if the engine type is POSTGRES.
-- `password` (String) The connection user password used by Bytebase to perform DDL and DML operations.
-- `ssl_ca` (String) The CA certificate. Optional, you can set this if the engine type is MYSQL, POSTGRES, TIDB or CLICKHOUSE.
-- `ssl_cert` (String) The client certificate. Optional, you can set this if the engine type is MYSQL, POSTGRES, TIDB or CLICKHOUSE.
-- `ssl_key` (String) The client key. Optional, you can set this if the engine type is MYSQL, POSTGRES, TIDB or CLICKHOUSE.
+- `password` (String, Sensitive) The connection user password used by Bytebase to perform DDL and DML operations.
+- `ssl_ca` (String, Sensitive) The CA certificate. Optional, you can set this if the engine type is MYSQL, POSTGRES, TIDB or CLICKHOUSE.
+- `ssl_cert` (String, Sensitive) The client certificate. Optional, you can set this if the engine type is MYSQL, POSTGRES, TIDB or CLICKHOUSE.
+- `ssl_key` (String, Sensitive) The client key. Optional, you can set this if the engine type is MYSQL, POSTGRES, TIDB or CLICKHOUSE.
- `username` (String) The connection user name used by Bytebase to perform DDL and DML operations.
diff --git a/docs/resources/policy.md b/docs/resources/policy.md
index 7ea158b..7df802b 100644
--- a/docs/resources/policy.md
+++ b/docs/resources/policy.md
@@ -22,76 +22,55 @@ The policy resource.
### Optional
-- `access_control_policy` (Block List) (see [below for nested schema](#nestedblock--access_control_policy))
-- `backup_plan_policy` (Block List) (see [below for nested schema](#nestedblock--backup_plan_policy))
-- `deployment_approval_policy` (Block List) (see [below for nested schema](#nestedblock--deployment_approval_policy))
- `enforce` (Boolean) Decide if the policy is enforced.
- `inherit_from_parent` (Boolean) Decide if the policy should inherit from the parent.
-- `sensitive_data_policy` (Block List) (see [below for nested schema](#nestedblock--sensitive_data_policy))
+- `masking_exception_policy` (Block List) (see [below for nested schema](#nestedblock--masking_exception_policy))
+- `masking_policy` (Block List) (see [below for nested schema](#nestedblock--masking_policy))
### Read-Only
- `id` (String) The ID of this resource.
- `name` (String) The policy full name
-
-### Nested Schema for `access_control_policy`
+
+### Nested Schema for `masking_exception_policy`
Optional:
-- `disallow_rules` (Block List) (see [below for nested schema](#nestedblock--access_control_policy--disallow_rules))
+- `exceptions` (Block List) (see [below for nested schema](#nestedblock--masking_exception_policy--exceptions))
-
-### Nested Schema for `access_control_policy.disallow_rules`
+
+### Nested Schema for `masking_exception_policy.exceptions`
Optional:
-- `all_databases` (Boolean)
-
-
-
-
-### Nested Schema for `backup_plan_policy`
-
-Optional:
-
-- `retention_duration` (Number) The minimum allowed seconds that backup data is kept for databases in an environment.
-- `schedule` (String)
-
-
-
-### Nested Schema for `deployment_approval_policy`
-
-Optional:
-
-- `default_strategy` (String)
-- `deployment_approval_strategies` (Block List) (see [below for nested schema](#nestedblock--deployment_approval_policy--deployment_approval_strategies))
-
-
-### Nested Schema for `deployment_approval_policy.deployment_approval_strategies`
-
-Optional:
-
-- `approval_group` (String)
-- `approval_strategy` (String)
-- `deployment_type` (String)
+- `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
+- `masking_level` (String)
+- `member` (String) The member in user:{email} format.
+- `schema` (String)
+- `table` (String)
-
-### Nested Schema for `sensitive_data_policy`
+
+### Nested Schema for `masking_policy`
Optional:
-- `sensitive_data` (Block List) (see [below for nested schema](#nestedblock--sensitive_data_policy--sensitive_data))
+- `mask_data` (Block List) (see [below for nested schema](#nestedblock--masking_policy--mask_data))
-
-### Nested Schema for `sensitive_data_policy.sensitive_data`
+
+### Nested Schema for `masking_policy.mask_data`
Optional:
- `column` (String)
-- `mask_type` (String)
+- `full_masking_algorithm_id` (String)
+- `masking_level` (String)
+- `partial_masking_algorithm_id` (String)
- `schema` (String)
- `table` (String)
diff --git a/docs/resources/setting.md b/docs/resources/setting.md
new file mode 100644
index 0000000..93938a6
--- /dev/null
+++ b/docs/resources/setting.md
@@ -0,0 +1,102 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "bytebase_setting Resource - terraform-provider-bytebase"
+subcategory: ""
+description: |-
+ The setting resource.
+---
+
+# bytebase_setting (Resource)
+
+The setting resource.
+
+
+
+
+## Schema
+
+### Required
+
+- `name` (String)
+
+### Optional
+
+- `approval_flow` (Block List) (see [below for nested schema](#nestedblock--approval_flow))
+- `external_approval_nodes` (Block List) (see [below for nested schema](#nestedblock--external_approval_nodes))
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+
+
+### Nested Schema for `approval_flow`
+
+Required:
+
+- `rules` (Block List, Min: 1) (see [below for nested schema](#nestedblock--approval_flow--rules))
+
+
+### Nested Schema for `approval_flow.rules`
+
+Required:
+
+- `flow` (Block List, Min: 1) (see [below for nested schema](#nestedblock--approval_flow--rules--flow))
+
+Optional:
+
+- `conditions` (Block List) Match any condition will trigger this approval flow. (see [below for nested schema](#nestedblock--approval_flow--rules--conditions))
+
+
+### Nested Schema for `approval_flow.rules.flow`
+
+Required:
+
+- `creator` (String) The creator name in users/{email} format
+- `steps` (Block List, Min: 1) Approval flow following the step order. (see [below for nested schema](#nestedblock--approval_flow--rules--flow--steps))
+- `title` (String)
+
+Optional:
+
+- `description` (String)
+
+
+### Nested Schema for `approval_flow.rules.flow.steps`
+
+Required:
+
+- `node` (String)
+
+Optional:
+
+- `type` (String)
+
+
+
+
+### Nested Schema for `approval_flow.rules.conditions`
+
+Optional:
+
+- `level` (String)
+- `source` (String)
+
+
+
+
+
+### Nested Schema for `external_approval_nodes`
+
+Required:
+
+- `nodes` (Block List, Min: 1) (see [below for nested schema](#nestedblock--external_approval_nodes--nodes))
+
+
+### Nested Schema for `external_approval_nodes.nodes`
+
+Required:
+
+- `endpoint` (String) The endpoint URL to receive the approval message. Learn more: https://www.bytebase.com/docs/api/external-approval
+- `id` (String) The unique external node id.
+- `title` (String) The external node title.
+
+
diff --git a/examples/policies/main.tf b/examples/policies/main.tf
new file mode 100644
index 0000000..b0da04d
--- /dev/null
+++ b/examples/policies/main.tf
@@ -0,0 +1,28 @@
+terraform {
+ required_providers {
+ bytebase = {
+ version = "1.0.4"
+ # For local development, please use "terraform.local/bytebase/bytebase" instead
+ source = "registry.terraform.io/bytebase/bytebase"
+ }
+ }
+}
+
+provider "bytebase" {
+ # You need to replace the account and key with your Bytebase service account.
+ service_account = "terraform@service.bytebase.com"
+ service_key = "bbs_BxVIp7uQsARl8nR92ZZV"
+ # The Bytebase service URL. You can use the external URL in production.
+ # Check the docs about external URL: https://www.bytebase.com/docs/get-started/install/external-url
+ url = "https://bytebase.example.com"
+}
+
+data "bytebase_policy" "masking_policy" {
+ parent = "instances/test-sample-instance/databases/employee"
+ type = "MASKING"
+}
+
+data "bytebase_policy" "masking_exception_policy" {
+ parent = "projects/project-sample"
+ type = "MASKING_EXCEPTION"
+}
diff --git a/examples/settings/main.tf b/examples/settings/main.tf
index b43021e..377e866 100644
--- a/examples/settings/main.tf
+++ b/examples/settings/main.tf
@@ -1,22 +1,26 @@
terraform {
required_providers {
bytebase = {
- version = "1.0.3"
+ version = "1.0.4"
# For local development, please use "terraform.local/bytebase/bytebase" instead
- source = "terraform.local/bytebase/bytebase"
+ source = "registry.terraform.io/bytebase/bytebase"
}
}
}
provider "bytebase" {
# You need to replace the account and key with your Bytebase service account.
- service_account = "ed@bytebase.com"
- service_key = "12345678A!"
+ service_account = "terraform@service.bytebase.com"
+ service_key = "bbs_BxVIp7uQsARl8nR92ZZV"
# The Bytebase service URL. You can use the external URL in production.
# Check the docs about external URL: https://www.bytebase.com/docs/get-started/install/external-url
- url = "http://localhost:8080"
+ url = "https://bytebase.example.com"
}
data "bytebase_setting" "approval_flow" {
name = "bb.workspace.approval"
}
+
+data "bytebase_setting" "external_approval" {
+ name = "bb.workspace.approval.external"
+}
diff --git a/examples/setup/main.tf b/examples/setup/main.tf
index 8266761..93b1dd7 100644
--- a/examples/setup/main.tf
+++ b/examples/setup/main.tf
@@ -1,7 +1,7 @@
terraform {
required_providers {
bytebase = {
- version = "1.0.3"
+ version = "1.0.4"
# For local development, please use "terraform.local/bytebase/bytebase" instead
source = "registry.terraform.io/bytebase/bytebase"
}
@@ -45,6 +45,9 @@ resource "bytebase_environment" "prod" {
# Create a new instance named "test instance"
# You can replace the parameters with your real instance
resource "bytebase_instance" "test" {
+ depends_on = [
+ bytebase_environment.test
+ ]
resource_id = local.instance_id_test
environment = bytebase_environment.test.name
title = "test instance"
@@ -73,6 +76,10 @@ resource "bytebase_instance" "test" {
# Create a new instance named "prod instance"
resource "bytebase_instance" "prod" {
+ depends_on = [
+ bytebase_environment.prod
+ ]
+
resource_id = local.instance_id_prod
environment = bytebase_environment.prod.name
title = "prod instance"
@@ -96,6 +103,24 @@ resource "bytebase_project" "sample_project" {
key = "SAMM"
}
+resource "bytebase_setting" "external_approval" {
+ name = "bb.workspace.approval.external"
+
+ external_approval_nodes {
+ nodes {
+ id = "9e150339-f014-4835-83d7-123aeb1895ba"
+ title = "Example node"
+ endpoint = "https://example.com"
+ }
+
+ nodes {
+ id = "49a976be-50de-4541-b2d3-f2e32f8e41ef"
+ title = "Example node 2"
+ endpoint = "https://example.com"
+ }
+ }
+}
+
resource "bytebase_setting" "approval_flow" {
name = "bb.workspace.approval"
approval_flow {
@@ -129,3 +154,57 @@ resource "bytebase_setting" "approval_flow" {
}
}
}
+
+resource "bytebase_policy" "masking_policy" {
+ depends_on = [
+ bytebase_instance.test
+ ]
+
+ parent = "instances/test-sample-instance/databases/employee"
+ type = "MASKING"
+ enforce = true
+ inherit_from_parent = false
+
+ masking_policy {
+ mask_data {
+ table = "salary"
+ column = "amount"
+ masking_level = "FULL"
+ }
+ mask_data {
+ table = "salary"
+ column = "emp_no"
+ masking_level = "NONE"
+ }
+ }
+}
+
+resource "bytebase_policy" "masking_exception_policy" {
+ depends_on = [
+ bytebase_project.sample_project
+ ]
+
+ parent = bytebase_project.sample_project.name
+ type = "MASKING_EXCEPTION"
+ enforce = true
+ inherit_from_parent = false
+
+ masking_exception_policy {
+ exceptions {
+ database = "instances/test-sample-instance/databases/employee"
+ table = "salary"
+ column = "amount"
+ masking_level = "NONE"
+ member = "user:ed@bytebase.com"
+ action = "EXPORT"
+ }
+ exceptions {
+ database = "instances/test-sample-instance/databases/employee"
+ table = "salary"
+ column = "amount"
+ masking_level = "NONE"
+ member = "user:ed@bytebase.com"
+ action = "QUERY"
+ }
+ }
+}
diff --git a/provider/data_source_policy.go b/provider/data_source_policy.go
index bd148b2..1be5abd 100644
--- a/provider/data_source_policy.go
+++ b/provider/data_source_policy.go
@@ -4,12 +4,14 @@ import (
"context"
"fmt"
"regexp"
- "strconv"
"strings"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
+ "github.com/pkg/errors"
+
+ v1pb "github.com/bytebase/bytebase/proto/generated-go/v1"
"github.com/bytebase/terraform-provider-bytebase/api"
"github.com/bytebase/terraform-provider-bytebase/provider/internal"
@@ -34,7 +36,7 @@ func dataSourcePolicy() *schema.Resource {
// project policy
regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.ProjectNamePrefix, internal.ResourceIDPattern)),
// database policy
- regexp.MustCompile(fmt.Sprintf("^%s%s%s%s$", internal.InstanceNamePrefix, internal.ResourceIDPattern, internal.DatabaseIDPrefix, internal.ResourceIDPattern)),
+ regexp.MustCompile(fmt.Sprintf("^%s%s/%s%s$", internal.InstanceNamePrefix, internal.ResourceIDPattern, internal.DatabaseIDPrefix, internal.ResourceIDPattern)),
),
Description: "The policy parent name for the policy, support projects/{resource id}, environments/{resource id}, instances/{resource id}, or instances/{resource id}/databases/{database name}",
},
@@ -42,10 +44,8 @@ func dataSourcePolicy() *schema.Resource {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
- string(api.PolicyTypeDeploymentApproval),
- string(api.PolicyTypeBackupPlan),
- string(api.PolicyTypeSensitiveData),
- string(api.PolicyTypeAccessControl),
+ v1pb.PolicyType_MASKING.String(),
+ v1pb.PolicyType_MASKING_EXCEPTION.String(),
}, false),
Description: "The policy type.",
},
@@ -64,140 +64,13 @@ func dataSourcePolicy() *schema.Resource {
Computed: true,
Description: "Decide if the policy is enforced.",
},
- "deployment_approval_policy": getDeploymentApprovalPolicySchema(true),
- "backup_plan_policy": getBackupPlanPolicySchema(true),
- "sensitive_data_policy": getSensitiveDataPolicy(true),
- "access_control_policy": getAccessControlPolicy(true),
+ "masking_policy": getMaskingPolicySchema(true),
+ "masking_exception_policy": getMaskingExceptionPolicySchema(true),
},
}
}
-func dataSourcePolicyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- c := m.(api.Client)
-
- policyName := fmt.Sprintf("%s/%s%s", d.Get("parent").(string), internal.PolicyNamePrefix, d.Get("type").(string))
- policy, err := c.GetPolicy(ctx, policyName)
- if err != nil {
- return diag.FromErr(err)
- }
-
- d.SetId(policy.Name)
- return setPolicyMessage(d, policy)
-}
-
-func setPolicyMessage(d *schema.ResourceData, policy *api.PolicyMessage) diag.Diagnostics {
- parent, _, 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())
- }
- if err := d.Set("enforce", policy.Enforce); err != nil {
- return diag.Errorf("cannot set enforce for policy: %s", err.Error())
- }
-
- if p := policy.DeploymentApprovalPolicy; p != nil {
- if err := d.Set("deployment_approval_policy", flattenDeploymentApprovalPolicy(p)); err != nil {
- return diag.Errorf("cannot set deployment_approval_policy: %s", err.Error())
- }
- }
-
- if p := policy.BackupPlanPolicy; p != nil {
- backupPlan, err := flattenBackupPlanPolicy(p)
- if err != nil {
- return diag.FromErr(err)
- }
- if err := d.Set("backup_plan_policy", backupPlan); err != nil {
- return diag.Errorf("cannot set backup_plan_policy: %s", err.Error())
- }
- }
-
- if p := policy.SensitiveDataPolicy; p != nil {
- if err := d.Set("sensitive_data_policy", flattenSensitiveDataPolicy(p)); err != nil {
- return diag.Errorf("cannot set sensitive_data_policy: %s", err.Error())
- }
- }
-
- if p := policy.AccessControlPolicy; p != nil {
- if err := d.Set("access_control_policy", flattenAccessControlPolicy(p)); err != nil {
- return diag.Errorf("cannot set access_control_policy: %s", err.Error())
- }
- }
-
- return nil
-}
-
-func flattenDeploymentApprovalPolicy(p *api.DeploymentApprovalPolicy) []interface{} {
- strategies := []interface{}{}
- for _, strategy := range p.DeploymentApprovalStrategies {
- raw := map[string]interface{}{}
- raw["approval_group"] = strategy.ApprovalGroup
- raw["approval_strategy"] = strategy.ApprovalStrategy
- raw["deployment_type"] = strategy.DeploymentType
- strategies = append(strategies, raw)
- }
- policy := map[string]interface{}{
- "default_strategy": p.DefaultStrategy,
- "deployment_approval_strategies": strategies,
- }
-
- return []interface{}{policy}
-}
-
-func flattenBackupPlanPolicy(p *api.BackupPlanPolicy) ([]interface{}, error) {
- duration := p.RetentionDuration
- if strings.HasSuffix(duration, "s") {
- duration = duration[:(len(duration) - 1)]
- }
- d, err := strconv.Atoi(duration)
- if err != nil {
- return nil, err
- }
-
- policy := map[string]interface{}{
- "schedule": p.Schedule,
- "retention_duration": d,
- }
- return []interface{}{policy}, nil
-}
-
-func flattenSensitiveDataPolicy(p *api.SensitiveDataPolicy) []interface{} {
- sensitiveDataList := []interface{}{}
- for _, data := range p.SensitiveData {
- raw := map[string]interface{}{}
- raw["schema"] = data.Schema
- raw["table"] = data.Table
- raw["column"] = data.Column
- raw["mask_type"] = data.MaskType
- sensitiveDataList = append(sensitiveDataList, raw)
- }
- policy := map[string]interface{}{
- "sensitive_data": sensitiveDataList,
- }
- return []interface{}{policy}
-}
-
-func flattenAccessControlPolicy(p *api.AccessControlPolicy) []interface{} {
- rules := []interface{}{}
- for _, rule := range p.DisallowRules {
- raw := map[string]interface{}{}
- raw["all_databases"] = rule.FullDatabase
- rules = append(rules, raw)
- }
- policy := map[string]interface{}{
- "disallow_rules": rules,
- }
- return []interface{}{policy}
-}
-
-func getDeploymentApprovalPolicySchema(computed bool) *schema.Schema {
+func getMaskingExceptionPolicySchema(computed bool) *schema.Schema {
return &schema.Schema{
Computed: computed,
Optional: true,
@@ -205,53 +78,68 @@ func getDeploymentApprovalPolicySchema(computed bool) *schema.Schema {
Type: schema.TypeList,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
- "default_strategy": {
- Type: schema.TypeString,
+ "exceptions": {
Computed: computed,
Optional: true,
- ValidateFunc: validation.StringInSlice([]string{
- string(api.ApprovalStrategyManual),
- string(api.ApprovalStrategyAutomatic),
- }, false),
- },
- "deployment_approval_strategies": {
+ Default: nil,
Type: schema.TypeList,
- Computed: computed,
- Optional: true,
- MinItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
- "approval_group": {
+ "database": {
+ Type: schema.TypeString,
+ Computed: computed,
+ Optional: true,
+ ValidateFunc: validation.StringIsNotEmpty,
+ Description: "The database full name in instances/{instance resource id}/databases/{database name} format",
+ },
+ "schema": {
Type: schema.TypeString,
Computed: computed,
Optional: true,
- ValidateFunc: validation.StringInSlice([]string{
- string(api.ApprovalGroupDBA),
- string(api.ApprovalGroupOwner),
- }, false),
},
- "approval_strategy": {
+ "table": {
+ Type: schema.TypeString,
+ Computed: computed,
+ Optional: true,
+ ValidateFunc: validation.StringIsNotEmpty,
+ },
+ "column": {
+ Type: schema.TypeString,
+ Computed: computed,
+ Optional: true,
+ ValidateFunc: validation.StringIsNotEmpty,
+ },
+ "member": {
+ Type: schema.TypeString,
+ Computed: computed,
+ Optional: true,
+ ValidateFunc: validation.StringIsNotEmpty,
+ Description: "The member in user:{email} format.",
+ },
+ "masking_level": {
Type: schema.TypeString,
Computed: computed,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
- string(api.ApprovalStrategyManual),
- string(api.ApprovalStrategyAutomatic),
+ v1pb.MaskingLevel_NONE.String(),
+ v1pb.MaskingLevel_PARTIAL.String(),
}, false),
},
- "deployment_type": {
+ "action": {
Type: schema.TypeString,
Computed: computed,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
- string(api.DeploymentTypeDatabaseCreate),
- string(api.DeploymentTypeDatabaseDDL),
- string(api.DeploymentTypeDatabaseDDLGhost),
- string(api.DeploymentTypeDatabaseDML),
- string(api.DeploymentTypeDatabaseRestorePITR),
- string(api.DeploymentTypeDatabaseDMLRollback),
+ v1pb.MaskingExceptionPolicy_MaskingException_QUERY.String(),
+ v1pb.MaskingExceptionPolicy_MaskingException_EXPORT.String(),
}, false),
},
+ "expire_timestamp": {
+ Type: schema.TypeString,
+ Computed: computed,
+ Optional: true,
+ Description: "The expiration timestamp in YYYY-MM-DDThh:mm:ss.000Z format",
+ },
},
},
},
@@ -260,36 +148,7 @@ func getDeploymentApprovalPolicySchema(computed bool) *schema.Schema {
}
}
-func getBackupPlanPolicySchema(computed bool) *schema.Schema {
- return &schema.Schema{
- Computed: computed,
- Optional: true,
- Default: nil,
- Type: schema.TypeList,
- Elem: &schema.Resource{
- Schema: map[string]*schema.Schema{
- "schedule": {
- Type: schema.TypeString,
- Computed: computed,
- Optional: true,
- ValidateFunc: validation.StringInSlice([]string{
- string(api.BackupPlanScheduleUnset),
- string(api.BackupPlanScheduleDaily),
- string(api.BackupPlanScheduleWeekly),
- }, false),
- },
- "retention_duration": {
- Type: schema.TypeInt,
- Computed: computed,
- Optional: true,
- Description: "The minimum allowed seconds that backup data is kept for databases in an environment.",
- },
- },
- },
- }
-}
-
-func getSensitiveDataPolicy(computed bool) *schema.Schema {
+func getMaskingPolicySchema(computed bool) *schema.Schema {
return &schema.Schema{
Computed: computed,
Optional: true,
@@ -297,9 +156,10 @@ func getSensitiveDataPolicy(computed bool) *schema.Schema {
Type: schema.TypeList,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
- "sensitive_data": {
+ "mask_data": {
Computed: computed,
Optional: true,
+ Default: nil,
Type: schema.TypeList,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
@@ -309,21 +169,35 @@ func getSensitiveDataPolicy(computed bool) *schema.Schema {
Optional: true,
},
"table": {
+ Type: schema.TypeString,
+ Computed: computed,
+ Optional: true,
+ ValidateFunc: validation.StringIsNotEmpty,
+ },
+ "column": {
+ Type: schema.TypeString,
+ Computed: computed,
+ Optional: true,
+ ValidateFunc: validation.StringIsNotEmpty,
+ },
+ "full_masking_algorithm_id": {
Type: schema.TypeString,
Computed: computed,
Optional: true,
},
- "column": {
+ "partial_masking_algorithm_id": {
Type: schema.TypeString,
Computed: computed,
Optional: true,
},
- "mask_type": {
+ "masking_level": {
Type: schema.TypeString,
Computed: computed,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
- string(api.SensitiveDataMaskTypeDefault),
+ v1pb.MaskingLevel_NONE.String(),
+ v1pb.MaskingLevel_PARTIAL.String(),
+ v1pb.MaskingLevel_FULL.String(),
}, false),
},
},
@@ -334,29 +208,130 @@ func getSensitiveDataPolicy(computed bool) *schema.Schema {
}
}
-func getAccessControlPolicy(computed bool) *schema.Schema {
- return &schema.Schema{
- Computed: computed,
- Optional: true,
- Default: nil,
- Type: schema.TypeList,
- Elem: &schema.Resource{
- Schema: map[string]*schema.Schema{
- "disallow_rules": {
- Computed: computed,
- Optional: true,
- Type: schema.TypeList,
- Elem: &schema.Resource{
- Schema: map[string]*schema.Schema{
- "all_databases": {
- Type: schema.TypeBool,
- Computed: computed,
- Optional: true,
- },
- },
- },
- },
- },
- },
+func dataSourcePolicyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ c := m.(api.Client)
+
+ policyName := fmt.Sprintf("%s/%s%s", d.Get("parent").(string), internal.PolicyNamePrefix, d.Get("type").(string))
+ policy, err := c.GetPolicy(ctx, policyName)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ d.SetId(policy.Name)
+ return setPolicyMessage(d, policy)
+}
+
+func setPolicyMessage(d *schema.ResourceData, policy *v1pb.Policy) diag.Diagnostics {
+ parent, _, 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())
+ }
+ if err := d.Set("enforce", policy.Enforce); err != nil {
+ return diag.Errorf("cannot set enforce for policy: %s", err.Error())
+ }
+
+ if p := policy.GetMaskingPolicy(); p != nil {
+ if err := d.Set("masking_policy", flattenMaskingPolicy(p)); err != nil {
+ return diag.Errorf("cannot set masking_policy: %s", err.Error())
+ }
}
+ if p := policy.GetMaskingExceptionPolicy(); p != nil {
+ exceptionPolicy, err := flattenMaskingExceptionPolicy(p)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ if err := d.Set("masking_exception_policy", exceptionPolicy); err != nil {
+ return diag.Errorf("cannot set masking_policy: %s", err.Error())
+ }
+ }
+
+ return nil
+}
+
+func flattenMaskingPolicy(p *v1pb.MaskingPolicy) []interface{} {
+ maskDataList := []interface{}{}
+ for _, maskData := range p.MaskData {
+ raw := map[string]interface{}{}
+ raw["schema"] = maskData.Schema
+ raw["table"] = maskData.Table
+ raw["column"] = maskData.Column
+ raw["full_masking_algorithm_id"] = maskData.FullMaskingAlgorithmId
+ raw["partial_masking_algorithm_id"] = maskData.PartialMaskingAlgorithmId
+ raw["masking_level"] = maskData.MaskingLevel.String()
+ maskDataList = append(maskDataList, raw)
+ }
+ policy := map[string]interface{}{
+ "mask_data": maskDataList,
+ }
+ return []interface{}{policy}
+}
+
+func flattenMaskingExceptionPolicy(p *v1pb.MaskingExceptionPolicy) ([]interface{}, error) {
+ exceptionList := []interface{}{}
+ for _, exception := range p.MaskingExceptions {
+ raw := map[string]interface{}{}
+ raw["member"] = exception.Member
+ raw["action"] = exception.Action.String()
+ raw["masking_level"] = exception.MaskingLevel.String()
+
+ expressions := strings.Split(exception.Condition.Expression, " && ")
+ instanceID := ""
+ databaseName := ""
+ for _, expression := range expressions {
+ if strings.HasPrefix(expression, "resource.instance_id == ") {
+ instanceID = strings.TrimSuffix(
+ strings.TrimPrefix(expression, `resource.instance_id == "`),
+ `"`,
+ )
+ }
+ if strings.HasPrefix(expression, "resource.database_name == ") {
+ databaseName = strings.TrimSuffix(
+ strings.TrimPrefix(expression, `resource.database_name == "`),
+ `"`,
+ )
+ }
+ if strings.HasPrefix(expression, "resource.table_name == ") {
+ raw["table"] = strings.TrimSuffix(
+ strings.TrimPrefix(expression, `resource.table_name == "`),
+ `"`,
+ )
+ }
+ if strings.HasPrefix(expression, "resource.schema_name == ") {
+ raw["schema"] = strings.TrimSuffix(
+ strings.TrimPrefix(expression, `resource.schema_name == "`),
+ `"`,
+ )
+ }
+ if strings.HasPrefix(expression, "resource.column_name == ") {
+ raw["column"] = strings.TrimSuffix(
+ strings.TrimPrefix(expression, `resource.column_name == "`),
+ `"`,
+ )
+ }
+ if strings.HasPrefix(expression, "request.time < ") {
+ raw["expire_timestamp"] = strings.TrimSuffix(
+ strings.TrimPrefix(expression, `request.time < timestamp("`),
+ `")`,
+ )
+ }
+ }
+ if instanceID == "" || databaseName == "" {
+ return nil, errors.Errorf("invalid exception policy condition: %v", exception.Condition.Expression)
+ }
+ raw["database"] = fmt.Sprintf("%s%s/%s%s", internal.InstanceNamePrefix, instanceID, internal.DatabaseIDPrefix, databaseName)
+ exceptionList = append(exceptionList, raw)
+ }
+ policy := map[string]interface{}{
+ "exceptions": exceptionList,
+ }
+ return []interface{}{policy}, nil
}
diff --git a/provider/data_source_policy_list.go b/provider/data_source_policy_list.go
index 48462f3..10bcf64 100644
--- a/provider/data_source_policy_list.go
+++ b/provider/data_source_policy_list.go
@@ -62,10 +62,8 @@ func dataSourcePolicyList() *schema.Resource {
Computed: true,
Description: "Decide if the policy is enforced.",
},
- "deployment_approval_policy": getDeploymentApprovalPolicySchema(true),
- "backup_plan_policy": getBackupPlanPolicySchema(true),
- "sensitive_data_policy": getSensitiveDataPolicy(true),
- "access_control_policy": getAccessControlPolicy(true),
+ "masking_policy": getMaskingPolicySchema(true),
+ "masking_exception_policy": getMaskingExceptionPolicySchema(true),
},
},
},
@@ -76,11 +74,7 @@ func dataSourcePolicyList() *schema.Resource {
func dataSourcePolicyListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
c := m.(api.Client)
- find := &api.PolicyFindMessage{
- Parent: d.Get("parent").(string),
- }
-
- response, err := c.ListPolicies(ctx, find)
+ response, err := c.ListPolicies(ctx, d.Get("parent").(string))
if err != nil {
return diag.FromErr(err)
}
@@ -89,25 +83,21 @@ func dataSourcePolicyListRead(ctx context.Context, d *schema.ResourceData, m int
for _, policy := range response.Policies {
raw := make(map[string]interface{})
raw["name"] = policy.Name
- raw["type"] = policy.Type
+ raw["type"] = policy.Type.String()
raw["inherit_from_parent"] = policy.InheritFromParent
raw["enforce"] = policy.Enforce
- if p := policy.DeploymentApprovalPolicy; p != nil {
- raw["deployment_approval_policy"] = flattenDeploymentApprovalPolicy(p)
+
+ if p := policy.GetMaskingPolicy(); p != nil {
+ raw["masking_policy"] = flattenMaskingPolicy(p)
}
- if p := policy.BackupPlanPolicy; p != nil {
- backupPlan, err := flattenBackupPlanPolicy(p)
+ if p := policy.GetMaskingExceptionPolicy(); p != nil {
+ exceptionPolicy, err := flattenMaskingExceptionPolicy(p)
if err != nil {
return diag.FromErr(err)
}
- raw["backup_plan_policy"] = backupPlan
- }
- if p := policy.SensitiveDataPolicy; p != nil {
- raw["sensitive_data_policy"] = flattenSensitiveDataPolicy(p)
- }
- if p := policy.AccessControlPolicy; p != nil {
- raw["access_control_policy"] = flattenAccessControlPolicy(p)
+ raw["masking_exception_policy"] = exceptionPolicy
}
+
policies = append(policies, raw)
}
diff --git a/provider/data_source_policy_list_test.go b/provider/data_source_policy_list_test.go
index 0a089d9..7dfb26b 100644
--- a/provider/data_source_policy_list_test.go
+++ b/provider/data_source_policy_list_test.go
@@ -5,7 +5,8 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
- "github.com/bytebase/terraform-provider-bytebase/api"
+ v1pb "github.com/bytebase/bytebase/proto/generated-go/v1"
+
"github.com/bytebase/terraform-provider-bytebase/provider/internal"
)
@@ -27,12 +28,12 @@ func TestAccPolicyListDataSource(t *testing.T) {
),
internal.GetTestStepForDataSourceList(
testAccCheckPolicyResource(
- "backup_plan",
- "environments/test",
- getBackupPlanPolicy(string(api.BackupPlanScheduleDaily), 999),
- api.PolicyTypeBackupPlan,
+ "masking_policy",
+ "instances/test-sample-instance/databases/employee",
+ getMaskingPolicy("salary", "amount", v1pb.MaskingLevel_FULL),
+ v1pb.PolicyType_MASKING,
),
- "bytebase_policy.backup_plan",
+ "bytebase_policy.masking_policy",
"bytebase_policy_list",
"after",
"policies",
diff --git a/provider/data_source_policy_test.go b/provider/data_source_policy_test.go
index afa3461..06dedbb 100644
--- a/provider/data_source_policy_test.go
+++ b/provider/data_source_policy_test.go
@@ -7,7 +7,8 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
- "github.com/bytebase/terraform-provider-bytebase/api"
+ v1pb "github.com/bytebase/bytebase/proto/generated-go/v1"
+
"github.com/bytebase/terraform-provider-bytebase/provider/internal"
)
@@ -22,22 +23,24 @@ func TestAccPolicyDataSource(t *testing.T) {
{
Config: testAccCheckPolicyDataSource(
testAccCheckPolicyResource(
- "backup_plan",
- "environments/test",
- getBackupPlanPolicy(string(api.BackupPlanScheduleDaily), 999),
- api.PolicyTypeBackupPlan,
+ "masking_policy",
+ "instances/test-sample-instance/databases/employee",
+ getMaskingPolicy("salary", "amount", v1pb.MaskingLevel_FULL),
+ v1pb.PolicyType_MASKING,
),
- "backup_plan",
- "environments/test",
- "bytebase_policy.backup_plan",
- api.PolicyTypeBackupPlan,
+ "masking_policy",
+ "instances/test-sample-instance/databases/employee",
+ "bytebase_policy.masking_policy",
+ v1pb.PolicyType_MASKING,
),
Check: resource.ComposeTestCheckFunc(
- internal.TestCheckResourceExists("data.bytebase_policy.backup_plan"),
- resource.TestCheckResourceAttr("data.bytebase_policy.backup_plan", "type", string(api.PolicyTypeBackupPlan)),
- resource.TestCheckResourceAttr("data.bytebase_policy.backup_plan", "backup_plan_policy.#", "1"),
- resource.TestCheckResourceAttr("data.bytebase_policy.backup_plan", "backup_plan_policy.0.schedule", string(api.BackupPlanScheduleDaily)),
- resource.TestCheckResourceAttr("data.bytebase_policy.backup_plan", "backup_plan_policy.0.retention_duration", "999"),
+ internal.TestCheckResourceExists("data.bytebase_policy.masking_policy"),
+ resource.TestCheckResourceAttr("data.bytebase_policy.masking_policy", "type", v1pb.PolicyType_MASKING.String()),
+ resource.TestCheckResourceAttr("data.bytebase_policy.masking_policy", "masking_policy.#", "1"),
+ resource.TestCheckResourceAttr("data.bytebase_policy.masking_policy", "masking_policy.0.mask_data.#", "1"),
+ resource.TestCheckResourceAttr("data.bytebase_policy.masking_policy", "masking_policy.0.mask_data.0.table", "salary"),
+ resource.TestCheckResourceAttr("data.bytebase_policy.masking_policy", "masking_policy.0.mask_data.0.column", "amount"),
+ resource.TestCheckResourceAttr("data.bytebase_policy.masking_policy", "masking_policy.0.mask_data.0.masking_level", v1pb.MaskingLevel_FULL.String()),
),
},
},
@@ -56,11 +59,11 @@ func TestAccPolicyDataSource_NotFound(t *testing.T) {
Config: testAccCheckPolicyDataSource(
"",
"policy",
- "environments/test",
+ "instances/test-sample-instance/databases/employee",
"",
- api.PolicyTypeDeploymentApproval,
+ v1pb.PolicyType_MASKING,
),
- ExpectError: regexp.MustCompile("Cannot found policy environments/test/policies/DEPLOYMENT_APPROVAL"),
+ ExpectError: regexp.MustCompile("Cannot found policy instances/test-sample-instance/databases/employee/policies/MASKING"),
},
},
})
@@ -71,7 +74,7 @@ func testAccCheckPolicyDataSource(
identifier,
parent,
dependsOn string,
- pType api.PolicyType) string {
+ pType v1pb.PolicyType) string {
return fmt.Sprintf(`
%s
@@ -82,5 +85,5 @@ func testAccCheckPolicyDataSource(
%s
]
}
- `, resource, identifier, parent, pType, dependsOn)
+ `, resource, identifier, parent, pType.String(), dependsOn)
}
diff --git a/provider/data_source_project.go b/provider/data_source_project.go
index 7a992b7..5a4f484 100644
--- a/provider/data_source_project.go
+++ b/provider/data_source_project.go
@@ -3,6 +3,7 @@ package provider
import (
"context"
"fmt"
+ "time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -138,7 +139,7 @@ func setProjectWithDatabases(d *schema.ResourceData, project *v1pb.Project, data
db["name"] = database.Name
db["environment"] = database.Environment
db["sync_state"] = database.SyncState.String()
- db["successful_sync_time"] = database.SuccessfulSyncTime
+ db["successful_sync_time"] = database.SuccessfulSyncTime.AsTime().UTC().Format(time.RFC3339)
db["schema_version"] = database.SchemaVersion
db["labels"] = database.Labels
dbList = append(dbList, db)
diff --git a/provider/data_source_project_list.go b/provider/data_source_project_list.go
index 025d165..6a7c5de 100644
--- a/provider/data_source_project_list.go
+++ b/provider/data_source_project_list.go
@@ -138,7 +138,7 @@ func dataSourceProjectListRead(ctx context.Context, d *schema.ResourceData, m in
db["name"] = database.Name
db["environment"] = database.Environment
db["sync_state"] = database.SyncState.String()
- db["successful_sync_time"] = database.SuccessfulSyncTime
+ db["successful_sync_time"] = database.SuccessfulSyncTime.AsTime().UTC().Format(time.RFC3339)
db["schema_version"] = database.SchemaVersion
db["labels"] = database.Labels
dbList = append(dbList, db)
diff --git a/provider/data_source_setting.go b/provider/data_source_setting.go
index 6f16844..5b6a78a 100644
--- a/provider/data_source_setting.go
+++ b/provider/data_source_setting.go
@@ -27,9 +27,51 @@ func dataSourceSetting() *schema.Resource {
Required: true,
ValidateFunc: validation.StringInSlice([]string{
string(api.SettingWorkspaceApproval),
+ string(api.SettingWorkspaceExternalApproval),
}, false),
},
- "approval_flow": getWorkspaceApprovalSetting(true),
+ "approval_flow": getWorkspaceApprovalSetting(true),
+ "external_approval_nodes": getExternalApprovalSetting(true),
+ },
+ }
+}
+
+func getExternalApprovalSetting(computed bool) *schema.Schema {
+ return &schema.Schema{
+ Computed: computed,
+ Optional: true,
+ Default: nil,
+ Type: schema.TypeList,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "nodes": {
+ Type: schema.TypeList,
+ Computed: computed,
+ Required: !computed,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "id": {
+ Type: schema.TypeString,
+ Computed: computed,
+ Required: !computed,
+ Description: "The unique external node id.",
+ },
+ "title": {
+ Type: schema.TypeString,
+ Computed: computed,
+ Required: !computed,
+ Description: "The external node title.",
+ },
+ "endpoint": {
+ Type: schema.TypeString,
+ Computed: computed,
+ Required: !computed,
+ Description: "The endpoint URL to receive the approval message. Learn more: https://www.bytebase.com/docs/api/external-approval",
+ },
+ },
+ },
+ },
+ },
},
}
}
@@ -45,19 +87,19 @@ func getWorkspaceApprovalSetting(computed bool) *schema.Schema {
"rules": {
Type: schema.TypeList,
Computed: computed,
- Optional: true,
+ Required: !computed,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"flow": {
Computed: computed,
- Optional: true,
+ Required: !computed,
Type: schema.TypeList,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"title": {
Type: schema.TypeString,
Computed: computed,
- Optional: true,
+ Required: !computed,
},
"description": {
Type: schema.TypeString,
@@ -65,14 +107,16 @@ func getWorkspaceApprovalSetting(computed bool) *schema.Schema {
Optional: true,
},
"creator": {
- Type: schema.TypeString,
- Computed: computed,
- Optional: true,
+ Type: schema.TypeString,
+ Computed: computed,
+ Required: !computed,
+ Description: "The creator name in users/{email} format",
},
"steps": {
- Type: schema.TypeList,
- Computed: computed,
- Optional: true,
+ Type: schema.TypeList,
+ Computed: computed,
+ Required: !computed,
+ Description: "Approval flow following the step order.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
@@ -86,11 +130,10 @@ func getWorkspaceApprovalSetting(computed bool) *schema.Schema {
}, false),
},
"node": {
- Optional: true,
+ Required: !computed,
Default: nil,
Computed: computed,
Type: schema.TypeString,
- // TODO(ed): consider add validate
},
},
},
@@ -99,9 +142,10 @@ func getWorkspaceApprovalSetting(computed bool) *schema.Schema {
},
},
"conditions": {
- Computed: computed,
- Type: schema.TypeList,
- Optional: true,
+ Computed: computed,
+ Type: schema.TypeList,
+ Optional: true,
+ Description: "Match any condition will trigger this approval flow.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"source": {
@@ -162,6 +206,12 @@ 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.GetExternalApprovalSettingValue(); value != nil {
+ settingVal := flattenExternalApprovalSetting(value)
+ if err := d.Set("external_approval_nodes", settingVal); err != nil {
+ return diag.Errorf("cannot set external_approval_nodes: %s", err.Error())
+ }
+ }
return nil
}
@@ -285,3 +335,19 @@ func flattenWorkspaceApprovalSetting(ctx context.Context, client api.Client, set
}
return []interface{}{approvalSetting}, nil
}
+
+func flattenExternalApprovalSetting(setting *v1pb.ExternalApprovalSetting) []interface{} {
+ nodeList := []interface{}{}
+ for _, node := range setting.Nodes {
+ rawNode := map[string]interface{}{}
+ rawNode["id"] = node.Id
+ rawNode["title"] = node.Title
+ rawNode["endpoint"] = node.Endpoint
+ nodeList = append(nodeList, rawNode)
+ }
+
+ approvalSetting := map[string]interface{}{
+ "nodes": nodeList,
+ }
+ return []interface{}{approvalSetting}
+}
diff --git a/provider/internal/mock_client.go b/provider/internal/mock_client.go
index e31f289..4cf78d3 100644
--- a/provider/internal/mock_client.go
+++ b/provider/internal/mock_client.go
@@ -16,7 +16,7 @@ import (
var environmentMap map[string]*v1pb.Environment
var instanceMap map[string]*v1pb.Instance
-var policyMap map[string]*api.PolicyMessage
+var policyMap map[string]*v1pb.Policy
var projectMap map[string]*v1pb.Project
var databaseMap map[string]*v1pb.Database
var settingMap map[string]*v1pb.Setting
@@ -24,7 +24,7 @@ var settingMap map[string]*v1pb.Setting
func init() {
environmentMap = map[string]*v1pb.Environment{}
instanceMap = map[string]*v1pb.Instance{}
- policyMap = map[string]*api.PolicyMessage{}
+ policyMap = map[string]*v1pb.Policy{}
projectMap = map[string]*v1pb.Project{}
databaseMap = map[string]*v1pb.Database{}
settingMap = map[string]*v1pb.Setting{}
@@ -33,7 +33,7 @@ func init() {
type mockClient struct {
environmentMap map[string]*v1pb.Environment
instanceMap map[string]*v1pb.Instance
- policyMap map[string]*api.PolicyMessage
+ policyMap map[string]*v1pb.Policy
projectMap map[string]*v1pb.Project
databaseMap map[string]*v1pb.Database
settingMap map[string]*v1pb.Setting
@@ -254,21 +254,21 @@ func (*mockClient) SyncInstanceSchema(_ context.Context, _ string) error {
}
// ListPolicies lists policies in a specific resource.
-func (c *mockClient) ListPolicies(_ context.Context, find *api.PolicyFindMessage) (*api.ListPolicyMessage, error) {
- policies := make([]*api.PolicyMessage, 0)
+func (c *mockClient) ListPolicies(_ context.Context, parent string) (*v1pb.ListPoliciesResponse, error) {
+ policies := make([]*v1pb.Policy, 0)
for _, policy := range c.policyMap {
- if find.Parent == "" || strings.HasPrefix(policy.Name, find.Parent) {
+ if parent == "" || strings.HasPrefix(policy.Name, parent) {
policies = append(policies, policy)
}
}
- return &api.ListPolicyMessage{
+ return &v1pb.ListPoliciesResponse{
Policies: policies,
}, nil
}
// GetPolicy gets a policy in a specific resource.
-func (c *mockClient) GetPolicy(_ context.Context, policyName string) (*api.PolicyMessage, error) {
+func (c *mockClient) GetPolicy(_ context.Context, policyName string) (*v1pb.Policy, error) {
policy, ok := c.policyMap[policyName]
if !ok {
return nil, errors.Errorf("Cannot found policy %s", policyName)
@@ -278,7 +278,7 @@ func (c *mockClient) GetPolicy(_ context.Context, policyName string) (*api.Polic
}
// UpsertPolicy creates or updates the policy.
-func (c *mockClient) UpsertPolicy(_ context.Context, patch *api.PolicyPatchMessage) (*api.PolicyMessage, error) {
+func (c *mockClient) UpsertPolicy(_ context.Context, patch *v1pb.Policy, updateMasks []string) (*v1pb.Policy, error) {
_, policyType, err := GetPolicyParentAndType(patch.Name)
if err != nil {
return nil, err
@@ -287,7 +287,7 @@ func (c *mockClient) UpsertPolicy(_ context.Context, patch *api.PolicyPatchMessa
policy, existed := c.policyMap[patch.Name]
if !existed {
- policy = &api.PolicyMessage{
+ policy = &v1pb.Policy{
Name: patch.Name,
Type: policyType,
Enforce: true,
@@ -295,48 +295,37 @@ func (c *mockClient) UpsertPolicy(_ context.Context, patch *api.PolicyPatchMessa
}
switch policyType {
- case api.PolicyTypeAccessControl:
+ case v1pb.PolicyType_MASKING:
if !existed {
- if patch.AccessControlPolicy == nil {
+ if patch.GetMaskingPolicy() == nil {
return nil, errors.Errorf("payload is required to create the policy")
}
}
- if v := patch.AccessControlPolicy; v != nil {
- policy.AccessControlPolicy = v
- }
- case api.PolicyTypeBackupPlan:
- if !existed {
- if patch.BackupPlanPolicy == nil {
- return nil, errors.Errorf("payload is required to create the policy")
+ if v := patch.GetMaskingPolicy(); v != nil {
+ policy.Policy = &v1pb.Policy_MaskingPolicy{
+ MaskingPolicy: v,
}
}
- if v := patch.BackupPlanPolicy; v != nil {
- policy.BackupPlanPolicy = v
- }
- case api.PolicyTypeDeploymentApproval:
+ case v1pb.PolicyType_MASKING_EXCEPTION:
if !existed {
- if patch.DeploymentApprovalPolicy == nil {
+ if patch.GetMaskingExceptionPolicy() == nil {
return nil, errors.Errorf("payload is required to create the policy")
}
}
- if v := patch.DeploymentApprovalPolicy; v != nil {
- policy.DeploymentApprovalPolicy = v
- }
- case api.PolicyTypeSensitiveData:
- if !existed {
- if patch.SensitiveDataPolicy == nil {
- return nil, errors.Errorf("payload is required to create the policy")
+ if v := patch.GetMaskingExceptionPolicy(); v != nil {
+ policy.Policy = &v1pb.Policy_MaskingExceptionPolicy{
+ MaskingExceptionPolicy: v,
}
}
- if v := patch.SensitiveDataPolicy; v != nil {
- policy.SensitiveDataPolicy = v
- }
default:
return nil, errors.Errorf("invalid policy type %v", policyType)
}
- if v := patch.InheritFromParent; v != nil {
- policy.InheritFromParent = *v
+ if slices.Contains(updateMasks, "inherit_from_parent") {
+ policy.InheritFromParent = patch.InheritFromParent
+ }
+ if slices.Contains(updateMasks, "enforce") {
+ policy.Enforce = patch.Enforce
}
c.policyMap[policy.Name] = policy
diff --git a/provider/internal/utils.go b/provider/internal/utils.go
index a4068cd..8a8a435 100644
--- a/provider/internal/utils.go
+++ b/provider/internal/utils.go
@@ -5,13 +5,12 @@ import (
"regexp"
"strings"
+ v1pb "github.com/bytebase/bytebase/proto/generated-go/v1"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/pkg/errors"
-
- "github.com/bytebase/terraform-provider-bytebase/api"
)
const (
@@ -69,14 +68,18 @@ func ResourceNameValidation(regexs ...*regexp.Regexp) schema.SchemaValidateDiagF
}
// GetPolicyParentAndType returns the policy parent and type by the name.
-func GetPolicyParentAndType(name string) (string, api.PolicyType, error) {
+func GetPolicyParentAndType(name string) (string, v1pb.PolicyType, error) {
names := strings.Split(name, PolicyNamePrefix)
if len(names) != 2 {
- return "", "", errors.Errorf("invalid policy name %s", name)
+ return "", v1pb.PolicyType_POLICY_TYPE_UNSPECIFIED, errors.Errorf("invalid policy name %s", name)
+ }
+ policyType := strings.ToUpper(names[1])
+ pType, ok := v1pb.PolicyType_value[policyType]
+ if !ok {
+ return "", v1pb.PolicyType_POLICY_TYPE_UNSPECIFIED, errors.Errorf("invalid policy name %s", name)
}
- policyType := api.PolicyType(strings.ToUpper(names[1]))
- return strings.TrimSuffix(names[0], "/"), policyType, nil
+ return strings.TrimSuffix(names[0], "/"), v1pb.PolicyType(pType), nil
}
// GetEnvironmentID will parse the environment resource id.
diff --git a/provider/resource_policy.go b/provider/resource_policy.go
index 992bc3d..18ccffa 100644
--- a/provider/resource_policy.go
+++ b/provider/resource_policy.go
@@ -10,6 +10,9 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/pkg/errors"
+ "google.golang.org/genproto/googleapis/type/expr"
+
+ v1pb "github.com/bytebase/bytebase/proto/generated-go/v1"
"github.com/bytebase/terraform-provider-bytebase/api"
"github.com/bytebase/terraform-provider-bytebase/provider/internal"
@@ -39,7 +42,7 @@ func resourcePolicy() *schema.Resource {
// project policy
regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.ProjectNamePrefix, internal.ResourceIDPattern)),
// database policy
- regexp.MustCompile(fmt.Sprintf("^%s%s%s%s$", internal.InstanceNamePrefix, internal.ResourceIDPattern, internal.DatabaseIDPrefix, internal.ResourceIDPattern)),
+ regexp.MustCompile(fmt.Sprintf("^%s%s/%s%s$", internal.InstanceNamePrefix, internal.ResourceIDPattern, internal.DatabaseIDPrefix, internal.ResourceIDPattern)),
),
Description: "The policy parent name for the policy, support projects/{resource id}, environments/{resource id}, instances/{resource id}, or instances/{resource id}/databases/{database name}",
},
@@ -47,10 +50,8 @@ func resourcePolicy() *schema.Resource {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
- string(api.PolicyTypeDeploymentApproval),
- string(api.PolicyTypeBackupPlan),
- string(api.PolicyTypeSensitiveData),
- string(api.PolicyTypeAccessControl),
+ v1pb.PolicyType_MASKING.String(),
+ v1pb.PolicyType_MASKING_EXCEPTION.String(),
}, false),
Description: "The policy type.",
},
@@ -71,10 +72,8 @@ func resourcePolicy() *schema.Resource {
Default: false,
Description: "Decide if the policy should inherit from the parent.",
},
- "deployment_approval_policy": getDeploymentApprovalPolicySchema(false),
- "backup_plan_policy": getBackupPlanPolicySchema(false),
- "sensitive_data_policy": getSensitiveDataPolicy(false),
- "access_control_policy": getAccessControlPolicy(false),
+ "masking_policy": getMaskingPolicySchema(false),
+ "masking_exception_policy": getMaskingExceptionPolicySchema(false),
},
}
}
@@ -113,52 +112,39 @@ func resourcePolicyCreate(ctx context.Context, d *schema.ResourceData, m interfa
inheritFromParent := d.Get("inherit_from_parent").(bool)
enforce := d.Get("enforce").(bool)
- patch := &api.PolicyPatchMessage{
+ _, policyType, err := internal.GetPolicyParentAndType(policyName)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ patch := &v1pb.Policy{
Name: policyName,
- InheritFromParent: &inheritFromParent,
- Enforce: &enforce,
+ InheritFromParent: inheritFromParent,
+ Enforce: enforce,
+ Type: policyType,
}
- policyType := api.PolicyType(strings.ToUpper(d.Get("type").(string)))
switch policyType {
- case api.PolicyTypeDeploymentApproval:
- if _, ok := d.GetOk("deployment_approval_policy"); ok {
- policy, err := convertDeploymentApprovalPolicy(d)
- if err != nil {
- return diag.FromErr(err)
- }
- patch.DeploymentApprovalPolicy = policy
- }
- case api.PolicyTypeBackupPlan:
- if _, ok := d.GetOk("backup_plan_policy"); ok {
- policy, err := convertBackupPlanPolicy(d)
- if err != nil {
- return diag.FromErr(err)
- }
- patch.BackupPlanPolicy = policy
+ case v1pb.PolicyType_MASKING:
+ maskingPolicy, err := convertToMaskingPolicy(d)
+ if err != nil {
+ return diag.FromErr(err)
}
- case api.PolicyTypeSensitiveData:
- if _, ok := d.GetOk("sensitive_data_policy"); ok {
- policy, err := convertSensitiveDataPolicy(d)
- if err != nil {
- return diag.FromErr(err)
- }
- patch.SensitiveDataPolicy = policy
+ patch.Policy = &v1pb.Policy_MaskingPolicy{
+ MaskingPolicy: maskingPolicy,
}
- case api.PolicyTypeAccessControl:
- policy, err := convertAccessControlPolicy(d)
+ case v1pb.PolicyType_MASKING_EXCEPTION:
+ maskingExceptionPolicy, err := convertToMaskingExceptionPolicy(d)
if err != nil {
return diag.FromErr(err)
}
- patch.AccessControlPolicy = policy
- }
-
- if err := validatePolicy(policyType, patch); err != nil {
- return diag.FromErr(err)
+ patch.Policy = &v1pb.Policy_MaskingExceptionPolicy{
+ MaskingExceptionPolicy: maskingExceptionPolicy,
+ }
}
var diags diag.Diagnostics
- p, err := c.UpsertPolicy(ctx, patch)
+ p, err := c.UpsertPolicy(ctx, patch, []string{"inherit_from_parent", "enforce", "payload"})
if err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
@@ -181,56 +167,49 @@ 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)
+ }
- patch := &api.PolicyPatchMessage{
- Name: policyName,
+ patch := &v1pb.Policy{
+ Name: policyName,
+ InheritFromParent: d.Get("inherit_from_parent").(bool),
+ Enforce: d.Get("enforce").(bool),
+ Type: policyType,
}
+ updateMasks := []string{}
if d.HasChange("inherit_from_parent") {
- v := d.Get("inherit_from_parent").(bool)
- patch.InheritFromParent = &v
+ updateMasks = append(updateMasks, "inherit_from_parent")
}
if d.HasChange("enforce") {
- v := d.Get("enforce").(bool)
- patch.Enforce = &v
+ updateMasks = append(updateMasks, "enforce")
}
- if d.HasChange("deployment_approval_policy") {
- policy, err := convertDeploymentApprovalPolicy(d)
+ if d.HasChange("masking_policy") {
+ updateMasks = append(updateMasks, "payload")
+ maskingPolicy, err := convertToMaskingPolicy(d)
if err != nil {
return diag.FromErr(err)
}
- patch.DeploymentApprovalPolicy = policy
- }
- if d.HasChange("backup_plan_policy") {
- policy, err := convertBackupPlanPolicy(d)
- if err != nil {
- return diag.FromErr(err)
+ patch.Policy = &v1pb.Policy_MaskingPolicy{
+ MaskingPolicy: maskingPolicy,
}
- patch.BackupPlanPolicy = policy
}
- if d.HasChange("sensitive_data_policy") {
- policy, err := convertSensitiveDataPolicy(d)
+ if d.HasChange("masking_exception_policy") {
+ updateMasks = append(updateMasks, "payload")
+ maskingExceptionPolicy, err := convertToMaskingExceptionPolicy(d)
if err != nil {
return diag.FromErr(err)
}
- patch.SensitiveDataPolicy = policy
- }
- if d.HasChange("access_control_policy") {
- policy, err := convertAccessControlPolicy(d)
- if err != nil {
- return diag.FromErr(err)
+ patch.Policy = &v1pb.Policy_MaskingExceptionPolicy{
+ MaskingExceptionPolicy: maskingExceptionPolicy,
}
- patch.AccessControlPolicy = policy
- }
-
- policyType := api.PolicyType(strings.ToUpper(d.Get("type").(string)))
- if err := validatePolicy(policyType, patch); err != nil {
- return diag.FromErr(err)
}
var diags diag.Diagnostics
- if _, err := c.UpsertPolicy(ctx, patch); err != nil {
+ if _, err := c.UpsertPolicy(ctx, patch, updateMasks); err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Failed to upsert policy",
@@ -247,119 +226,86 @@ func resourcePolicyUpdate(ctx context.Context, d *schema.ResourceData, m interfa
return diags
}
-func convertDeploymentApprovalPolicy(d *schema.ResourceData) (*api.DeploymentApprovalPolicy, error) {
- rawList, ok := d.Get("deployment_approval_policy").([]interface{})
- if !ok || len(rawList) != 1 {
- return nil, errors.Errorf("invalid deployment_approval_policy")
- }
-
- raw := rawList[0].(map[string]interface{})
- strategies := raw["deployment_approval_strategies"].([]interface{})
-
- policy := &api.DeploymentApprovalPolicy{
- DefaultStrategy: api.ApprovalStrategy(raw["default_strategy"].(string)),
- }
-
- for _, strategy := range strategies {
- rawStrategy := strategy.(map[string]interface{})
- policy.DeploymentApprovalStrategies = append(policy.DeploymentApprovalStrategies, &api.DeploymentApprovalStrategy{
- ApprovalGroup: api.ApprovalGroup(rawStrategy["approval_group"].(string)),
- ApprovalStrategy: api.ApprovalStrategy(rawStrategy["approval_strategy"].(string)),
- DeploymentType: api.DeploymentType(rawStrategy["deployment_type"].(string)),
- })
- }
- return policy, nil
+func convertToMaskingLevel(level string) v1pb.MaskingLevel {
+ return v1pb.MaskingLevel(v1pb.MaskingLevel_value[level])
}
-func convertBackupPlanPolicy(d *schema.ResourceData) (*api.BackupPlanPolicy, error) {
- rawList, ok := d.Get("backup_plan_policy").([]interface{})
+func convertToMaskingExceptionPolicy(d *schema.ResourceData) (*v1pb.MaskingExceptionPolicy, error) {
+ rawList, ok := d.Get("masking_exception_policy").([]interface{})
if !ok || len(rawList) != 1 {
- return nil, errors.Errorf("invalid backup_plan_policy")
+ return nil, errors.Errorf("invalid masking_exception_policy")
}
raw := rawList[0].(map[string]interface{})
- return &api.BackupPlanPolicy{
- Schedule: api.BackupPlanSchedule(raw["schedule"].(string)),
- RetentionDuration: fmt.Sprintf("%ds", raw["retention_duration"].(int)),
- }, nil
-}
+ exceptionList := raw["exceptions"].([]interface{})
-func convertSensitiveDataPolicy(d *schema.ResourceData) (*api.SensitiveDataPolicy, error) {
- rawList, ok := d.Get("sensitive_data_policy").([]interface{})
- if !ok || len(rawList) != 1 {
- return nil, errors.Errorf("invalid sensitive_data_policy")
- }
+ policy := &v1pb.MaskingExceptionPolicy{}
- raw := rawList[0].(map[string]interface{})
- dataList := raw["sensitive_data"].([]interface{})
- policy := &api.SensitiveDataPolicy{}
-
- for _, data := range dataList {
- rawData := data.(map[string]interface{})
- policy.SensitiveData = append(policy.SensitiveData, &api.SensitiveData{
- Schema: rawData["schema"].(string),
- Table: rawData["table"].(string),
- Column: rawData["column"].(string),
- MaskType: api.SensitiveDataMaskType(rawData["mask_type"].(string)),
+ for _, exception := range exceptionList {
+ rawException := exception.(map[string]interface{})
+
+ databaseFullName := rawException["database"].(string)
+ instanceID, databaseName, err := internal.GetInstanceDatabaseID(databaseFullName)
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid database full name: %v", databaseFullName)
+ }
+
+ expressions := []string{
+ 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 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 != "" {
+ expressions = append(expressions, fmt.Sprintf(`request.time < timestamp("%s")`, expire))
+ }
+ member := rawException["member"].(string)
+ if !strings.HasPrefix(member, "user:") {
+ return nil, errors.Errorf("member should in user:{email} format")
+ }
+ policy.MaskingExceptions = append(policy.MaskingExceptions, &v1pb.MaskingExceptionPolicy_MaskingException{
+ Member: rawException["member"].(string),
+ Action: v1pb.MaskingExceptionPolicy_MaskingException_Action(
+ v1pb.MaskingExceptionPolicy_MaskingException_Action_value[rawException["action"].(string)],
+ ),
+ MaskingLevel: convertToMaskingLevel(rawException["masking_level"].(string)),
+ Condition: &expr.Expr{
+ Expression: strings.Join(expressions, " && "),
+ },
})
}
-
return policy, nil
}
-func convertAccessControlPolicy(d *schema.ResourceData) (*api.AccessControlPolicy, error) {
- rawList, ok := d.Get("access_control_policy").([]interface{})
- if !ok || len(rawList) > 1 {
- return nil, errors.Errorf("invalid access_control_policy")
- }
-
- if len(rawList) == 0 {
- if _, ok := d.GetOk("database"); !ok {
- return nil, errors.Errorf("access_control_policy is required")
- }
-
- return &api.AccessControlPolicy{
- DisallowRules: []*api.AccessControlRule{
- {
- FullDatabase: false,
- },
- },
- }, nil
+func convertToMaskingPolicy(d *schema.ResourceData) (*v1pb.MaskingPolicy, error) {
+ rawList, ok := d.Get("masking_policy").([]interface{})
+ if !ok || len(rawList) != 1 {
+ return nil, errors.Errorf("invalid masking_policy")
}
raw := rawList[0].(map[string]interface{})
- rules := raw["disallow_rules"].([]interface{})
- policy := &api.AccessControlPolicy{}
-
- for _, rule := range rules {
- rawRule := rule.(map[string]interface{})
- policy.DisallowRules = append(policy.DisallowRules, &api.AccessControlRule{
- FullDatabase: rawRule["all_databases"].(bool),
+ rawMaskList := raw["mask_data"].([]interface{})
+
+ policy := &v1pb.MaskingPolicy{}
+
+ for _, maskData := range rawMaskList {
+ rawMask := maskData.(map[string]interface{})
+ policy.MaskData = append(policy.MaskData, &v1pb.MaskData{
+ Schema: rawMask["schema"].(string),
+ Table: rawMask["table"].(string),
+ Column: rawMask["column"].(string),
+ FullMaskingAlgorithmId: rawMask["full_masking_algorithm_id"].(string),
+ PartialMaskingAlgorithmId: rawMask["partial_masking_algorithm_id"].(string),
+ MaskingLevel: convertToMaskingLevel(rawMask["masking_level"].(string)),
})
}
return policy, nil
}
-
-func validatePolicy(policyType api.PolicyType, patch *api.PolicyPatchMessage) error {
- switch policyType {
- case api.PolicyTypeDeploymentApproval:
- if patch.DeploymentApprovalPolicy == nil {
- return errors.Errorf("must set deployment_approval_policy for %v policy", policyType)
- }
- case api.PolicyTypeBackupPlan:
- if patch.BackupPlanPolicy == nil {
- return errors.Errorf("must set backup_plan_policy for %v policy", policyType)
- }
- case api.PolicyTypeSensitiveData:
- if patch.SensitiveDataPolicy == nil {
- return errors.Errorf("must set sensitive_data_policy for %v policy", policyType)
- }
- case api.PolicyTypeAccessControl:
- if patch.AccessControlPolicy == nil {
- return errors.Errorf("must set access_control_policy for %v policy", policyType)
- }
- }
-
- return nil
-}
diff --git a/provider/resource_policy_test.go b/provider/resource_policy_test.go
index b2aa4d2..e78d951 100644
--- a/provider/resource_policy_test.go
+++ b/provider/resource_policy_test.go
@@ -3,10 +3,9 @@ package provider
import (
"context"
"fmt"
- "regexp"
- "strings"
"testing"
+ v1pb "github.com/bytebase/bytebase/proto/generated-go/v1"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/pkg/errors"
@@ -25,108 +24,43 @@ func TestAccPolicy(t *testing.T) {
Steps: []resource.TestStep{
{
Config: testAccCheckPolicyResource(
- "backup_plan",
- "environments/test",
- getBackupPlanPolicy(string(api.BackupPlanScheduleDaily), 999),
- api.PolicyTypeBackupPlan,
+ "masking_policy",
+ "instances/test-sample-instance/databases/employee",
+ getMaskingPolicy("salary", "amount", v1pb.MaskingLevel_FULL),
+ v1pb.PolicyType_MASKING,
),
Check: resource.ComposeTestCheckFunc(
- internal.TestCheckResourceExists("bytebase_policy.backup_plan"),
- resource.TestCheckResourceAttr("bytebase_policy.backup_plan", "type", string(api.PolicyTypeBackupPlan)),
- resource.TestCheckResourceAttr("bytebase_policy.backup_plan", "backup_plan_policy.#", "1"),
- resource.TestCheckResourceAttr("bytebase_policy.backup_plan", "backup_plan_policy.0.schedule", string(api.BackupPlanScheduleDaily)),
- resource.TestCheckResourceAttr("bytebase_policy.backup_plan", "backup_plan_policy.0.retention_duration", "999"),
+ internal.TestCheckResourceExists("bytebase_policy.masking_policy"),
+ resource.TestCheckResourceAttr("bytebase_policy.masking_policy", "type", v1pb.PolicyType_MASKING.String()),
+ resource.TestCheckResourceAttr("bytebase_policy.masking_policy", "masking_policy.#", "1"),
+ resource.TestCheckResourceAttr("bytebase_policy.masking_policy", "masking_policy.0.mask_data.#", "1"),
+ resource.TestCheckResourceAttr("bytebase_policy.masking_policy", "masking_policy.0.mask_data.0.table", "salary"),
+ resource.TestCheckResourceAttr("bytebase_policy.masking_policy", "masking_policy.0.mask_data.0.column", "amount"),
+ resource.TestCheckResourceAttr("bytebase_policy.masking_policy", "masking_policy.0.mask_data.0.masking_level", v1pb.MaskingLevel_FULL.String()),
),
},
{
Config: testAccCheckPolicyResource(
- "backup_plan",
- "environments/test",
- getBackupPlanPolicy(string(api.BackupPlanScheduleWeekly), 99),
- api.PolicyTypeBackupPlan,
+ "masking_exception_policy",
+ "projects/project-sample",
+ getMaskingExceptionPolicy("instances/test-sample-instance/databases/employee", "salary", "amount", v1pb.MaskingLevel_PARTIAL),
+ v1pb.PolicyType_MASKING_EXCEPTION,
),
Check: resource.ComposeTestCheckFunc(
- internal.TestCheckResourceExists("bytebase_policy.backup_plan"),
- resource.TestCheckResourceAttr("bytebase_policy.backup_plan", "type", string(api.PolicyTypeBackupPlan)),
- resource.TestCheckResourceAttr("bytebase_policy.backup_plan", "backup_plan_policy.#", "1"),
- resource.TestCheckResourceAttr("bytebase_policy.backup_plan", "backup_plan_policy.0.schedule", string(api.BackupPlanScheduleWeekly)),
- resource.TestCheckResourceAttr("bytebase_policy.backup_plan", "backup_plan_policy.0.retention_duration", "99"),
- ),
- },
- {
- Config: testAccCheckPolicyResource(
- "deployment_approval",
- "environments/test",
- getDeploymentApprovalPolicy(string(api.ApprovalStrategyAutomatic), []*api.DeploymentApprovalStrategy{
- {
- ApprovalGroup: api.ApprovalGroupDBA,
- ApprovalStrategy: api.ApprovalStrategyAutomatic,
- DeploymentType: api.DeploymentTypeDatabaseCreate,
- },
- {
- ApprovalGroup: api.ApprovalGroupOwner,
- ApprovalStrategy: api.ApprovalStrategyAutomatic,
- DeploymentType: api.DeploymentTypeDatabaseDDL,
- },
- }),
- api.PolicyTypeDeploymentApproval,
- ),
- Check: resource.ComposeTestCheckFunc(
- internal.TestCheckResourceExists("bytebase_policy.deployment_approval"),
- resource.TestCheckResourceAttr("bytebase_policy.deployment_approval", "type", string(api.PolicyTypeDeploymentApproval)),
- resource.TestCheckResourceAttr("bytebase_policy.deployment_approval", "deployment_approval_policy.#", "1"),
- resource.TestCheckResourceAttr("bytebase_policy.deployment_approval", "deployment_approval_policy.0.default_strategy", string(api.ApprovalStrategyAutomatic)),
- resource.TestCheckResourceAttr("bytebase_policy.deployment_approval", "deployment_approval_policy.0.deployment_approval_strategies.#", "2"),
- resource.TestCheckResourceAttr("bytebase_policy.deployment_approval", "deployment_approval_policy.0.deployment_approval_strategies.0.deployment_type", string(api.DeploymentTypeDatabaseCreate)),
- resource.TestCheckResourceAttr("bytebase_policy.deployment_approval", "deployment_approval_policy.0.deployment_approval_strategies.1.deployment_type", string(api.DeploymentTypeDatabaseDDL)),
+ internal.TestCheckResourceExists("bytebase_policy.masking_exception_policy"),
+ resource.TestCheckResourceAttr("bytebase_policy.masking_exception_policy", "type", v1pb.PolicyType_MASKING_EXCEPTION.String()),
+ 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.masking_level", v1pb.MaskingLevel_PARTIAL.String()),
),
},
},
})
}
-func TestAccPolicy_InvalidInput(t *testing.T) {
- resource.Test(t, resource.TestCase{
- PreCheck: func() {
- testAccPreCheck(t)
- },
- Providers: testAccProviders,
- CheckDestroy: testAccCheckPolicyDestroy,
- Steps: []resource.TestStep{
- {
- Config: testAccCheckPolicyResource(
- "backup_plan",
- "environments/test",
- getBackupPlanPolicy("daily", 999),
- api.PolicyTypeBackupPlan,
- ),
- ExpectError: regexp.MustCompile("expected backup_plan_policy.0.schedule to be one of"),
- },
- {
- Config: testAccCheckPolicyResource(
- "deployment_approval",
- "environments/test",
- getDeploymentApprovalPolicy("unknown", []*api.DeploymentApprovalStrategy{
- {
- ApprovalGroup: api.ApprovalGroupDBA,
- ApprovalStrategy: api.ApprovalStrategyAutomatic,
- DeploymentType: api.DeploymentTypeDatabaseCreate,
- },
- {
- ApprovalGroup: api.ApprovalGroupOwner,
- ApprovalStrategy: api.ApprovalStrategyAutomatic,
- DeploymentType: api.DeploymentTypeDatabaseDDL,
- },
- }),
- api.PolicyTypeDeploymentApproval,
- ),
- ExpectError: regexp.MustCompile("expected deployment_approval_policy.0.default_strategy to be one of"),
- },
- },
- })
-}
-
-func testAccCheckPolicyResource(identifier, parent, payload string, pType api.PolicyType) string {
+func testAccCheckPolicyResource(identifier, parent, payload string, pType v1pb.PolicyType) string {
return fmt.Sprintf(`
resource "bytebase_policy" "%s" {
parent = "%s"
@@ -134,36 +68,34 @@ func testAccCheckPolicyResource(identifier, parent, payload string, pType api.Po
%s
}
- `, identifier, parent, pType, payload)
+ `, identifier, parent, pType.String(), payload)
}
-func getBackupPlanPolicy(schedule string, duration int) string {
+func getMaskingPolicy(table, column string, level v1pb.MaskingLevel) string {
return fmt.Sprintf(`
- backup_plan_policy {
- schedule = "%s"
- retention_duration = %d
- }
- `, schedule, duration)
-}
-
-func getDeploymentApprovalPolicy(defaultStrategy string, strategies []*api.DeploymentApprovalStrategy) string {
- approvalStrategies := []string{}
- for _, strategy := range strategies {
- approvalStrategies = append(approvalStrategies, fmt.Sprintf(`
- deployment_approval_strategies {
- approval_group = "%s"
- approval_strategy = "%s"
- deployment_type = "%s"
+ masking_policy {
+ mask_data {
+ table = "%s"
+ column = "%s"
+ masking_level = "%s"
}
- `, strategy.ApprovalGroup, strategy.ApprovalStrategy, strategy.DeploymentType))
}
+ `, table, column, level.String())
+}
+func getMaskingExceptionPolicy(database, table, column string, level v1pb.MaskingLevel) string {
return fmt.Sprintf(`
- deployment_approval_policy {
- default_strategy = "%s"
- %s
+ masking_exception_policy {
+ exceptions {
+ database = "%s"
+ table = "%s"
+ column = "%s"
+ masking_level = "%s"
+ member = "user:ed@bytebase.com"
+ action = "QUERY"
+ }
}
- `, defaultStrategy, strings.Join(approvalStrategies, "\n"))
+ `, database, table, column, level.String())
}
func testAccCheckPolicyDestroy(s *terraform.State) error {
diff --git a/provider/resource_setting.go b/provider/resource_setting.go
index 8bf890e..7fd5ffa 100644
--- a/provider/resource_setting.go
+++ b/provider/resource_setting.go
@@ -33,9 +33,11 @@ func resourceSetting() *schema.Resource {
Required: true,
ValidateFunc: validation.StringInSlice([]string{
string(api.SettingWorkspaceApproval),
+ string(api.SettingWorkspaceExternalApproval),
}, false),
},
- "approval_flow": getWorkspaceApprovalSetting(false),
+ "approval_flow": getWorkspaceApprovalSetting(false),
+ "external_approval_nodes": getExternalApprovalSetting(false),
},
}
}
@@ -62,6 +64,16 @@ func resourceSettingUpsert(ctx context.Context, d *schema.ResourceData, m interf
WorkspaceApprovalSettingValue: workspaceApproval,
},
}
+ case api.SettingWorkspaceExternalApproval:
+ externalApproval, err := convertToV1ExternalNodesSetting(d)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ setting.Value = &v1pb.Value{
+ Value: &v1pb.Value_ExternalApprovalSettingValue{
+ ExternalApprovalSettingValue: externalApproval,
+ },
+ }
default:
return diag.FromErr(errors.Errorf("Unsupport setting: %v", name))
}
@@ -81,6 +93,27 @@ func resourceSettingUpsert(ctx context.Context, d *schema.ResourceData, m interf
return diags
}
+func convertToV1ExternalNodesSetting(d *schema.ResourceData) (*v1pb.ExternalApprovalSetting, error) {
+ rawList, ok := d.Get("external_approval_nodes").([]interface{})
+ if !ok || len(rawList) != 1 {
+ return nil, errors.Errorf("invalid external_approval_nodes")
+ }
+
+ raw := rawList[0].(map[string]interface{})
+ nodes := raw["nodes"].([]interface{})
+ externalApprovalSetting := &v1pb.ExternalApprovalSetting{}
+
+ for _, node := range nodes {
+ rawNode := node.(map[string]interface{})
+ externalApprovalSetting.Nodes = append(externalApprovalSetting.Nodes, &v1pb.ExternalApprovalSetting_Node{
+ Id: rawNode["id"].(string),
+ Title: rawNode["title"].(string),
+ Endpoint: rawNode["endpoint"].(string),
+ })
+ }
+ return externalApprovalSetting, nil
+}
+
func convertToV1ApprovalSetting(d *schema.ResourceData) (*v1pb.WorkspaceApprovalSetting, error) {
rawList, ok := d.Get("approval_flow").([]interface{})
if !ok || len(rawList) != 1 {
@@ -111,6 +144,10 @@ func convertToV1ApprovalSetting(d *schema.ResourceData) (*v1pb.WorkspaceApproval
return nil, errors.Errorf("invalid flow")
}
rawFlow := flowList[0].(map[string]interface{})
+ creator := rawFlow["creator"].(string)
+ if !strings.HasPrefix(creator, "users/") {
+ return nil, errors.Errorf("creator should in users/{email} format")
+ }
approvalRule := &v1pb.WorkspaceApprovalSetting_Rule{
Template: &v1pb.ApprovalTemplate{
Title: rawFlow["title"].(string),
@@ -138,13 +175,23 @@ func convertToV1ApprovalSetting(d *schema.ResourceData) (*v1pb.WorkspaceApproval
}
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", node)
+ 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),