Skip to content

Commit

Permalink
ENG-12949: Policy engine providers (#547)
Browse files Browse the repository at this point in the history
* Consolidated changes for policy engine providers

* Added tf resource example, updated descriptions and tmpl file

* Added tf resource example, updated descriptions and tmpl file

* Review comment; changed optional to computed for read-only attributes in datasource.

* Review comment; The 'last updated' and 'created' attributes are not being transferred from the schema to the API."

* go sum

* Removed unused function, hiding approval from examples and docs, up-deps

* Hiding approval from import syntax in docs as well

* File structure refactor

* review comments

* Removed a comment and update dependencies

* Deprecate resource cyral_policy

* Bump golang to v1.22

* Fix test error

* Fix docs

* Bump golang to v1.22

---------

Co-authored-by: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com>
  • Loading branch information
gengdahlCyral and wcmjunior authored Jul 3, 2024
1 parent b1b6c73 commit 7e99c93
Show file tree
Hide file tree
Showing 24 changed files with 1,014 additions and 97 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.21
go-version: 1.22
- name: Import GPG key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v5.0.0
Expand Down
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM hashicorp/terraform:1.7.5 as terraform
FROM hashicorp/terraform:1.9.0 as terraform

FROM golang:1.21.5-alpine3.17 AS build
FROM golang:1.22.5-alpine3.20 AS build
WORKDIR /go/src/cyral
COPY main.go go.mod go.sum ./
COPY client/ client/
Expand All @@ -12,7 +12,7 @@ RUN gofmt -w . \
&& GOOS=darwin GOARCH=amd64 go build -o out/darwin_amd64/terraform-provider-cyral . \
&& GOOS=linux GOARCH=amd64 go build -o out/linux_amd64/terraform-provider-cyral .

FROM alpine:3.20.0 as output
FROM alpine:3.20.1 as output
ARG VERSION
RUN mkdir -p /root/.terraform.d/plugins/local/terraform/cyral/${VERSION:?You must set the VERSION build argument}
COPY --from=build /go/src/cyral/out/ /root/.terraform.d/plugins/local/terraform/cyral/${VERSION}
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var resourceContextHandler = core.DefaultContextHandler{

func resourceSchema() *schema.Resource {
return &schema.Resource{
DeprecationMessage: "For control planes `>= v4.15`, please use resource `cyral_policy_v2` instead.",
Description: "Manages [policies](https://cyral.com/docs/reference/policy). See also: " +
"[Policy Rule](./policy_rule.md). For more information, see the " +
"[Policy Guide](https://cyral.com/docs/policy/overview).",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"
"testing"

"github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy"
"github.com/cyralinc/terraform-provider-cyral/cyral/internal/deprecated/policy"
"github.com/cyralinc/terraform-provider-cyral/cyral/provider"
"github.com/cyralinc/terraform-provider-cyral/cyral/utils"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
Expand Down
File renamed without changes.
22 changes: 22 additions & 0 deletions cyral/internal/policy/v2/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package policyv2

const (
resourceName = "cyral_policy_v2"
dataSourceName = resourceName
apiPathLocal = "v2/policies/local"
apiPathGlobal = "v2/policies/global"
apiPathApproval = "v2/policies/approval"
)

func getAPIPath(policyType string) string {
switch policyType {
case "POLICY_TYPE_LOCAL", "local":
return apiPathLocal
case "POLICY_TYPE_GLOBAL", "global":
return apiPathGlobal
case "POLICY_TYPE_APPROVAL", "approval":
return apiPathApproval
default:
return ""
}
}
111 changes: 111 additions & 0 deletions cyral/internal/policy/v2/datasource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package policyv2

import (
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

"github.com/cyralinc/terraform-provider-cyral/cyral/client"
"github.com/cyralinc/terraform-provider-cyral/cyral/core"
"github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype"
)

var dsContextHandler = core.DefaultContextHandler{
ResourceName: dataSourceName,
ResourceType: resourcetype.DataSource,
SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &PolicyV2{} },
ReadUpdateDeleteURLFactory: func(d *schema.ResourceData, c *client.Client) string {
return fmt.Sprintf("https://%s/%s/%s", c.ControlPlane, getAPIPath(d.Get("type").(string)), d.Get("id").(string))
},
}

func dataSourceSchema() *schema.Resource {
return &schema.Resource{
Description: "This data source provides information about a policy.",
ReadContext: dsContextHandler.ReadContext(),
Schema: map[string]*schema.Schema{
"id": {
Description: "Identifier for the policy, unique within the policy type.",
Type: schema.TypeString,
Required: true,
},
"type": {
Description: "Type of the policy, one of [`local`, `global`]",
Type: schema.TypeString,
Required: true,
},
"name": {
Description: "Name of the policy.",
Type: schema.TypeString,
Computed: true,
},
"description": {
Description: "Description of the policy.",
Type: schema.TypeString,
Computed: true,
},
"enabled": {
Description: "Indicates if the policy is enabled.",
Type: schema.TypeBool,
Computed: true,
},
"tags": {
Description: "Tags associated with the policy for categorization.",
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"scope": {
Description: "Scope of the policy. If empty or omitted, all repositories are in scope.",
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"repo_ids": {
Description: "List of repository IDs that are in scope.",
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Computed: true,
},
},
},
},
"valid_from": {
Description: "Time when the policy comes into effect. If omitted, the policy is in effect immediately.",
Type: schema.TypeString,
Computed: true,
},
"valid_until": {
Description: "Time after which the policy is no longer in effect. If omitted, the policy is in effect indefinitely.",
Type: schema.TypeString,
Computed: true,
},
"document": {
Description: "The actual policy document in JSON format. It must conform to the schema for the policy type.",
Type: schema.TypeString,
Computed: true,
},
"last_updated": {
Description: "Information about when and by whom the policy was last updated.",
Type: schema.TypeMap,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"created": {
Description: "Information about when and by whom the policy was created.",
Type: schema.TypeMap,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"enforced": {
Description: "Indicates if the policy is enforced. If not enforced, no action is taken based on the policy, but alerts are triggered for violations.",
Type: schema.TypeBool,
Computed: true,
},
},
}
}
140 changes: 140 additions & 0 deletions cyral/internal/policy/v2/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package policyv2

import (
"fmt"

"github.com/cyralinc/terraform-provider-cyral/cyral/utils"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

// 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"`
}

// ToMap converts ChangeInfo to a map
func (c ChangeInfo) ToMap() map[string]interface{} {
return map[string]interface{}{
"actor": c.Actor,
"actor_type": c.ActorType,
"timestamp": c.Timestamp,
}
}

// PolicyV2 represents the top-level policy structure
type PolicyV2 struct {
Policy Policy `json:"policy,omitempty"`
}

type Scope struct {
RepoIds []string `json:"repoIds,omitempty"`
}

// ToMap converts Scope to a list of maps
func (s *Scope) ToMap() []map[string]interface{} {
return []map[string]interface{}{
{
"repo_ids": s.RepoIds,
},
}
}

// Policy represents the policy details
type Policy struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Enabled bool `json:"enabled,omitempty"`
Scope *Scope `json:"scope,omitempty"`
Tags []string `json:"tags,omitempty"`
ValidFrom string `json:"validFrom,omitempty"`
ValidUntil string `json:"validUntil,omitempty"`
Document string `json:"document,omitempty"`
LastUpdated ChangeInfo `json:"lastUpdated,omitempty"`
Created ChangeInfo `json:"created,omitempty"`
Enforced bool `json:"enforced,omitempty"`
Type string `json:"type,omitempty"`
}

// WriteToSchema writes the policy data to the schema
func (r PolicyV2) WriteToSchema(d *schema.ResourceData) error {
if err := d.Set("id", r.Policy.ID); err != nil {
return fmt.Errorf("error setting 'id' field: %w", err)
}
if err := d.Set("name", r.Policy.Name); err != nil {
return fmt.Errorf("error setting 'name' field: %w", err)
}
if err := d.Set("description", r.Policy.Description); err != nil {
return fmt.Errorf("error setting 'description' field: %w", err)
}
if err := d.Set("enabled", r.Policy.Enabled); err != nil {
return fmt.Errorf("error setting 'enabled' field: %w", err)
}
if err := d.Set("tags", r.Policy.Tags); err != nil {
return fmt.Errorf("error setting 'tags' field: %w", err)
}
if err := d.Set("valid_from", r.Policy.ValidFrom); err != nil {
return fmt.Errorf("error setting 'valid_from' field: %w", err)
}
if err := d.Set("valid_until", r.Policy.ValidUntil); err != nil {
return fmt.Errorf("error setting 'valid_until' field: %w", err)
}
if err := d.Set("document", r.Policy.Document); err != nil {
return fmt.Errorf("error setting 'document' field: %w", err)
}

// Use the ToMap method to set the last_updated and created fields
if err := d.Set("last_updated", r.Policy.LastUpdated.ToMap()); err != nil {
return fmt.Errorf("error setting 'last_updated' field: %w", err)
}
if err := d.Set("created", r.Policy.Created.ToMap()); err != nil {
return fmt.Errorf("error setting 'created' field: %w", err)
}
if err := d.Set("enforced", r.Policy.Enforced); err != nil {
return fmt.Errorf("error setting 'enforced' field: %w", err)
}
if r.Policy.Type != "" {
if err := d.Set("type", r.Policy.Type); err != nil {
return fmt.Errorf("error setting 'type' field: %w", err)
}
}
if r.Policy.Scope != nil {
if err := d.Set("scope", r.Policy.Scope.ToMap()); err != nil {
return fmt.Errorf("error setting 'scope' field: %w", err)
}
}
d.SetId(r.Policy.ID)
return nil
}

// ReadFromSchema reads the policy data from the schema
func (r *PolicyV2) ReadFromSchema(d *schema.ResourceData) error {
r.Policy.ID = d.Get("id").(string)
r.Policy.Name = d.Get("name").(string)
r.Policy.Description = d.Get("description").(string)
r.Policy.Enabled = d.Get("enabled").(bool)
r.Policy.Tags = utils.ConvertFromInterfaceList[string](d.Get("tags").([]interface{}))
r.Policy.ValidFrom = d.Get("valid_from").(string)
r.Policy.ValidUntil = d.Get("valid_until").(string)
r.Policy.Document = d.Get("document").(string)
r.Policy.Enforced = d.Get("enforced").(bool)
r.Policy.Type = d.Get("type").(string)
if v, ok := d.GetOk("scope"); ok {
r.Policy.Scope = scopeFromInterface(v.([]interface{}))
}
return nil
}

// scopeFromInterface converts the map to a Scope struct
func scopeFromInterface(s []interface{}) *Scope {
if len(s) == 0 || s[0] == nil {
return nil
}
m := s[0].(map[string]interface{})
scope := Scope{
RepoIds: utils.ConvertFromInterfaceList[string](m["repo_ids"].([]interface{})),
}
return &scope
}
Loading

0 comments on commit 7e99c93

Please sign in to comment.