-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ENG-12949: Policy engine providers #547
Changes from 7 commits
b2ad3ba
9636a60
83911e5
4526ad6
b02c3af
84dc008
b295c84
7833171
a0b8969
ad95877
6c598d5
8f0e30a
71049f1
bf2eae2
b5d553f
a3fd663
5736787
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 "" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package policyv2 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of creating a package There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
|
||
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{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This schema is identical to the resource. You may simplify this code by calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not, is it. I mean for the resources, you have to have some four fields, the rest are optional. For datasource you have to have two fields (type, id) - but not name & document or any other field. |
||
"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`, `approval`]", | ||
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, | ||
}, | ||
}, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,158 @@ | ||||||||||||||||||||
package policyv2 | ||||||||||||||||||||
|
||||||||||||||||||||
import ( | ||||||||||||||||||||
"fmt" | ||||||||||||||||||||
|
||||||||||||||||||||
"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"` | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
// PolicyV2 represents the top-level policy structure | ||||||||||||||||||||
type PolicyV2 struct { | ||||||||||||||||||||
Policy Policy `json:"policy,omitempty"` | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
type Scope struct { | ||||||||||||||||||||
RepoIds []string `json:"repoIds,omitempty"` | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
// 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) | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
if err := d.Set("last_updated", map[string]interface{}{ | ||||||||||||||||||||
"actor": r.Policy.LastUpdated.Actor, | ||||||||||||||||||||
"actor_type": r.Policy.LastUpdated.ActorType, | ||||||||||||||||||||
"timestamp": r.Policy.LastUpdated.Timestamp, | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Teach There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||||||||||||||||||||
}); err != nil { | ||||||||||||||||||||
return fmt.Errorf("error setting 'last_updated' field: %w", err) | ||||||||||||||||||||
} | ||||||||||||||||||||
if err := d.Set("created", map[string]interface{}{ | ||||||||||||||||||||
"actor": r.Policy.Created.Actor, | ||||||||||||||||||||
"actor_type": r.Policy.Created.ActorType, | ||||||||||||||||||||
"timestamp": r.Policy.Created.Timestamp, | ||||||||||||||||||||
}); 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 err := d.Set("scope", flattenScope(r.Policy.Scope)); 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 = expandStringList(d.Get("tags").([]interface{})) | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||||||||||||||||||||
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 = expandScope(v.([]interface{})) | ||||||||||||||||||||
} | ||||||||||||||||||||
return nil | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
// expandStringList converts a list of interface{} to a list of strings | ||||||||||||||||||||
func expandStringList(list []interface{}) []string { | ||||||||||||||||||||
result := make([]string, len(list)) | ||||||||||||||||||||
for i, v := range list { | ||||||||||||||||||||
result[i] = v.(string) | ||||||||||||||||||||
} | ||||||||||||||||||||
return result | ||||||||||||||||||||
} | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||||||||||||||||||||
|
||||||||||||||||||||
// getStringOrDefault returns the string value or an empty string if nil or not a string | ||||||||||||||||||||
func getStringOrDefault(v interface{}) string { | ||||||||||||||||||||
str, ok := v.(string) | ||||||||||||||||||||
if !ok { | ||||||||||||||||||||
return "" | ||||||||||||||||||||
} | ||||||||||||||||||||
return str | ||||||||||||||||||||
gengdahlCyral marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
// flattenScope converts the Scope struct to a list of maps | ||||||||||||||||||||
func flattenScope(scope *Scope) []map[string]interface{} { | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||||||||||||||||||||
if scope == nil { | ||||||||||||||||||||
return nil | ||||||||||||||||||||
} | ||||||||||||||||||||
scopeMap := []map[string]interface{}{ | ||||||||||||||||||||
{ | ||||||||||||||||||||
"repo_ids": scope.RepoIds, | ||||||||||||||||||||
}, | ||||||||||||||||||||
} | ||||||||||||||||||||
return scopeMap | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
// expandScope converts the map to a Scope struct | ||||||||||||||||||||
func expandScope(s []interface{}) *Scope { | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New name seems better IMO, but feel free to not use it.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||||||||||||||||||||
if len(s) == 0 || s[0] == nil { | ||||||||||||||||||||
return nil | ||||||||||||||||||||
} | ||||||||||||||||||||
m := s[0].(map[string]interface{}) | ||||||||||||||||||||
scope := Scope{ | ||||||||||||||||||||
RepoIds: expandStringList(m["repo_ids"].([]interface{})), | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||||||||||||||||||||
} | ||||||||||||||||||||
return &scope | ||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe something like
cyral_custom_policy
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's keep as
cyral_policy_v2
we will replacecyral_policy
by this new resource in the next major version of the provider. Please also mark thecyral_policy
as deprecated as we should encourage the move.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just rename the policy package with deprecated? Looks like that is what we've done before? Moving the files to deprecated folder won't work with this new model (as it will eventually lead to filename collisions)?