Skip to content

Commit

Permalink
implement policy v2 resource using gRPC
Browse files Browse the repository at this point in the history
  • Loading branch information
yoursnerdly committed Dec 3, 2024
1 parent 1bab53a commit 673e8d9
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 122 deletions.
4 changes: 4 additions & 0 deletions cyral/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ func New(clientID, clientSecret, controlPlane string, tlsSkipVerify bool) (*Clie
}, nil
}

func (c *Client) GRPCClient() grpc.ClientConnInterface {
return c.grpcClient
}

// DoRequest calls the httpMethod informed and delivers the resourceData as a payload,
// filling the response parameter (if not nil) with the response body.
func (c *Client) DoRequest(ctx context.Context, url, httpMethod string, resourceData interface{}) ([]byte, error) {
Expand Down
20 changes: 2 additions & 18 deletions cyral/internal/policy/v2/constants.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,6 @@
package policyv2

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

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 ""
}
}
19 changes: 8 additions & 11 deletions cyral/internal/policy/v2/datasource.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,25 @@
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))
},
var dsContextHandler = core.GenericContextHandler{
ResourceName: dataSourceName,
ResourceType: resourcetype.DataSource,
Read: readPolicy,
Create: createPolicy,
Update: updatePolicy,
Delete: deletePolicy,
}

