Skip to content

Commit

Permalink
Merge pull request #895 from hashicorp/jspiker/policyEnforcementLevel
Browse files Browse the repository at this point in the history
Add enforcement level to policies
  • Loading branch information
JarrettSpiker authored Apr 29, 2024
2 parents 7e3d4a4 + 774956e commit 03314f5
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 27 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
* Adds `Teams` field to `OrganizationMembershipCreateOptions` to allow users to be added to teams at the same time they are invited to an organization. by @JarrettSpiker [#886](https://github.com/hashicorp/go-tfe/pull/886)
* `IsCloud()` returns true when TFP-AppName is "HCP Terraform" by @sebasslash [#891](https://github.com/hashicorp/go-tfe/pull/891)
* `OrganizationScoped` attribute for `OAuthClient` is now generally available by @netramali [#873](https://github.com/hashicorp/go-tfe/pull/873)
* Add `EnforcementLevel` to `Policy` create and update options. This will replace the deprecated `[]Enforce` method for specifying enforcement level. @JarrettSpiker [#895](https://github.com/hashicorp/go-tfe/pull/895)

## Deprecations
* The `Enforce` fields on `Policy`, `PolicyCreateOptions`, and `PolicyUpdateOptions` have been deprecated. Use the `EnforcementLevel` instead. @JarrettSpiker [#895](https://github.com/hashicorp/go-tfe/pull/895)

# v1.50.0

Expand Down
4 changes: 3 additions & 1 deletion errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,9 @@ var (

ErrRequiredEnabled = errors.New("enabled is required")

ErrRequiredEnforce = errors.New("enforce is required")
ErrRequiredEnforce = errors.New("enforce or enforcement-level is required")

ErrConflictingEnforceEnforcementLevel = errors.New("enforce and enforcement-level may not both be specified together")

ErrRequiredEnforcementPath = errors.New("enforcement path is required")

Expand Down
22 changes: 13 additions & 9 deletions helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -771,20 +771,24 @@ func createPolicyWithOptions(t *testing.T, client *Client, org *Organization, op
}

name := randomString(t)
path := name + ".sentinel"
if opts.Kind == OPA {
path = name + ".rego"
}
options := PolicyCreateOptions{
Name: String(name),
Kind: opts.Kind,
Query: opts.Query,
Enforce: []*EnforcementOptions{
Name: String(name),
Kind: opts.Kind,
Query: opts.Query,
EnforcementLevel: opts.EnforcementLevel,
}

if len(opts.Enforce) > 0 {
path := name + ".sentinel"
if opts.Kind == OPA {
path = name + ".rego"
}
options.Enforce = []*EnforcementOptions{
{
Path: String(path),
Mode: opts.Enforce[0].Mode,
},
},
}
}

ctx := context.Background()
Expand Down
52 changes: 35 additions & 17 deletions policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,16 @@ type PolicyList struct {

// Policy represents a Terraform Enterprise policy.
type Policy struct {
ID string `jsonapi:"primary,policies"`
Name string `jsonapi:"attr,name"`
Kind PolicyKind `jsonapi:"attr,kind"`
Query *string `jsonapi:"attr,query"`
Description string `jsonapi:"attr,description"`
Enforce []*Enforcement `jsonapi:"attr,enforce"`
PolicySetCount int `jsonapi:"attr,policy-set-count"`
UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"`
ID string `jsonapi:"primary,policies"`
Name string `jsonapi:"attr,name"`
Kind PolicyKind `jsonapi:"attr,kind"`
Query *string `jsonapi:"attr,query"`
Description string `jsonapi:"attr,description"`
// Deprecated: Use EnforcementLevel instead.
Enforce []*Enforcement `jsonapi:"attr,enforce"`
EnforcementLevel EnforcementLevel `jsonapi:"attr,enforcement-level"`
PolicySetCount int `jsonapi:"attr,policy-set-count"`
UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"`

// Relations
Organization *Organization `jsonapi:"relation,organization"`
Expand Down Expand Up @@ -121,8 +123,14 @@ type PolicyCreateOptions struct {
// Optional: A description of the policy's purpose.
Description *string `jsonapi:"attr,description,omitempty"`

// Required: The enforcements of the policy.
Enforce []*EnforcementOptions `jsonapi:"attr,enforce"`
// The enforcements of the policy.
//
// Deprecated: Use EnforcementLevel instead.
Enforce []*EnforcementOptions `jsonapi:"attr,enforce,omitempty"`

// Required: The enforcement level of the policy.
// Either EnforcementLevel or Enforce must be set.
EnforcementLevel *EnforcementLevel `jsonapi:"attr,enforcement-level,omitempty"`
}

// PolicyUpdateOptions represents the options for updating a policy.
Expand All @@ -140,7 +148,12 @@ type PolicyUpdateOptions struct {
Query *string `jsonapi:"attr,query,omitempty"`

// Optional: The enforcements of the policy.
//
// Deprecated: Use EnforcementLevel instead.
Enforce []*EnforcementOptions `jsonapi:"attr,enforce,omitempty"`

// Optional: The enforcement level of the policy.
EnforcementLevel *EnforcementLevel `jsonapi:"attr,enforcement-level,omitempty"`
}

// List all the policies for a given organization
Expand Down Expand Up @@ -291,15 +304,20 @@ func (o PolicyCreateOptions) valid() error {
if o.Kind == OPA && !validString(o.Query) {
return ErrRequiredQuery
}
if o.Enforce == nil {
if o.Enforce == nil && o.EnforcementLevel == nil {
return ErrRequiredEnforce
}
for _, e := range o.Enforce {
if !validString(e.Path) {
return ErrRequiredEnforcementPath
}
if e.Mode == nil {
return ErrRequiredEnforcementMode
if o.Enforce != nil && o.EnforcementLevel != nil {
return ErrConflictingEnforceEnforcementLevel
}
if o.Enforce != nil {
for _, e := range o.Enforce {
if !validString(e.Path) {
return ErrRequiredEnforcementPath
}
if e.Mode == nil {
return ErrRequiredEnforcementMode
}
}
}
return nil
Expand Down
69 changes: 69 additions & 0 deletions policy_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,35 @@ func TestPoliciesCreate(t *testing.T) {
}
})

t.Run("with valid options - Enforcement Level", func(t *testing.T) {
name := randomString(t)
options := PolicyCreateOptions{
Name: String(name),
Description: String("A sample policy"),
Kind: Sentinel,
EnforcementLevel: EnforcementMode(EnforcementHard),
}

p, err := client.Policies.Create(ctx, orgTest.Name, options)
require.NoError(t, err)

// Get a refreshed view from the API.
refreshed, err := client.Policies.Read(ctx, p.ID)
require.NoError(t, err)

for _, item := range []*Policy{
p,
refreshed,
} {
assert.NotEmpty(t, item.ID)
assert.Equal(t, *options.Name, item.Name)
assert.Equal(t, options.Kind, item.Kind)
assert.Nil(t, options.Query)
assert.Equal(t, *options.Description, item.Description)
assert.Equal(t, *options.EnforcementLevel, item.EnforcementLevel)
}
})

t.Run("when options has an invalid name", func(t *testing.T) {
p, err := client.Policies.Create(ctx, orgTest.Name, PolicyCreateOptions{
Name: String(badIdentifier),
Expand Down Expand Up @@ -369,6 +398,24 @@ func TestPoliciesCreate(t *testing.T) {
assert.Equal(t, err, ErrRequiredEnforcementMode)
})

t.Run("when options is have both enforcement level and enforcement path", func(t *testing.T) {
name := randomString(t)
options := PolicyCreateOptions{
Name: String(name),
Enforce: []*EnforcementOptions{
{
Path: String(randomString(t) + ".sentinel"),
Mode: EnforcementMode(EnforcementSoft),
},
},
EnforcementLevel: EnforcementMode(EnforcementMandatory),
}

p, err := client.Policies.Create(ctx, orgTest.Name, options)
assert.Nil(t, p)
assert.Equal(t, err, ErrConflictingEnforceEnforcementLevel)
})

t.Run("when options has an invalid organization", func(t *testing.T) {
p, err := client.Policies.Create(ctx, badIdentifier, PolicyCreateOptions{
Name: String("foo"),
Expand Down Expand Up @@ -396,6 +443,7 @@ func TestPoliciesRead(t *testing.T) {
assert.Equal(t, pTest.Name, p.Name)
assert.Equal(t, pTest.PolicySetCount, p.PolicySetCount)
assert.Empty(t, p.Enforce)
assert.Equal(t, p.EnforcementLevel, pTest.EnforcementLevel)
assert.Equal(t, pTest.Organization.Name, p.Organization.Name)
})

Expand All @@ -413,6 +461,7 @@ func TestPoliciesRead(t *testing.T) {
assert.NotEmpty(t, p.Enforce)
assert.NotEmpty(t, p.Enforce[0].Path)
assert.NotEmpty(t, p.Enforce[0].Mode)
assert.Equal(t, p.EnforcementLevel, pTest.EnforcementLevel)
assert.Equal(t, pTest.Organization.Name, p.Organization.Name)
})

Expand Down Expand Up @@ -529,6 +578,26 @@ func TestPoliciesUpdate(t *testing.T) {
assert.Equal(t, "terraform.policy1.deny", *pAfter.Query)
})

t.Run("with a new enforcement level", func(t *testing.T) {
options := PolicyCreateOptions{
Description: String("A sample OPA policy"),
Kind: OPA,
Query: String("data.example.rule"),
EnforcementLevel: EnforcementMode(EnforcementMandatory),
}
pBefore, pBeforeCleanup := createUploadedPolicyWithOptions(t, client, true, orgTest, options)
defer pBeforeCleanup()

pAfter, err := client.Policies.Update(ctx, pBefore.ID, PolicyUpdateOptions{
EnforcementLevel: EnforcementMode(EnforcementAdvisory),
})
require.NoError(t, err)

assert.Equal(t, pBefore.Name, pAfter.Name)
assert.Equal(t, pBefore.EnforcementLevel, EnforcementMandatory)
assert.Equal(t, pAfter.EnforcementLevel, EnforcementAdvisory)
})

t.Run("update query when kind is not OPA", func(t *testing.T) {
pBefore, pBeforeCleanup := createUploadedPolicy(t, client, true, orgTest)
defer pBeforeCleanup()
Expand Down

0 comments on commit 03314f5

Please sign in to comment.