diff --git a/cyral/internal/policy/v2/model.go b/cyral/internal/policy/v2/model.go
index 01d07330..3ad70f19 100644
--- a/cyral/internal/policy/v2/model.go
+++ b/cyral/internal/policy/v2/model.go
@@ -13,13 +13,6 @@ import (
"github.com/cyralinc/terraform-provider-cyral/cyral/utils"
)
-// ChangeInfo represents information about changes to the policy
-type ChangeInfo struct {
- Actor string `json:"actor,omitempty"`
- ActorType string `json:"actorType,omitempty"`
- Timestamp string `json:"timestamp,omitempty"`
-}
-
// changeInfoToMap converts ChangeInfo to a map
func changeInfoToMap(c *msg.ChangeInfo) map[string]interface{} {
return map[string]interface{}{
diff --git a/cyral/internal/policyset/constants.go b/cyral/internal/policyset/constants.go
new file mode 100644
index 00000000..cceefe56
--- /dev/null
+++ b/cyral/internal/policyset/constants.go
@@ -0,0 +1,6 @@
+package policyset
+
+const (
+ resourceName = "cyral_policy_set"
+ dataSourceName = resourceName
+)
diff --git a/cyral/internal/policyset/datasource.go b/cyral/internal/policyset/datasource.go
new file mode 100644
index 00000000..844c5077
--- /dev/null
+++ b/cyral/internal/policyset/datasource.go
@@ -0,0 +1,105 @@
+package policyset
+
+import (
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+
+ "github.com/cyralinc/terraform-provider-cyral/cyral/core"
+ "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype"
+)
+
+var dsContextHandler = core.ContextHandler{
+ ResourceName: dataSourceName,
+ ResourceType: resourcetype.DataSource,
+ Read: readPolicySet,
+}
+
+func dataSourceSchema() *schema.Resource {
+ return &schema.Resource{
+ Description: "This data source provides information about a policy set.",
+ ReadContext: dsContextHandler.ReadContext,
+ Schema: map[string]*schema.Schema{
+ "id": {
+ Description: "Identifier for the policy set.",
+ Type: schema.TypeString,
+ Required: true,
+ },
+ "wizard_id": {
+ Description: "The ID of the policy wizard used to create this policy set.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "name": {
+ Description: "Name of the policy set.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "description": {
+ Description: "Description of the policy set.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "tags": {
+ Description: "Tags associated with the policy set.",
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ },
+ "scope": {
+ Description: "Scope of the policy set.",
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "repo_ids": {
+ Description: "List of repository IDs that are in scope. Empty list means all repositories are in scope.",
+ Type: schema.TypeList,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ Computed: true,
+ },
+ },
+ },
+ },
+ "wizard_parameters": {
+ Description: "Parameters passed to the wizard while creating the policy set.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "enabled": {
+ Description: "Indicates if the policy set is enabled.",
+ Type: schema.TypeBool,
+ Computed: true,
+ },
+ "policies": {
+ Description: "List of policies that comprise the policy set.",
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "type": {
+ Description: "Type of the policy.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "id": {
+ Description: "ID of the policy.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ },
+ },
+ "last_updated": {
+ Description: "Information about when and by whom the policy set was last updated.",
+ Type: schema.TypeMap,
+ Computed: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ },
+ "created": {
+ Description: "Information about when and by whom the policy set was created.",
+ Type: schema.TypeMap,
+ Computed: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ },
+ },
+ }
+}
diff --git a/cyral/internal/policyset/model.go b/cyral/internal/policyset/model.go
new file mode 100644
index 00000000..fc1941f8
--- /dev/null
+++ b/cyral/internal/policyset/model.go
@@ -0,0 +1,168 @@
+package policyset
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ methods "buf.build/gen/go/cyral/policy/grpc/go/policy/v1/policyv1grpc"
+ msg "buf.build/gen/go/cyral/policy/protocolbuffers/go/policy/v1"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+
+ "github.com/cyralinc/terraform-provider-cyral/cyral/client"
+ "github.com/cyralinc/terraform-provider-cyral/cyral/utils"
+)
+
+// ToMap converts PolicySetPolicy to a map
+func policySetPolicyToMap(p *msg.PolicySetPolicy) map[string]interface{} {
+ return map[string]interface{}{
+ "type": p.GetType().String(),
+ "id": p.GetId(),
+ }
+}
+
+func policiesToMaps(policies []*msg.PolicySetPolicy) []map[string]interface{} {
+ var result []map[string]interface{}
+ for _, policy := range policies {
+ result = append(result, policySetPolicyToMap(policy))
+ }
+ return result
+}
+
+// changeInfoToMap converts ChangeInfo to a map
+func changeInfoToMap(c *msg.ChangeInfo) map[string]interface{} {
+ return map[string]interface{}{
+ "actor": c.GetActor(),
+ "actor_type": c.GetActorType().String(),
+ "timestamp": c.GetTimestamp().AsTime().Format(time.RFC3339),
+ }
+}
+
+// scopeToMap converts Scope to a list of maps
+func scopeToMap(s *msg.Scope) []map[string]interface{} {
+ return []map[string]interface{}{
+ {
+ "repo_ids": s.GetRepoIds(),
+ },
+ }
+}
+
+// updateSchema writes the policy set data to the schema
+func updateSchema(ps *msg.PolicySet, d *schema.ResourceData) error {
+ if err := d.Set("id", ps.GetId()); err != nil {
+ return fmt.Errorf("error setting 'id' field: %w", err)
+ }
+ if err := d.Set("wizard_id", ps.GetWizardId()); err != nil {
+ return fmt.Errorf("error setting 'id' field: %w", err)
+ }
+ if err := d.Set("name", ps.GetName()); err != nil {
+ return fmt.Errorf("error setting 'name' field: %w", err)
+ }
+ if err := d.Set("description", ps.GetDescription()); err != nil {
+ return fmt.Errorf("error setting 'description' field: %w", err)
+ }
+ if err := d.Set("enabled", ps.GetEnabled()); err != nil {
+ return fmt.Errorf("error setting 'enabled' field: %w", err)
+ }
+ if err := d.Set("tags", ps.GetTags()); err != nil {
+ return fmt.Errorf("error setting 'tags' field: %w", err)
+ }
+ if err := d.Set("wizard_parameters", ps.GetWizardParameters()); err != nil {
+ return fmt.Errorf("error setting 'document' field: %w", err)
+ }
+
+ if err := d.Set("policies", policiesToMaps(ps.GetPolicies())); err != nil {
+ return fmt.Errorf("error setting 'policies' field: %w", err)
+ }
+ if ps.GetScope() != nil {
+ if err := d.Set("scope", scopeToMap(ps.GetScope())); err != nil {
+ return fmt.Errorf("error setting 'scope' field: %w", err)
+ }
+ }
+ // Use the changeInfoToMap method to set the last_updated and created fields
+ if err := d.Set("last_updated", changeInfoToMap(ps.GetLastUpdated())); err != nil {
+ return fmt.Errorf("error setting 'last_updated' field: %w", err)
+ }
+ if err := d.Set("created", changeInfoToMap(ps.GetCreated())); err != nil {
+ return fmt.Errorf("error setting 'created' field: %w", err)
+ }
+ d.SetId(ps.GetId())
+ return nil
+}
+
+func policySetFromSchema(d *schema.ResourceData) *msg.PolicySet {
+ p := &msg.PolicySet{
+ Id: d.Get("id").(string),
+ Name: d.Get("name").(string),
+ Description: d.Get("description").(string),
+ Enabled: d.Get("enabled").(bool),
+ Tags: utils.ConvertFromInterfaceList[string](d.Get("tags").([]interface{})),
+ WizardId: d.Get("wizard_id").(string),
+ WizardParameters: d.Get("wizard_parameters").(string),
+ }
+
+ if v, ok := d.GetOk("scope"); ok {
+ p.Scope = scopeFromInterface(v.([]interface{}))
+ }
+ return p
+}
+
+// scopeFromInterface converts the map to a Scope struct
+func scopeFromInterface(s []interface{}) *msg.Scope {
+ if len(s) == 0 || s[0] == nil {
+ return nil
+ }
+ m := s[0].(map[string]interface{})
+ return &msg.Scope{
+ RepoIds: utils.ConvertFromInterfaceList[string](m["repo_ids"].([]interface{})),
+ }
+}
+
+func createPolicySet(ctx context.Context, cl *client.Client, rd *schema.ResourceData) error {
+ ps := policySetFromSchema(rd)
+ req := &msg.CreatePolicySetRequest{
+ PolicySet: ps,
+ }
+ grpcClient := methods.NewPolicyWizardServiceClient(cl.GRPCClient())
+ resp, err := grpcClient.CreatePolicySet(ctx, req)
+ if err != nil {
+ return err
+ }
+ rd.SetId(resp.GetPolicySet().GetId())
+ return nil
+}
+
+func readPolicySet(ctx context.Context, cl *client.Client, rd *schema.ResourceData) error {
+ req := &msg.ReadPolicySetRequest{
+ Id: rd.Get("id").(string),
+ }
+ grpcClient := methods.NewPolicyWizardServiceClient(cl.GRPCClient())
+ resp, err := grpcClient.ReadPolicySet(ctx, req)
+ if err != nil {
+ return err
+ }
+ return updateSchema(resp.GetPolicySet(), rd)
+}
+
+func updatePolicySet(ctx context.Context, cl *client.Client, rd *schema.ResourceData) error {
+ ps := policySetFromSchema(rd)
+ req := &msg.UpdatePolicySetRequest{
+ Id: ps.GetId(),
+ PolicySet: ps,
+ }
+ grpcClient := methods.NewPolicyWizardServiceClient(cl.GRPCClient())
+ resp, err := grpcClient.UpdatePolicySet(ctx, req)
+ if err != nil {
+ return err
+ }
+ return updateSchema(resp.GetPolicySet(), rd)
+}
+
+func deletePolicySet(ctx context.Context, cl *client.Client, rd *schema.ResourceData) error {
+ req := &msg.DeletePolicySetRequest{
+ Id: rd.Get("id").(string),
+ }
+ grpcClient := methods.NewPolicyWizardServiceClient(cl.GRPCClient())
+ _, err := grpcClient.DeletePolicySet(ctx, req)
+ return err
+}
diff --git a/cyral/internal/policyset/resource.go b/cyral/internal/policyset/resource.go
new file mode 100644
index 00000000..459d0a1c
--- /dev/null
+++ b/cyral/internal/policyset/resource.go
@@ -0,0 +1,121 @@
+package policyset
+
+import (
+ "context"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+
+ "github.com/cyralinc/terraform-provider-cyral/cyral/core"
+ "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype"
+)
+
+var resourceContextHandler = core.ContextHandler{
+ ResourceName: resourceName,
+ ResourceType: resourcetype.Resource,
+ Create: createPolicySet,
+ Read: readPolicySet,
+ Update: updatePolicySet,
+ Delete: deletePolicySet,
+}
+
+func resourceSchema() *schema.Resource {
+ return &schema.Resource{
+ Description: "This resource allows management of policy sets in the Cyral platform.",
+ CreateContext: resourceContextHandler.CreateContext,
+ ReadContext: resourceContextHandler.ReadContext,
+ UpdateContext: resourceContextHandler.UpdateContext,
+ DeleteContext: resourceContextHandler.DeleteContext,
+ Importer: &schema.ResourceImporter{
+ StateContext: importPolicySetStateContext,
+ },
+ Schema: map[string]*schema.Schema{
+ "id": {
+ Description: "Identifier for the policy set.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "wizard_id": {
+ Description: "The ID of the policy wizard used to create this policy set.",
+ Type: schema.TypeString,
+ Required: true,
+ },
+ "name": {
+ Description: "Name of the policy set.",
+ Type: schema.TypeString,
+ Required: true,
+ },
+ "description": {
+ Description: "Description of the policy set.",
+ Type: schema.TypeString,
+ Optional: true,
+ },
+ "tags": {
+ Description: "Tags associated with the policy set.",
+ Type: schema.TypeList,
+ Optional: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ },
+ "scope": {
+ Description: "Scope of the policy set.",
+ Type: schema.TypeList,
+ Optional: true,
+ MaxItems: 1,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "repo_ids": {
+ Description: "List of repository IDs that are in scope. Empty list means all repositories are in scope.",
+ Type: schema.TypeList,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ Optional: true,
+ },
+ },
+ },
+ },
+ "wizard_parameters": {
+ Description: "Parameters passed to the wizard while creating the policy set.",
+ Type: schema.TypeString,
+ Required: true,
+ },
+ "enabled": {
+ Description: "Indicates if the policy set is enabled.",
+ Type: schema.TypeBool,
+ Optional: true,
+ },
+ "policies": {
+ Description: "List of policies that comprise the policy set.",
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "type": {
+ Description: "Type of the policy.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "id": {
+ Description: "ID of the policy.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ },
+ },
+ "last_updated": {
+ Description: "Information about when and by whom the policy set was last updated.",
+ Type: schema.TypeMap,
+ Computed: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ },
+ "created": {
+ Description: "Information about when and by whom the policy set was created.",
+ Type: schema.TypeMap,
+ Computed: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ },
+ },
+ }
+}
+
+func importPolicySetStateContext(_ context.Context, d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) {
+ return []*schema.ResourceData{d}, nil
+}
diff --git a/cyral/internal/policyset/resource_test.go b/cyral/internal/policyset/resource_test.go
new file mode 100644
index 00000000..44a155c7
--- /dev/null
+++ b/cyral/internal/policyset/resource_test.go
@@ -0,0 +1,162 @@
+package policyset_test
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+
+ "github.com/cyralinc/terraform-provider-cyral/cyral/provider"
+)
+
+func initialPolicySetConfig() map[string]interface{} {
+ wizardParameters := `{"failClosed":true,"denyByDefault":true}`
+
+ return map[string]interface{}{
+ "wizard_id": "repo-lockdown",
+ "name": "Lockdown all repos",
+ "description": "Policy set created via wizard",
+ "tags": []string{"default", "block", "fail closed"},
+ "scope": map[string][]string{"repo_ids": {}}, // Empty scope means to target all repos
+ "wizard_parameters": wizardParameters,
+ "enabled": true,
+ }
+}
+
+func updatedPolicySetConfig() map[string]interface{} {
+ wizardParameters := `{"failClosed":false,"denyByDefault":true}`
+
+ return map[string]interface{}{
+ "wizard_id": "repo-lockdown",
+ "name": "Lockdown all repos",
+ "description": "Updated policy set created via wizard",
+ "tags": []string{"default", "block", "fail open"},
+ "scope": map[string][]string{"repo_ids": {}},
+ "wizard_parameters": wizardParameters,
+ "enabled": true,
+ }
+}
+
+func TestAccPolicyWizardV1Resource(t *testing.T) {
+ testInitialConfig, testInitialFunc := setupPolicySetTest("main_test", initialPolicySetConfig())
+ testUpdatedConfig, testUpdatedFunc := setupPolicySetTest("main_test", updatedPolicySetConfig())
+ resource.Test(t, resource.TestCase{
+ ProviderFactories: provider.ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testInitialConfig,
+ Check: testInitialFunc,
+ },
+ {
+ Config: testUpdatedConfig,
+ Check: testUpdatedFunc,
+ },
+ {
+ // Test importing the resource
+ ResourceName: "cyral_policy_set.main_test",
+ ImportState: true,
+ ImportStateIdFunc: testImportStateIdFunc("cyral_policy_set.main_test"),
+ ImportStateVerify: true,
+ },
+ },
+ })
+}
+
+func setupPolicySetTest(resName string, policySet map[string]interface{}) (string, resource.TestCheckFunc) {
+ config := formatPolicySetIntoConfig(resName, policySet)
+ resourceFullName := fmt.Sprintf("cyral_policy_set.%s", resName)
+
+ testFunction := resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(resourceFullName, "wizard_id", policySet["wizard_id"].(string)),
+ resource.TestCheckResourceAttr(resourceFullName, "name", policySet["name"].(string)),
+ resource.TestCheckResourceAttr(resourceFullName, "description", policySet["description"].(string)),
+ resource.TestCheckResourceAttr(resourceFullName, "wizard_parameters", policySet["wizard_parameters"].(string)),
+ resource.TestCheckResourceAttr(resourceFullName, "enabled", fmt.Sprintf("%v", policySet["enabled"])),
+ )
+
+ // Check tags
+ if tags, ok := policySet["tags"].([]string); ok && len(tags) > 0 {
+ for i, tag := range tags {
+ key := fmt.Sprintf("tags.%d", i)
+ testFunction = resource.ComposeTestCheckFunc(
+ testFunction,
+ resource.TestCheckResourceAttr(resourceFullName, key, tag),
+ )
+ }
+ }
+
+ // Check scope
+ if scope, ok := policySet["scope"].(map[string][]string); ok {
+ if repoIds, ok := scope["repo_ids"]; ok && len(repoIds) > 0 {
+ for i, repoId := range repoIds {
+ key := fmt.Sprintf("scope.0.repo_ids.%d", i)
+ testFunction = resource.ComposeTestCheckFunc(
+ testFunction,
+ resource.TestCheckResourceAttr(resourceFullName, key, repoId),
+ )
+ }
+ }
+ }
+
+ return config, testFunction
+}
+
+func formatPolicySetIntoConfig(resName string, policySet map[string]interface{}) string {
+ config := fmt.Sprintf(`
+resource "cyral_policy_set" "%s" {
+ wizard_id = "%s"
+ name = "%s"
+`, resName, policySet["wizard_id"].(string), policySet["name"].(string))
+
+ if description, ok := policySet["description"].(string); ok && description != "" {
+ config += fmt.Sprintf(` description = "%s"
+`, description)
+ }
+
+ // Handle tags
+ if tags, ok := policySet["tags"].([]string); ok && len(tags) > 0 {
+ config += " tags = [\n"
+ for _, tag := range tags {
+ config += fmt.Sprintf(` "%s",
+`, tag)
+ }
+ config += " ]\n"
+ }
+
+ // Handle scope
+ if scope, ok := policySet["scope"].(map[string][]string); ok {
+ if repoIds, ok := scope["repo_ids"]; ok && len(repoIds) > 0 {
+ config += " scope {\n"
+ config += " repo_ids = [\n"
+ for _, repoId := range repoIds {
+ config += fmt.Sprintf(` "%s",
+`, repoId)
+ }
+ config += " ]\n"
+ config += " }\n"
+ }
+ }
+
+ // Properly escape wizard_parameters
+ config += fmt.Sprintf(" wizard_parameters = %q\n", policySet["wizard_parameters"].(string))
+
+ if enabled, ok := policySet["enabled"].(bool); ok {
+ config += fmt.Sprintf(" enabled = %v\n", enabled)
+ }
+
+ config += "}\n"
+
+ return config
+}
+
+func testImportStateIdFunc(resourceName string) func(*terraform.State) (string, error) {
+ return func(s *terraform.State) (string, error) {
+ rs, ok := s.RootModule().Resources[resourceName]
+ if !ok {
+ return "", fmt.Errorf("resource %s not found in state", resourceName)
+ }
+ // The ID is the policy set ID
+ return rs.Primary.ID, nil
+ }
+}
diff --git a/cyral/internal/policyset/schema_loader.go b/cyral/internal/policyset/schema_loader.go
new file mode 100644
index 00000000..229e9642
--- /dev/null
+++ b/cyral/internal/policyset/schema_loader.go
@@ -0,0 +1,31 @@
+package policyset
+
+import "github.com/cyralinc/terraform-provider-cyral/cyral/core"
+
+type packageSchema struct {
+}
+
+func (p *packageSchema) Name() string {
+ return "policyset"
+}
+
+func (p *packageSchema) Schemas() []*core.SchemaDescriptor {
+ return []*core.SchemaDescriptor{
+
+ {
+ Name: dataSourceName,
+ Type: core.DataSourceSchemaType,
+ Schema: dataSourceSchema,
+ },
+
+ {
+ Name: resourceName,
+ Type: core.ResourceSchemaType,
+ Schema: resourceSchema,
+ },
+ }
+}
+
+func PackageSchema() core.PackageSchema {
+ return &packageSchema{}
+}
diff --git a/cyral/provider/schema_loader.go b/cyral/provider/schema_loader.go
index 0cc34034..9c01d263 100644
--- a/cyral/provider/schema_loader.go
+++ b/cyral/provider/schema_loader.go
@@ -16,6 +16,7 @@ import (
integration_teams "github.com/cyralinc/terraform-provider-cyral/cyral/internal/integration/teams"
"github.com/cyralinc/terraform-provider-cyral/cyral/internal/permission"
policyv2 "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy/v2"
+ "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policyset"
"github.com/cyralinc/terraform-provider-cyral/cyral/internal/regopolicy"
"github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository"
repository_accessgateway "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/accessgateway"
@@ -56,6 +57,7 @@ func packagesSchemas() []core.PackageSchema {
integration_teams.PackageSchema(),
permission.PackageSchema(),
policyv2.PackageSchema(),
+ policyset.PackageSchema(),
regopolicy.PackageSchema(),
repository.PackageSchema(),
repository_accessgateway.PackageSchema(),
diff --git a/docs/data-sources/policy_set.md b/docs/data-sources/policy_set.md
new file mode 100644
index 00000000..2aab2b50
--- /dev/null
+++ b/docs/data-sources/policy_set.md
@@ -0,0 +1,49 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "cyral_policy_set Data Source - terraform-provider-cyral"
+subcategory: ""
+description: |-
+ This data source provides information about a policy set.
+---
+
+# cyral_policy_set (Data Source)
+
+This data source provides information about a policy set.
+
+
+
+## Schema
+
+### Required
+
+- `id` (String) Identifier for the policy set.
+
+### Read-Only
+
+- `created` (Map of String) Information about when and by whom the policy set was created.
+- `description` (String) Description of the policy set.
+- `enabled` (Boolean) Indicates if the policy set is enabled.
+- `last_updated` (Map of String) Information about when and by whom the policy set was last updated.
+- `name` (String) Name of the policy set.
+- `policies` (List of Object) List of policies that comprise the policy set. (see [below for nested schema](#nestedatt--policies))
+- `scope` (List of Object) Scope of the policy set. (see [below for nested schema](#nestedatt--scope))
+- `tags` (List of String) Tags associated with the policy set.
+- `wizard_id` (String) The ID of the policy wizard used to create this policy set.
+- `wizard_parameters` (String) Parameters passed to the wizard while creating the policy set.
+
+
+
+### Nested Schema for `policies`
+
+Read-Only:
+
+- `id` (String)
+- `type` (String)
+
+
+
+### Nested Schema for `scope`
+
+Read-Only:
+
+- `repo_ids` (List of String)
diff --git a/docs/resources/policy_set.md b/docs/resources/policy_set.md
new file mode 100644
index 00000000..26c8e2e4
--- /dev/null
+++ b/docs/resources/policy_set.md
@@ -0,0 +1,80 @@
+# cyral_policy_set (Resource)
+
+This resource allows management of policy sets in the Cyral platform.
+
+-> Import ID syntax is `{policy_set_id}`.
+
+## Example Usage
+
+```terraform
+resource "cyral_repository" "myrepo" {
+ type = "mongodb"
+ name = "myrepo"
+
+ repo_node {
+ name = "node-1"
+ host = "mongodb.cyral.com"
+ port = 27017
+ }
+
+ mongodb_settings {
+ server_type = "standalone"
+ }
+}
+
+resource "cyral_policy_set" "repo_lockdown_example" {
+ wizard_id = "repo-lockdown"
+ name = "default block with failopen"
+ description = "This default policy will block by default all queries for myrepo except the ones not parsed by Cyral"
+ enabled = true
+ tags = ["default block", "fail open"]
+ scope {
+ repo_ids = [cyral_repository.myrepo.id]
+ }
+ wizard_parameters = jsonencode({
+ denyByDefault = true
+ failClosed = false
+ })
+}
+```
+
+
+
+## Schema
+
+### Required
+
+- `name` (String) Name of the policy set.
+- `wizard_id` (String) The ID of the policy wizard used to create this policy set.
+- `wizard_parameters` (String) Parameters passed to the wizard while creating the policy set.
+
+### Optional
+
+- `description` (String) Description of the policy set.
+- `enabled` (Boolean) Indicates if the policy set is enabled.
+- `scope` (Block List, Max: 1) Scope of the policy set. (see [below for nested schema](#nestedblock--scope))
+- `tags` (List of String) Tags associated with the policy set.
+
+### Read-Only
+
+- `created` (Map of String) Information about when and by whom the policy set was created.
+- `id` (String) Identifier for the policy set.
+- `last_updated` (Map of String) Information about when and by whom the policy set was last updated.
+- `policies` (List of Object) List of policies that comprise the policy set. (see [below for nested schema](#nestedatt--policies))
+
+
+
+### Nested Schema for `scope`
+
+Optional:
+
+- `repo_ids` (List of String) List of repository IDs that are in scope. Empty list means all repositories are in scope.
+
+
+
+### Nested Schema for `policies`
+
+Read-Only:
+
+- `id` (String)
+- `type` (String)
diff --git a/docs/resources/repository_conf_analysis.md b/docs/resources/repository_conf_analysis.md
old mode 100755
new mode 100644
diff --git a/examples/resources/cyral_policy_set/resource.tf b/examples/resources/cyral_policy_set/resource.tf
new file mode 100644
index 00000000..210626bb
--- /dev/null
+++ b/examples/resources/cyral_policy_set/resource.tf
@@ -0,0 +1,29 @@
+resource "cyral_repository" "myrepo" {
+ type = "mongodb"
+ name = "myrepo"
+
+ repo_node {
+ name = "node-1"
+ host = "mongodb.cyral.com"
+ port = 27017
+ }
+
+ mongodb_settings {
+ server_type = "standalone"
+ }
+}
+
+resource "cyral_policy_set" "repo_lockdown_example" {
+ wizard_id = "repo-lockdown"
+ name = "default block with failopen"
+ description = "This default policy will block by default all queries for myrepo except the ones not parsed by Cyral"
+ enabled = true
+ tags = ["default block", "fail open"]
+ scope {
+ repo_ids = [cyral_repository.myrepo.id]
+ }
+ wizard_parameters = jsonencode({
+ denyByDefault = true
+ failClosed = false
+ })
+}
diff --git a/go.mod b/go.mod
index 2f8d6af3..48e03e25 100644
--- a/go.mod
+++ b/go.mod
@@ -13,9 +13,9 @@ require (
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0
github.com/stretchr/testify v1.10.0
- golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
+ golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d
golang.org/x/oauth2 v0.24.0
- google.golang.org/grpc v1.68.0
+ google.golang.org/grpc v1.68.1
google.golang.org/protobuf v1.35.2
)
@@ -80,18 +80,18 @@ require (
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/yuin/goldmark v1.7.1 // indirect
github.com/yuin/goldmark-meta v1.1.0 // indirect
- github.com/zclconf/go-cty v1.15.0 // indirect
+ github.com/zclconf/go-cty v1.15.1 // indirect
go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
- golang.org/x/crypto v0.29.0 // indirect
+ golang.org/x/crypto v0.30.0 // indirect
golang.org/x/mod v0.22.0 // indirect
- golang.org/x/net v0.31.0 // indirect
- golang.org/x/sync v0.9.0 // indirect
- golang.org/x/sys v0.27.0 // indirect
- golang.org/x/text v0.20.0 // indirect
- golang.org/x/tools v0.27.0 // indirect
+ golang.org/x/net v0.32.0 // indirect
+ golang.org/x/sync v0.10.0 // indirect
+ golang.org/x/sys v0.28.0 // indirect
+ golang.org/x/text v0.21.0 // indirect
+ golang.org/x/tools v0.28.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index c8f11504..dc75acc8 100644
--- a/go.sum
+++ b/go.sum
@@ -211,8 +211,8 @@ github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
-github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ=
-github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
+github.com/zclconf/go-cty v1.15.1 h1:RgQYm4j2EvoBRXOPxhUvxPzRrGDo1eCOhHXuGfrj5S0=
+github.com/zclconf/go-cty v1.15.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw=
@@ -220,10 +220,10 @@ go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
-golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
-golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
-golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
-golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
+golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
+golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
+golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
@@ -232,15 +232,15 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
-golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
-golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
+golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
+golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
-golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -254,8 +254,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
-golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
@@ -265,24 +265,24 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
-golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
-golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
+golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
+golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
-google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
-google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
-google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
-google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
+google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
+google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
+google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0=
+google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
diff --git a/templates/resources/policy_set.md.tmpl b/templates/resources/policy_set.md.tmpl
new file mode 100644
index 00000000..f1fe8d09
--- /dev/null
+++ b/templates/resources/policy_set.md.tmpl
@@ -0,0 +1,11 @@
+# {{ .Name | trimspace }} ({{ .Type | trimspace }})
+
+{{ .Description | trimspace }}
+
+-> Import ID syntax is `{policy_set_id}`.
+
+## Example Usage
+
+{{ tffile "examples/resources/cyral_policy_set/resource.tf" }}
+
+{{ .SchemaMarkdown | trimspace }}