func dataSourceSchema() *schema.Resource {
return &schema.Resource{
Description: "This data source provides information about a policy.",
ReadContext: dsContextHandler.ReadContext(),
ReadContext: dsContextHandler.ReadContext,
Schema: map[string]*schema.Schema{
"id": {
Description: "Identifier for the policy, unique within the policy type.",
Expand Down
221 changes: 151 additions & 70 deletions cyral/internal/policy/v2/model.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package policyv2

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"
"google.golang.org/protobuf/types/known/timestamppb"

"github.com/cyralinc/terraform-provider-cyral/cyral/client"
"github.com/cyralinc/terraform-provider-cyral/cyral/utils"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
Expand All @@ -14,127 +21,201 @@ type ChangeInfo struct {
Timestamp string `json:"timestamp,omitempty"`
}

// ToMap converts ChangeInfo to a map
func (c ChangeInfo) ToMap() map[string]interface{} {
// changeInfoToMap converts ChangeInfo to a map
func changeInfoToMap(c *msg.ChangeInfo) map[string]interface{} {
return map[string]interface{}{
"actor": c.Actor,
"actor_type": c.ActorType,
"timestamp": c.Timestamp,
"actor": c.GetActor(),
"actor_type": c.GetActorType().String(),
"timestamp": c.GetTimestamp().AsTime().Format(time.RFC3339),
}
}

// 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{} {
// scopeToMap converts Scope to a list of maps
func scopeToMap(s *msg.Scope) []map[string]interface{} {
return []map[string]interface{}{
{
"repo_ids": s.RepoIds,
"repo_ids": s.GetRepoIds(),
},
}
}

// 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 {
// updateSchema writes the policy data to the schema
func updateSchema(p *msg.Policy, ptype msg.PolicyType, d *schema.ResourceData) error {
if err := d.Set("id", p.GetId()); err != nil {
return fmt.Errorf("error setting 'id' field: %w", err)
}
if err := d.Set("name", r.Policy.Name); err != nil {
if err := d.Set("name", p.GetName()); err != nil {
return fmt.Errorf("error setting 'name' field: %w", err)
}
if err := d.Set("description", r.Policy.Description); err != nil {
if err := d.Set("description", p.GetDescription()); err != nil {
return fmt.Errorf("error setting 'description' field: %w", err)
}
if err := d.Set("enabled", r.Policy.Enabled); err != nil {
if err := d.Set("enabled", p.GetEnabled()); err != nil {
return fmt.Errorf("error setting 'enabled' field: %w", err)
}
if err := d.Set("tags", r.Policy.Tags); err != nil {
if err := d.Set("tags", p.GetTags()); err != nil {
return fmt.Errorf("error setting 'tags' field: %w", err)
}
if err := d.Set("valid_from", r.Policy.ValidFrom); err != nil {
if err := d.Set("valid_from", timestampFromProtobuf(p.GetValidFrom())); err != nil {
return fmt.Errorf("error setting 'valid_from' field: %w", err)
}
if err := d.Set("valid_until", r.Policy.ValidUntil); err != nil {
if err := d.Set("valid_until", timestampFromProtobuf(p.GetValidUntil())); err != nil {
return fmt.Errorf("error setting 'valid_until' field: %w", err)
}
if err := d.Set("document", r.Policy.Document); err != nil {
if err := d.Set("document", p.GetDocument()); 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 {
// Use the changeInfoToMap method to set the last_updated and created fields
if err := d.Set("last_updated", changeInfoToMap(p.GetLastUpdated())); err != nil {
return fmt.Errorf("error setting 'last_updated' field: %w", err)
}
if err := d.Set("created", r.Policy.Created.ToMap()); err != nil {
if err := d.Set("created", changeInfoToMap(p.GetCreated())); err != nil {
return fmt.Errorf("error setting 'created' field: %w", err)
}
if err := d.Set("enforced", r.Policy.Enforced); err != nil {
if err := d.Set("enforced", p.GetEnforced()); 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 {
// policy types have aliases, so we don't want to set the policy type
// except if the new value is not an alias for the old one.
if msg.PolicyType_value[d.Get("type").(string)] != int32(ptype) {
if err := d.Set("type", ptype.String()); 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 {
if p.GetScope() != nil {
if err := d.Set("scope", scopeToMap(p.GetScope())); err != nil {
return fmt.Errorf("error setting 'scope' field: %w", err)
}
}
d.SetId(r.Policy.ID)
d.SetId(p.GetId())
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)
func timestampFromResourceData(key string, d *schema.ResourceData) (*timestamppb.Timestamp, error) {
if v, ok := d.GetOk(key); ok {
ts := v.(string)
if ts == "" {
return nil, nil
}
if t, err := time.Parse(time.RFC3339, ts); err != nil {
return nil, fmt.Errorf("invalid valid_from value: %s", ts)
} else {
return timestamppb.New(t), nil
}
}
return nil, nil
}

func timestampFromProtobuf(ts *timestamppb.Timestamp) string {
if ts == nil {
return ""
}
return ts.AsTime().Format(time.RFC3339)
}

func policyAndTypeFromSchema(d *schema.ResourceData) (*msg.Policy, msg.PolicyType, error) {
ptypeString := d.Get("type").(string)
ptype := msg.PolicyType(msg.PolicyType_value[ptypeString])
if ptype == msg.PolicyType_POLICY_TYPE_UNSPECIFIED {
return nil, msg.PolicyType_POLICY_TYPE_UNSPECIFIED, fmt.Errorf(
"invalid policy type: %s", ptypeString,
)
}
p := &msg.Policy{
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{})),
Document: d.Get("document").(string),
Enforced: d.Get("enforced").(bool),
}
var err error
if p.ValidFrom, err = timestampFromResourceData("valid_from", d); err != nil {
return nil, msg.PolicyType_POLICY_TYPE_UNSPECIFIED, nil
}
if p.ValidUntil, err = timestampFromResourceData("valid_until", d); err != nil {
return nil, msg.PolicyType_POLICY_TYPE_UNSPECIFIED, nil
}

if v, ok := d.GetOk("scope"); ok {
r.Policy.Scope = scopeFromInterface(v.([]interface{}))
p.Scope = scopeFromInterface(v.([]interface{}))
}
return nil
return p, msg.PolicyType(ptype), nil
}

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

func createPolicy(ctx context.Context, cl *client.Client, rd *schema.ResourceData) error {
p, ptype, err := policyAndTypeFromSchema(rd)
if err != nil {
return err
}
req := &msg.CreatePolicyRequest{
Type: ptype,
Policy: p,
}
grpcClient := methods.NewPolicyServiceClient(cl.GRPCClient())
resp, err := grpcClient.CreatePolicy(ctx, req)
if err != nil {
return err
}
rd.SetId(resp.GetId())
return nil
}

func readPolicy(ctx context.Context, cl *client.Client, rd *schema.ResourceData) error {
p, ptype, err := policyAndTypeFromSchema(rd)
if err != nil {
return err
}
req := &msg.ReadPolicyRequest{
Id: p.GetId(),
Type: ptype,
}
grpcClient := methods.NewPolicyServiceClient(cl.GRPCClient())
resp, err := grpcClient.ReadPolicy(ctx, req)
if err != nil {
return err
}
return updateSchema(resp.GetPolicy(), ptype, rd)
}

func updatePolicy(ctx context.Context, cl *client.Client, rd *schema.ResourceData) error {
p, ptype, err := policyAndTypeFromSchema(rd)
if err != nil {
return err
}
req := &msg.UpdatePolicyRequest{
Id: p.GetId(),
Type: ptype,
Policy: p,
}
grpcClient := methods.NewPolicyServiceClient(cl.GRPCClient())
_, err = grpcClient.UpdatePolicy(ctx, req)
return err
}

func deletePolicy(ctx context.Context, cl *client.Client, rd *schema.ResourceData) error {
p, ptype, err := policyAndTypeFromSchema(rd)
if err != nil {
return err
}
req := &msg.DeletePolicyRequest{
Id: p.GetId(),
Type: ptype,
}
grpcClient := methods.NewPolicyServiceClient(cl.GRPCClient())
_, err = grpcClient.DeletePolicy(ctx, req)
return err
}
Loading

0 comments on commit 673e8d9

Please sign in to comment.