From f16efbbc68be653e6ca8596575fb3a2661677714 Mon Sep 17 00:00:00 2001 From: mrinalirao Date: Tue, 1 Nov 2022 12:32:02 +1100 Subject: [PATCH 1/6] add kind for policy set creation + update tests --- policy_set.go | 13 +++++++++++++ policy_set_integration_test.go | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/policy_set.go b/policy_set.go index 85207a1f7..8aff3e199 100644 --- a/policy_set.go +++ b/policy_set.go @@ -10,6 +10,15 @@ import ( // Compile-time proof of interface implementation. var _ PolicySets = (*policySets)(nil) +// PolicyKind is an indicator of the underlying technology that the policy or policy set supports. +// There are two Policykinds documented in the enum. +type PolicyKind string + +const ( + OPA PolicyKind = "opa" + Sentinel PolicyKind = "sentinel" +) + // PolicySets describes all the policy set related methods that the Terraform // Enterprise API supports. // @@ -64,6 +73,7 @@ type PolicySet struct { ID string `jsonapi:"primary,policy-sets"` Name string `jsonapi:"attr,name"` Description string `jsonapi:"attr,description"` + Kind string `jsonapi:"attr,kind"` Global bool `jsonapi:"attr,global"` PoliciesPath string `jsonapi:"attr,policies-path"` PolicyCount int `jsonapi:"attr,policy-count"` @@ -136,6 +146,9 @@ type PolicySetCreateOptions struct { // Optional: Whether or not the policy set is global. Global *bool `jsonapi:"attr,global,omitempty"` + // Optional: The underlying technology that the policy set supports + Kind PolicyKind `jsonapi:"attr,kind,omitempty"` + // Optional: The sub-path within the attached VCS repository to ingress. All // files and directories outside of this sub-path will be ignored. // This option may only be specified when a VCS repo is present. diff --git a/policy_set_integration_test.go b/policy_set_integration_test.go index ef09b6442..a77ee26a3 100644 --- a/policy_set_integration_test.go +++ b/policy_set_integration_test.go @@ -112,6 +112,7 @@ func TestPolicySetsCreate(t *testing.T) { t.Run("with valid attributes", func(t *testing.T) { options := PolicySetCreateOptions{ Name: String("policy-set"), + Kind: OPA, } ps, err := client.PolicySets.Create(ctx, orgTest.Name, options) @@ -119,6 +120,21 @@ func TestPolicySetsCreate(t *testing.T) { assert.Equal(t, ps.Name, *options.Name) assert.Equal(t, ps.Description, "") + assert.Equal(t, ps.Kind, "opa") + assert.False(t, ps.Global) + }) + + t.Run("with kind missing", func(t *testing.T) { + options := PolicySetCreateOptions{ + Name: String("policy-set2"), + } + + ps, err := client.PolicySets.Create(ctx, orgTest.Name, options) + require.NoError(t, err) + + assert.Equal(t, ps.Name, *options.Name) + assert.Equal(t, ps.Description, "") + assert.Equal(t, ps.Kind, "sentinel") assert.False(t, ps.Global) }) @@ -126,6 +142,7 @@ func TestPolicySetsCreate(t *testing.T) { options := PolicySetCreateOptions{ Name: String("global"), Description: String("Policies in this set will be checked in ALL workspaces!"), + Kind: Sentinel, Global: Bool(true), } @@ -134,6 +151,7 @@ func TestPolicySetsCreate(t *testing.T) { assert.Equal(t, ps.Name, *options.Name) assert.Equal(t, ps.Description, *options.Description) + assert.Equal(t, ps.Kind, "sentinel") assert.True(t, ps.Global) }) @@ -146,6 +164,7 @@ func TestPolicySetsCreate(t *testing.T) { options := PolicySetCreateOptions{ Name: String("populated-policy-set"), Policies: []*Policy{pTest}, + Kind: Sentinel, Workspaces: []*Workspace{wTest}, } @@ -156,6 +175,7 @@ func TestPolicySetsCreate(t *testing.T) { assert.Equal(t, ps.PolicyCount, 1) assert.Equal(t, ps.Policies[0].ID, pTest.ID) assert.Equal(t, ps.WorkspaceCount, 1) + assert.Equal(t, ps.Kind, "sentinel") assert.Equal(t, ps.Workspaces[0].ID, wTest.ID) }) @@ -170,6 +190,7 @@ func TestPolicySetsCreate(t *testing.T) { options := PolicySetCreateOptions{ Name: String("vcs-policy-set"), + Kind: Sentinel, PoliciesPath: String("/policy-sets/foo"), VCSRepo: &VCSRepoOptions{ Branch: String("policies"), @@ -190,6 +211,7 @@ func TestPolicySetsCreate(t *testing.T) { assert.False(t, ps.Global) assert.Equal(t, ps.PoliciesPath, "/policy-sets/foo") assert.Equal(t, ps.VCSRepo.Branch, "policies") + assert.Equal(t, ps.Kind, "sentinel") assert.Equal(t, ps.VCSRepo.DisplayIdentifier, githubIdentifier) assert.Equal(t, ps.VCSRepo.Identifier, githubIdentifier) assert.Equal(t, ps.VCSRepo.IngressSubmodules, true) From d16920da0384a261635e8a32c145d154752fec89 Mon Sep 17 00:00:00 2001 From: mrinalirao Date: Tue, 1 Nov 2022 15:55:25 +1100 Subject: [PATCH 2/6] Add OPA support for list policy set API + tests --- helper_test.go | 7 +- policy_check_integration_test.go | 10 +-- policy_set.go | 29 +++++--- policy_set_integration_test.go | 90 +++++++++++++++++++----- policy_set_parameter_integration_test.go | 6 +- policy_set_version_integration_test.go | 4 +- 6 files changed, 104 insertions(+), 42 deletions(-) diff --git a/helper_test.go b/helper_test.go index 1a3f6b194..40a7b364d 100644 --- a/helper_test.go +++ b/helper_test.go @@ -499,7 +499,7 @@ func createPolicySetParameter(t *testing.T, client *Client, ps *PolicySet) (*Pol var psCleanup func() if ps == nil { - ps, psCleanup = createPolicySet(t, client, nil, nil, nil) + ps, psCleanup = createPolicySet(t, client, nil, nil, nil, "") } ctx := context.Background() @@ -525,7 +525,7 @@ func createPolicySetParameter(t *testing.T, client *Client, ps *PolicySet) (*Pol } } -func createPolicySet(t *testing.T, client *Client, org *Organization, policies []*Policy, workspaces []*Workspace) (*PolicySet, func()) { +func createPolicySet(t *testing.T, client *Client, org *Organization, policies []*Policy, workspaces []*Workspace, kind PolicyKind) (*PolicySet, func()) { var orgCleanup func() if org == nil { @@ -537,6 +537,7 @@ func createPolicySet(t *testing.T, client *Client, org *Organization, policies [ Name: String(randomString(t)), Policies: policies, Workspaces: workspaces, + Kind: kind, }) if err != nil { t.Fatal(err) @@ -559,7 +560,7 @@ func createPolicySetVersion(t *testing.T, client *Client, ps *PolicySet) (*Polic var psCleanup func() if ps == nil { - ps, psCleanup = createPolicySet(t, client, nil, nil, nil) + ps, psCleanup = createPolicySet(t, client, nil, nil, nil, "") } ctx := context.Background() diff --git a/policy_check_integration_test.go b/policy_check_integration_test.go index 2bdac797f..d230fc9f9 100644 --- a/policy_check_integration_test.go +++ b/policy_check_integration_test.go @@ -31,7 +31,7 @@ func TestPolicyChecksList(t *testing.T) { defer policyCleanup2() wTest, wsCleanup := createWorkspace(t, client, orgTest) defer wsCleanup() - createPolicySet(t, client, orgTest, []*Policy{pTest1, pTest2}, []*Workspace{wTest}) + createPolicySet(t, client, orgTest, []*Policy{pTest1, pTest2}, []*Workspace{wTest}, "") rTest, runCleanup := createPolicyCheckedRun(t, client, wTest) defer runCleanup() @@ -95,7 +95,7 @@ func TestPolicyChecksRead(t *testing.T) { pTest, _ := createUploadedPolicy(t, client, true, orgTest) wTest, _ := createWorkspace(t, client, orgTest) - createPolicySet(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest}) + createPolicySet(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest}, "") rTest, _ := createPolicyCheckedRun(t, client, wTest) require.Equal(t, 1, len(rTest.PolicyChecks)) @@ -142,7 +142,7 @@ func TestPolicyChecksOverride(t *testing.T) { wTest, wTestCleanup := createWorkspace(t, client, orgTest) defer wTestCleanup() - createPolicySet(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest}) + createPolicySet(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest}, "") rTest, tTestCleanup := createPolicyCheckedRun(t, client, wTest) defer tTestCleanup() @@ -167,7 +167,7 @@ func TestPolicyChecksOverride(t *testing.T) { wTest, wTestCleanup := createWorkspace(t, client, orgTest) defer wTestCleanup() - createPolicySet(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest}) + createPolicySet(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest}, "") rTest, rTestCleanup := createPolicyCheckedRun(t, client, wTest) defer rTestCleanup() @@ -201,7 +201,7 @@ func TestPolicyChecksLogs(t *testing.T) { defer pTestCleanup() wTest, wTestCleanup := createWorkspace(t, client, orgTest) defer wTestCleanup() - createPolicySet(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest}) + createPolicySet(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest}, "") rTest, rTestCleanup := createPolicyCheckedRun(t, client, wTest) defer rTestCleanup() diff --git a/policy_set.go b/policy_set.go index 8aff3e199..589922285 100644 --- a/policy_set.go +++ b/policy_set.go @@ -70,17 +70,18 @@ type PolicySetList struct { // PolicySet represents a Terraform Enterprise policy set. type PolicySet struct { - ID string `jsonapi:"primary,policy-sets"` - Name string `jsonapi:"attr,name"` - Description string `jsonapi:"attr,description"` - Kind string `jsonapi:"attr,kind"` - Global bool `jsonapi:"attr,global"` - PoliciesPath string `jsonapi:"attr,policies-path"` - PolicyCount int `jsonapi:"attr,policy-count"` - VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"` - WorkspaceCount int `jsonapi:"attr,workspace-count"` - CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` - UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"` + ID string `jsonapi:"primary,policy-sets"` + Name string `jsonapi:"attr,name"` + Description string `jsonapi:"attr,description"` + Kind PolicyKind `jsonapi:"attr,kind"` + Overridable bool `jsonapi:"attr,overridable"` + Global bool `jsonapi:"attr,global"` + PoliciesPath string `jsonapi:"attr,policies-path"` + PolicyCount int `jsonapi:"attr,policy-count"` + VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"` + WorkspaceCount int `jsonapi:"attr,workspace-count"` + CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` + UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"` // Relations // The organization to which the policy set belongs to. @@ -115,6 +116,9 @@ type PolicySetListOptions struct { // Optional: A search string (partial policy set name) used to filter the results. Search string `url:"search[name],omitempty"` + // Optional: A kind string used to filter the results by the policy set kind. + Kind PolicyKind `url:"filter[kind],omitempty"` + // Optional: A list of relations to include. See available resources // https://www.terraform.io/cloud-docs/api-docs/policy-sets#available-related-resources Include []PolicySetIncludeOpt `url:"include,omitempty"` @@ -149,6 +153,9 @@ type PolicySetCreateOptions struct { // Optional: The underlying technology that the policy set supports Kind PolicyKind `jsonapi:"attr,kind,omitempty"` + // Optional: Whether or not users can override this policy when it fails during a run. Only valid for OPA policies. + Overridable *bool `jsonapi:"attr,overridable,omitempty"` + // Optional: The sub-path within the attached VCS repository to ingress. All // files and directories outside of this sub-path will be ignored. // This option may only be specified when a VCS repo is present. diff --git a/policy_set_integration_test.go b/policy_set_integration_test.go index a77ee26a3..72d278276 100644 --- a/policy_set_integration_test.go +++ b/policy_set_integration_test.go @@ -29,10 +29,12 @@ func TestPolicySetsList(t *testing.T) { workspace, workspaceCleanup := createWorkspace(t, client, orgTest) defer workspaceCleanup() - psTest1, psTestCleanup1 := createPolicySet(t, client, orgTest, nil, []*Workspace{workspace}) + psTest1, psTestCleanup1 := createPolicySet(t, client, orgTest, nil, []*Workspace{workspace}, "") defer psTestCleanup1() - psTest2, psTestCleanup2 := createPolicySet(t, client, orgTest, nil, []*Workspace{workspace}) + psTest2, psTestCleanup2 := createPolicySet(t, client, orgTest, nil, []*Workspace{workspace}, "") defer psTestCleanup2() + psTest3, psTestCleanup3 := createPolicySet(t, client, orgTest, nil, []*Workspace{workspace}, OPA) + defer psTestCleanup3() t.Run("without list options", func(t *testing.T) { psl, err := client.PolicySets.List(ctx, orgTest.Name, nil) @@ -40,8 +42,9 @@ func TestPolicySetsList(t *testing.T) { assert.Contains(t, psl.Items, psTest1) assert.Contains(t, psl.Items, psTest2) + assert.Contains(t, psl.Items, psTest3) assert.Equal(t, 1, psl.CurrentPage) - assert.Equal(t, 2, psl.TotalCount) + assert.Equal(t, 3, psl.TotalCount) }) t.Run("with pagination", func(t *testing.T) { @@ -58,7 +61,7 @@ func TestPolicySetsList(t *testing.T) { assert.Empty(t, psl.Items) assert.Equal(t, 999, psl.CurrentPage) - assert.Equal(t, 2, psl.TotalCount) + assert.Equal(t, 3, psl.TotalCount) }) t.Run("with search", func(t *testing.T) { @@ -81,7 +84,21 @@ func TestPolicySetsList(t *testing.T) { }) require.NoError(t, err) - assert.Equal(t, 2, len(psl.Items)) + assert.Equal(t, 3, len(psl.Items)) + + assert.NotNil(t, psl.Items[0].Workspaces) + assert.Equal(t, 1, len(psl.Items[0].Workspaces)) + assert.Equal(t, workspace.ID, psl.Items[0].Workspaces[0].ID) + }) + + t.Run("filter by kind", func(t *testing.T) { + psl, err := client.PolicySets.List(ctx, orgTest.Name, &PolicySetListOptions{ + Include: []PolicySetIncludeOpt{PolicySetWorkspaces}, + Kind: OPA, + }) + require.NoError(t, err) + + assert.Equal(t, 1, len(psl.Items)) assert.NotNil(t, psl.Items[0].Workspaces) assert.Equal(t, 1, len(psl.Items[0].Workspaces)) @@ -120,7 +137,7 @@ func TestPolicySetsCreate(t *testing.T) { assert.Equal(t, ps.Name, *options.Name) assert.Equal(t, ps.Description, "") - assert.Equal(t, ps.Kind, "opa") + assert.Equal(t, ps.Kind, OPA) assert.False(t, ps.Global) }) @@ -134,11 +151,11 @@ func TestPolicySetsCreate(t *testing.T) { assert.Equal(t, ps.Name, *options.Name) assert.Equal(t, ps.Description, "") - assert.Equal(t, ps.Kind, "sentinel") + assert.Equal(t, ps.Kind, Sentinel) assert.False(t, ps.Global) }) - t.Run("with all attributes provided", func(t *testing.T) { + t.Run("with all attributes provided - sentinel", func(t *testing.T) { options := PolicySetCreateOptions{ Name: String("global"), Description: String("Policies in this set will be checked in ALL workspaces!"), @@ -151,7 +168,44 @@ func TestPolicySetsCreate(t *testing.T) { assert.Equal(t, ps.Name, *options.Name) assert.Equal(t, ps.Description, *options.Description) - assert.Equal(t, ps.Kind, "sentinel") + assert.Equal(t, ps.Kind, Sentinel) + assert.True(t, ps.Global) + }) + + t.Run("with all attributes provided - OPA", func(t *testing.T) { + options := PolicySetCreateOptions{ + Name: String("global2"), + Description: String("Policies in this set will be checked in ALL workspaces!"), + Kind: OPA, + Overridable: Bool(true), + Global: Bool(true), + } + + ps, err := client.PolicySets.Create(ctx, orgTest.Name, options) + require.NoError(t, err) + + assert.Equal(t, ps.Name, *options.Name) + assert.Equal(t, ps.Description, *options.Description) + assert.Equal(t, ps.Overridable, *options.Overridable) + assert.Equal(t, ps.Kind, OPA) + assert.True(t, ps.Global) + }) + + t.Run("with missing overridable attribute", func(t *testing.T) { + options := PolicySetCreateOptions{ + Name: String("global3"), + Description: String("Policies in this set will be checked in ALL workspaces!"), + Kind: OPA, + Global: Bool(true), + } + + ps, err := client.PolicySets.Create(ctx, orgTest.Name, options) + require.NoError(t, err) + + assert.Equal(t, ps.Name, *options.Name) + assert.Equal(t, ps.Description, *options.Description) + assert.Equal(t, ps.Overridable, false) + assert.Equal(t, ps.Kind, OPA) assert.True(t, ps.Global) }) @@ -175,7 +229,7 @@ func TestPolicySetsCreate(t *testing.T) { assert.Equal(t, ps.PolicyCount, 1) assert.Equal(t, ps.Policies[0].ID, pTest.ID) assert.Equal(t, ps.WorkspaceCount, 1) - assert.Equal(t, ps.Kind, "sentinel") + assert.Equal(t, ps.Kind, Sentinel) assert.Equal(t, ps.Workspaces[0].ID, wTest.ID) }) @@ -211,7 +265,7 @@ func TestPolicySetsCreate(t *testing.T) { assert.False(t, ps.Global) assert.Equal(t, ps.PoliciesPath, "/policy-sets/foo") assert.Equal(t, ps.VCSRepo.Branch, "policies") - assert.Equal(t, ps.Kind, "sentinel") + assert.Equal(t, ps.Kind, Sentinel) assert.Equal(t, ps.VCSRepo.DisplayIdentifier, githubIdentifier) assert.Equal(t, ps.VCSRepo.Identifier, githubIdentifier) assert.Equal(t, ps.VCSRepo.IngressSubmodules, true) @@ -293,7 +347,7 @@ func TestPolicySetsRead(t *testing.T) { upgradeOrganizationSubscription(t, client, orgTest) - psTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, nil) + psTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, nil, "") defer psTestCleanup() t.Run("with a valid ID", func(t *testing.T) { @@ -362,7 +416,7 @@ func TestPolicySetsUpdate(t *testing.T) { upgradeOrganizationSubscription(t, client, orgTest) - psTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, nil) + psTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, nil, "") defer psTestCleanup() t.Run("with valid attributes", func(t *testing.T) { @@ -413,7 +467,7 @@ func TestPolicySetsAddPolicies(t *testing.T) { defer pTestCleanup1() pTest2, pTestCleanup2 := createPolicy(t, client, orgTest) defer pTestCleanup2() - psTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, nil) + psTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, nil, "") defer psTestCleanup() t.Run("with policies provided", func(t *testing.T) { @@ -471,7 +525,7 @@ func TestPolicySetsRemovePolicies(t *testing.T) { defer pTestCleanup1() pTest2, pTestCleanup2 := createPolicy(t, client, orgTest) defer pTestCleanup2() - psTest, psTestCleanup := createPolicySet(t, client, orgTest, []*Policy{pTest1, pTest2}, nil) + psTest, psTestCleanup := createPolicySet(t, client, orgTest, []*Policy{pTest1, pTest2}, nil, "") defer psTestCleanup() t.Run("with policies provided", func(t *testing.T) { @@ -523,7 +577,7 @@ func TestPolicySetsAddWorkspaces(t *testing.T) { defer wTestCleanup1() wTest2, wTestCleanup2 := createWorkspace(t, client, orgTest) defer wTestCleanup2() - psTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, nil) + psTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, nil, "") defer psTestCleanup() t.Run("with workspaces provided", func(t *testing.T) { @@ -595,7 +649,7 @@ func TestPolicySetsRemoveWorkspaces(t *testing.T) { defer wTestCleanup1() wTest2, wTestCleanup2 := createWorkspace(t, client, orgTest) defer wTestCleanup2() - psTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, []*Workspace{wTest1, wTest2}) + psTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, []*Workspace{wTest1, wTest2}, "") defer psTestCleanup() t.Run("with workspaces provided", func(t *testing.T) { @@ -657,7 +711,7 @@ func TestPolicySetsDelete(t *testing.T) { upgradeOrganizationSubscription(t, client, orgTest) - psTest, _ := createPolicySet(t, client, orgTest, nil, nil) + psTest, _ := createPolicySet(t, client, orgTest, nil, nil, "") t.Run("with valid options", func(t *testing.T) { err := client.PolicySets.Delete(ctx, psTest.ID) diff --git a/policy_set_parameter_integration_test.go b/policy_set_parameter_integration_test.go index 8d8f97cae..d630314d3 100644 --- a/policy_set_parameter_integration_test.go +++ b/policy_set_parameter_integration_test.go @@ -21,7 +21,7 @@ func TestPolicySetParametersList(t *testing.T) { orgTest, orgTestCleanup := createOrganization(t, client) defer orgTestCleanup() - psTest, pTestCleanup := createPolicySet(t, client, orgTest, nil, nil) + psTest, pTestCleanup := createPolicySet(t, client, orgTest, nil, nil, "") defer pTestCleanup() pTest1, pTestCleanup1 := createPolicySetParameter(t, client, psTest) @@ -71,7 +71,7 @@ func TestPolicySetParametersCreate(t *testing.T) { client := testClient(t) ctx := context.Background() - psTest, psTestCleanup := createPolicySet(t, client, nil, nil, nil) + psTest, psTestCleanup := createPolicySet(t, client, nil, nil, nil, "") defer psTestCleanup() t.Run("with valid options", func(t *testing.T) { @@ -281,7 +281,7 @@ func TestPolicySetParametersDelete(t *testing.T) { client := testClient(t) ctx := context.Background() - psTest, psTestCleanup := createPolicySet(t, client, nil, nil, nil) + psTest, psTestCleanup := createPolicySet(t, client, nil, nil, nil, "") defer psTestCleanup() pTest, _ := createPolicySetParameter(t, client, psTest) diff --git a/policy_set_version_integration_test.go b/policy_set_version_integration_test.go index 1094ad85b..ad0c4d3c2 100644 --- a/policy_set_version_integration_test.go +++ b/policy_set_version_integration_test.go @@ -21,7 +21,7 @@ func TestPolicySetVersionsCreate(t *testing.T) { client := testClient(t) ctx := context.Background() - psTest, psTestCleanup := createPolicySet(t, client, nil, nil, nil) + psTest, psTestCleanup := createPolicySet(t, client, nil, nil, nil, "") defer psTestCleanup() t.Run("with valid identifier", func(t *testing.T) { @@ -46,7 +46,7 @@ func TestPolicySetVersionsRead(t *testing.T) { client := testClient(t) ctx := context.Background() - psTest, psTestCleanup := createPolicySet(t, client, nil, nil, nil) + psTest, psTestCleanup := createPolicySet(t, client, nil, nil, nil, "") defer psTestCleanup() origPSV, err := client.PolicySetVersions.Create(ctx, psTest.ID) From 08d0173150c7fd2182906fbe86255b4aad70fed8 Mon Sep 17 00:00:00 2001 From: mrinalirao Date: Wed, 2 Nov 2022 08:51:44 +1100 Subject: [PATCH 3/6] seperate tests for beta feature --- policy_set_integration_beta_test.go | 335 ++++++++++++++++++++++++++++ policy_set_integration_test.go | 84 +------ 2 files changed, 339 insertions(+), 80 deletions(-) create mode 100644 policy_set_integration_beta_test.go diff --git a/policy_set_integration_beta_test.go b/policy_set_integration_beta_test.go new file mode 100644 index 000000000..5483de223 --- /dev/null +++ b/policy_set_integration_beta_test.go @@ -0,0 +1,335 @@ +package tfe + +import ( + "context" + "fmt" + "os" + "regexp" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPolicySetsList_Beta(t *testing.T) { + skipIfNotCINode(t) + skipIfFreeOnly(t) + skipIfBeta(t) + + client := testClient(t) + ctx := context.Background() + + orgTest, orgTestCleanup := createOrganization(t, client) + defer orgTestCleanup() + + upgradeOrganizationSubscription(t, client, orgTest) + + workspace, workspaceCleanup := createWorkspace(t, client, orgTest) + defer workspaceCleanup() + + psTest1, psTestCleanup1 := createPolicySet(t, client, orgTest, nil, []*Workspace{workspace}, "") + defer psTestCleanup1() + psTest2, psTestCleanup2 := createPolicySet(t, client, orgTest, nil, []*Workspace{workspace}, "") + defer psTestCleanup2() + psTest3, psTestCleanup3 := createPolicySet(t, client, orgTest, nil, []*Workspace{workspace}, OPA) + defer psTestCleanup3() + + t.Run("without list options", func(t *testing.T) { + psl, err := client.PolicySets.List(ctx, orgTest.Name, nil) + require.NoError(t, err) + + assert.Contains(t, psl.Items, psTest1) + assert.Contains(t, psl.Items, psTest2) + assert.Contains(t, psl.Items, psTest3) + assert.Equal(t, 1, psl.CurrentPage) + assert.Equal(t, 3, psl.TotalCount) + }) + + t.Run("with pagination", func(t *testing.T) { + // Request a page number which is out of range. The result should + // be successful, but return no results if the paging options are + // properly passed along. + psl, err := client.PolicySets.List(ctx, orgTest.Name, &PolicySetListOptions{ + ListOptions: ListOptions{ + PageNumber: 999, + PageSize: 100, + }, + }) + require.NoError(t, err) + + assert.Empty(t, psl.Items) + assert.Equal(t, 999, psl.CurrentPage) + assert.Equal(t, 3, psl.TotalCount) + }) + + t.Run("with search", func(t *testing.T) { + // Search by one of the policy set's names; we should get only that policy + // set and pagination data should reflect the search as well + psl, err := client.PolicySets.List(ctx, orgTest.Name, &PolicySetListOptions{ + Search: psTest1.Name, + }) + require.NoError(t, err) + + assert.Contains(t, psl.Items, psTest1) + assert.NotContains(t, psl.Items, psTest2) + assert.Equal(t, 1, psl.CurrentPage) + assert.Equal(t, 1, psl.TotalCount) + }) + + t.Run("with include param", func(t *testing.T) { + psl, err := client.PolicySets.List(ctx, orgTest.Name, &PolicySetListOptions{ + Include: []PolicySetIncludeOpt{PolicySetWorkspaces}, + }) + require.NoError(t, err) + + assert.Equal(t, 3, len(psl.Items)) + + assert.NotNil(t, psl.Items[0].Workspaces) + assert.Equal(t, 1, len(psl.Items[0].Workspaces)) + assert.Equal(t, workspace.ID, psl.Items[0].Workspaces[0].ID) + }) + + t.Run("filter by kind", func(t *testing.T) { + psl, err := client.PolicySets.List(ctx, orgTest.Name, &PolicySetListOptions{ + Include: []PolicySetIncludeOpt{PolicySetWorkspaces}, + Kind: OPA, + }) + require.NoError(t, err) + + assert.Equal(t, 1, len(psl.Items)) + + assert.NotNil(t, psl.Items[0].Workspaces) + assert.Equal(t, 1, len(psl.Items[0].Workspaces)) + assert.Equal(t, workspace.ID, psl.Items[0].Workspaces[0].ID) + }) + + t.Run("without a valid organization", func(t *testing.T) { + ps, err := client.PolicySets.List(ctx, badIdentifier, nil) + assert.Nil(t, ps) + assert.EqualError(t, err, ErrInvalidOrg.Error()) + }) +} + +func TestPolicySetsCreate_Beta(t *testing.T) { + skipIfNotCINode(t) + skipIfFreeOnly(t) + skipIfBeta(t) + + client := testClient(t) + ctx := context.Background() + + orgTest, orgTestCleanup := createOrganization(t, client) + defer orgTestCleanup() + + upgradeOrganizationSubscription(t, client, orgTest) + + var vcsPolicyID string + + t.Run("with valid attributes", func(t *testing.T) { + options := PolicySetCreateOptions{ + Name: String("policy-set"), + Kind: OPA, + } + + ps, err := client.PolicySets.Create(ctx, orgTest.Name, options) + require.NoError(t, err) + + assert.Equal(t, ps.Name, *options.Name) + assert.Equal(t, ps.Description, "") + assert.Equal(t, ps.Kind, OPA) + assert.False(t, ps.Global) + }) + + t.Run("with kind missing", func(t *testing.T) { + options := PolicySetCreateOptions{ + Name: String("policy-set1"), + } + + ps, err := client.PolicySets.Create(ctx, orgTest.Name, options) + require.NoError(t, err) + + assert.Equal(t, ps.Name, *options.Name) + assert.Equal(t, ps.Description, "") + assert.Equal(t, ps.Kind, Sentinel) + assert.False(t, ps.Global) + }) + + t.Run("with all attributes provided - sentinel", func(t *testing.T) { + options := PolicySetCreateOptions{ + Name: String("global"), + Description: String("Policies in this set will be checked in ALL workspaces!"), + Kind: Sentinel, + Global: Bool(true), + } + + ps, err := client.PolicySets.Create(ctx, orgTest.Name, options) + require.NoError(t, err) + + assert.Equal(t, ps.Name, *options.Name) + assert.Equal(t, ps.Description, *options.Description) + assert.Equal(t, ps.Kind, Sentinel) + assert.True(t, ps.Global) + }) + + t.Run("with all attributes provided - OPA", func(t *testing.T) { + options := PolicySetCreateOptions{ + Name: String("global1"), + Description: String("Policies in this set will be checked in ALL workspaces!"), + Kind: OPA, + Overridable: Bool(true), + Global: Bool(true), + } + + ps, err := client.PolicySets.Create(ctx, orgTest.Name, options) + require.NoError(t, err) + + assert.Equal(t, ps.Name, *options.Name) + assert.Equal(t, ps.Description, *options.Description) + assert.Equal(t, ps.Overridable, *options.Overridable) + assert.Equal(t, ps.Kind, OPA) + assert.True(t, ps.Global) + }) + + t.Run("with missing overridable attribute", func(t *testing.T) { + options := PolicySetCreateOptions{ + Name: String("global2"), + Description: String("Policies in this set will be checked in ALL workspaces!"), + Kind: OPA, + Global: Bool(true), + } + + ps, err := client.PolicySets.Create(ctx, orgTest.Name, options) + require.NoError(t, err) + + assert.Equal(t, ps.Name, *options.Name) + assert.Equal(t, ps.Description, *options.Description) + assert.Equal(t, ps.Overridable, false) + assert.Equal(t, ps.Kind, OPA) + assert.True(t, ps.Global) + }) + + t.Run("with policies and workspaces provided", func(t *testing.T) { + pTest, pTestCleanup := createPolicy(t, client, orgTest) + defer pTestCleanup() + wTest, wTestCleanup := createWorkspace(t, client, orgTest) + defer wTestCleanup() + + options := PolicySetCreateOptions{ + Name: String("populated-policy-set"), + Policies: []*Policy{pTest}, + Kind: Sentinel, + Workspaces: []*Workspace{wTest}, + } + + ps, err := client.PolicySets.Create(ctx, orgTest.Name, options) + require.NoError(t, err) + + assert.Equal(t, ps.Name, *options.Name) + assert.Equal(t, ps.PolicyCount, 1) + assert.Equal(t, ps.Policies[0].ID, pTest.ID) + assert.Equal(t, ps.WorkspaceCount, 1) + assert.Equal(t, ps.Kind, Sentinel) + assert.Equal(t, ps.Workspaces[0].ID, wTest.ID) + }) + + t.Run("with vcs policy set", func(t *testing.T) { + githubIdentifier := os.Getenv("GITHUB_POLICY_SET_IDENTIFIER") + if githubIdentifier == "" { + t.Skip("Export a valid GITHUB_POLICY_SET_IDENTIFIER before running this test") + } + + oc, ocTestCleanup := createOAuthToken(t, client, orgTest) + defer ocTestCleanup() + + options := PolicySetCreateOptions{ + Name: String("vcs-policy-set1"), + Kind: Sentinel, + PoliciesPath: String("/policy-sets/foo"), + VCSRepo: &VCSRepoOptions{ + Branch: String("policies"), + Identifier: String(githubIdentifier), + OAuthTokenID: String(oc.ID), + IngressSubmodules: Bool(true), + }, + } + + ps, err := client.PolicySets.Create(ctx, orgTest.Name, options) + require.NoError(t, err) + + // Save policy ID to be used by update func + vcsPolicyID = ps.ID + + assert.Equal(t, ps.Name, *options.Name) + assert.Equal(t, ps.Description, "") + assert.False(t, ps.Global) + assert.Equal(t, ps.PoliciesPath, "/policy-sets/foo") + assert.Equal(t, ps.VCSRepo.Branch, "policies") + assert.Equal(t, ps.Kind, Sentinel) + assert.Equal(t, ps.VCSRepo.DisplayIdentifier, githubIdentifier) + assert.Equal(t, ps.VCSRepo.Identifier, githubIdentifier) + assert.Equal(t, ps.VCSRepo.IngressSubmodules, true) + assert.Equal(t, ps.VCSRepo.OAuthTokenID, oc.ID) + assert.Equal(t, ps.VCSRepo.RepositoryHTTPURL, fmt.Sprintf("https://github.com/%s", githubIdentifier)) + assert.Equal(t, ps.VCSRepo.ServiceProvider, string(ServiceProviderGithub)) + assert.Regexp(t, fmt.Sprintf("^%s/webhooks/vcs/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", regexp.QuoteMeta(DefaultConfig().Address)), ps.VCSRepo.WebhookURL) + }) + + t.Run("with vcs policy updated", func(t *testing.T) { + githubIdentifier := os.Getenv("GITHUB_POLICY_SET_IDENTIFIER") + if githubIdentifier == "" { + t.Skip("Export a valid GITHUB_POLICY_SET_IDENTIFIER before running this test") + } + + oc, ocTestCleanup := createOAuthToken(t, client, orgTest) + defer ocTestCleanup() + + options := PolicySetUpdateOptions{ + Name: String("vcs-policy-set"), + PoliciesPath: String("/policy-sets/bar"), + VCSRepo: &VCSRepoOptions{ + Branch: String("policies"), + Identifier: String(githubIdentifier), + OAuthTokenID: String(oc.ID), + IngressSubmodules: Bool(false), + }, + } + + ps, err := client.PolicySets.Update(ctx, vcsPolicyID, options) + require.NoError(t, err) + + assert.Equal(t, ps.Name, *options.Name) + assert.Equal(t, ps.Description, "") + assert.False(t, ps.Global) + assert.Equal(t, ps.PoliciesPath, "/policy-sets/bar") + assert.Equal(t, ps.VCSRepo.Branch, "policies") + assert.Equal(t, ps.VCSRepo.DisplayIdentifier, githubIdentifier) + assert.Equal(t, ps.VCSRepo.Identifier, githubIdentifier) + assert.Equal(t, ps.VCSRepo.IngressSubmodules, false) + assert.Equal(t, ps.VCSRepo.OAuthTokenID, oc.ID) + assert.Equal(t, ps.VCSRepo.RepositoryHTTPURL, fmt.Sprintf("https://github.com/%s", githubIdentifier)) + assert.Equal(t, ps.VCSRepo.ServiceProvider, string(ServiceProviderGithub)) + assert.Regexp(t, fmt.Sprintf("^%s/webhooks/vcs/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", regexp.QuoteMeta(DefaultConfig().Address)), ps.VCSRepo.WebhookURL) + }) + + t.Run("without a name provided", func(t *testing.T) { + ps, err := client.PolicySets.Create(ctx, orgTest.Name, PolicySetCreateOptions{}) + assert.Nil(t, ps) + assert.EqualError(t, err, ErrRequiredName.Error()) + }) + + t.Run("with an invalid name provided", func(t *testing.T) { + ps, err := client.PolicySets.Create(ctx, orgTest.Name, PolicySetCreateOptions{ + Name: String("nope!"), + }) + assert.Nil(t, ps) + assert.EqualError(t, err, ErrInvalidName.Error()) + }) + + t.Run("without a valid organization", func(t *testing.T) { + ps, err := client.PolicySets.Create(ctx, badIdentifier, PolicySetCreateOptions{ + Name: String("policy-set"), + }) + assert.Nil(t, ps) + assert.EqualError(t, err, ErrInvalidOrg.Error()) + }) +} diff --git a/policy_set_integration_test.go b/policy_set_integration_test.go index 72d278276..ee4e08878 100644 --- a/policy_set_integration_test.go +++ b/policy_set_integration_test.go @@ -33,8 +33,6 @@ func TestPolicySetsList(t *testing.T) { defer psTestCleanup1() psTest2, psTestCleanup2 := createPolicySet(t, client, orgTest, nil, []*Workspace{workspace}, "") defer psTestCleanup2() - psTest3, psTestCleanup3 := createPolicySet(t, client, orgTest, nil, []*Workspace{workspace}, OPA) - defer psTestCleanup3() t.Run("without list options", func(t *testing.T) { psl, err := client.PolicySets.List(ctx, orgTest.Name, nil) @@ -42,9 +40,8 @@ func TestPolicySetsList(t *testing.T) { assert.Contains(t, psl.Items, psTest1) assert.Contains(t, psl.Items, psTest2) - assert.Contains(t, psl.Items, psTest3) assert.Equal(t, 1, psl.CurrentPage) - assert.Equal(t, 3, psl.TotalCount) + assert.Equal(t, 2, psl.TotalCount) }) t.Run("with pagination", func(t *testing.T) { @@ -61,7 +58,7 @@ func TestPolicySetsList(t *testing.T) { assert.Empty(t, psl.Items) assert.Equal(t, 999, psl.CurrentPage) - assert.Equal(t, 3, psl.TotalCount) + assert.Equal(t, 2, psl.TotalCount) }) t.Run("with search", func(t *testing.T) { @@ -84,21 +81,7 @@ func TestPolicySetsList(t *testing.T) { }) require.NoError(t, err) - assert.Equal(t, 3, len(psl.Items)) - - assert.NotNil(t, psl.Items[0].Workspaces) - assert.Equal(t, 1, len(psl.Items[0].Workspaces)) - assert.Equal(t, workspace.ID, psl.Items[0].Workspaces[0].ID) - }) - - t.Run("filter by kind", func(t *testing.T) { - psl, err := client.PolicySets.List(ctx, orgTest.Name, &PolicySetListOptions{ - Include: []PolicySetIncludeOpt{PolicySetWorkspaces}, - Kind: OPA, - }) - require.NoError(t, err) - - assert.Equal(t, 1, len(psl.Items)) + assert.Equal(t, 2, len(psl.Items)) assert.NotNil(t, psl.Items[0].Workspaces) assert.Equal(t, 1, len(psl.Items[0].Workspaces)) @@ -129,21 +112,6 @@ func TestPolicySetsCreate(t *testing.T) { t.Run("with valid attributes", func(t *testing.T) { options := PolicySetCreateOptions{ Name: String("policy-set"), - Kind: OPA, - } - - ps, err := client.PolicySets.Create(ctx, orgTest.Name, options) - require.NoError(t, err) - - assert.Equal(t, ps.Name, *options.Name) - assert.Equal(t, ps.Description, "") - assert.Equal(t, ps.Kind, OPA) - assert.False(t, ps.Global) - }) - - t.Run("with kind missing", func(t *testing.T) { - options := PolicySetCreateOptions{ - Name: String("policy-set2"), } ps, err := client.PolicySets.Create(ctx, orgTest.Name, options) @@ -151,51 +119,13 @@ func TestPolicySetsCreate(t *testing.T) { assert.Equal(t, ps.Name, *options.Name) assert.Equal(t, ps.Description, "") - assert.Equal(t, ps.Kind, Sentinel) assert.False(t, ps.Global) }) - t.Run("with all attributes provided - sentinel", func(t *testing.T) { + t.Run("with all attributes provided", func(t *testing.T) { options := PolicySetCreateOptions{ Name: String("global"), Description: String("Policies in this set will be checked in ALL workspaces!"), - Kind: Sentinel, - Global: Bool(true), - } - - ps, err := client.PolicySets.Create(ctx, orgTest.Name, options) - require.NoError(t, err) - - assert.Equal(t, ps.Name, *options.Name) - assert.Equal(t, ps.Description, *options.Description) - assert.Equal(t, ps.Kind, Sentinel) - assert.True(t, ps.Global) - }) - - t.Run("with all attributes provided - OPA", func(t *testing.T) { - options := PolicySetCreateOptions{ - Name: String("global2"), - Description: String("Policies in this set will be checked in ALL workspaces!"), - Kind: OPA, - Overridable: Bool(true), - Global: Bool(true), - } - - ps, err := client.PolicySets.Create(ctx, orgTest.Name, options) - require.NoError(t, err) - - assert.Equal(t, ps.Name, *options.Name) - assert.Equal(t, ps.Description, *options.Description) - assert.Equal(t, ps.Overridable, *options.Overridable) - assert.Equal(t, ps.Kind, OPA) - assert.True(t, ps.Global) - }) - - t.Run("with missing overridable attribute", func(t *testing.T) { - options := PolicySetCreateOptions{ - Name: String("global3"), - Description: String("Policies in this set will be checked in ALL workspaces!"), - Kind: OPA, Global: Bool(true), } @@ -204,8 +134,6 @@ func TestPolicySetsCreate(t *testing.T) { assert.Equal(t, ps.Name, *options.Name) assert.Equal(t, ps.Description, *options.Description) - assert.Equal(t, ps.Overridable, false) - assert.Equal(t, ps.Kind, OPA) assert.True(t, ps.Global) }) @@ -218,7 +146,6 @@ func TestPolicySetsCreate(t *testing.T) { options := PolicySetCreateOptions{ Name: String("populated-policy-set"), Policies: []*Policy{pTest}, - Kind: Sentinel, Workspaces: []*Workspace{wTest}, } @@ -229,7 +156,6 @@ func TestPolicySetsCreate(t *testing.T) { assert.Equal(t, ps.PolicyCount, 1) assert.Equal(t, ps.Policies[0].ID, pTest.ID) assert.Equal(t, ps.WorkspaceCount, 1) - assert.Equal(t, ps.Kind, Sentinel) assert.Equal(t, ps.Workspaces[0].ID, wTest.ID) }) @@ -244,7 +170,6 @@ func TestPolicySetsCreate(t *testing.T) { options := PolicySetCreateOptions{ Name: String("vcs-policy-set"), - Kind: Sentinel, PoliciesPath: String("/policy-sets/foo"), VCSRepo: &VCSRepoOptions{ Branch: String("policies"), @@ -265,7 +190,6 @@ func TestPolicySetsCreate(t *testing.T) { assert.False(t, ps.Global) assert.Equal(t, ps.PoliciesPath, "/policy-sets/foo") assert.Equal(t, ps.VCSRepo.Branch, "policies") - assert.Equal(t, ps.Kind, Sentinel) assert.Equal(t, ps.VCSRepo.DisplayIdentifier, githubIdentifier) assert.Equal(t, ps.VCSRepo.Identifier, githubIdentifier) assert.Equal(t, ps.VCSRepo.IngressSubmodules, true) From 7bc25d51f6d0334a417006a55b586c18ffcb377a Mon Sep 17 00:00:00 2001 From: mrinalirao Date: Wed, 2 Nov 2022 09:44:28 +1100 Subject: [PATCH 4/6] add comments to beta OPA attributes --- policy_set.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/policy_set.go b/policy_set.go index 589922285..da145f6f8 100644 --- a/policy_set.go +++ b/policy_set.go @@ -116,6 +116,7 @@ type PolicySetListOptions struct { // Optional: A search string (partial policy set name) used to filter the results. Search string `url:"search[name],omitempty"` + // **Note: This field is still in BETA and subject to change.** // Optional: A kind string used to filter the results by the policy set kind. Kind PolicyKind `url:"filter[kind],omitempty"` @@ -150,9 +151,11 @@ type PolicySetCreateOptions struct { // Optional: Whether or not the policy set is global. Global *bool `jsonapi:"attr,global,omitempty"` + // **Note: This field is still in BETA and subject to change.** // Optional: The underlying technology that the policy set supports Kind PolicyKind `jsonapi:"attr,kind,omitempty"` + // **Note: This field is still in BETA and subject to change.** // Optional: Whether or not users can override this policy when it fails during a run. Only valid for OPA policies. Overridable *bool `jsonapi:"attr,overridable,omitempty"` From 063f7ad33de199a5b4d11b2d0b91f15d0454aa37 Mon Sep 17 00:00:00 2001 From: mrinalirao Date: Wed, 2 Nov 2022 10:14:55 +1100 Subject: [PATCH 5/6] change overridable to a pointer and fix tests --- policy_set.go | 2 +- policy_set_integration_beta_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/policy_set.go b/policy_set.go index da145f6f8..637488747 100644 --- a/policy_set.go +++ b/policy_set.go @@ -74,7 +74,7 @@ type PolicySet struct { Name string `jsonapi:"attr,name"` Description string `jsonapi:"attr,description"` Kind PolicyKind `jsonapi:"attr,kind"` - Overridable bool `jsonapi:"attr,overridable"` + Overridable *bool `jsonapi:"attr,overridable"` Global bool `jsonapi:"attr,global"` PoliciesPath string `jsonapi:"attr,policies-path"` PolicyCount int `jsonapi:"attr,policy-count"` diff --git a/policy_set_integration_beta_test.go b/policy_set_integration_beta_test.go index 5483de223..7fa3affe8 100644 --- a/policy_set_integration_beta_test.go +++ b/policy_set_integration_beta_test.go @@ -185,7 +185,7 @@ func TestPolicySetsCreate_Beta(t *testing.T) { assert.Equal(t, ps.Name, *options.Name) assert.Equal(t, ps.Description, *options.Description) - assert.Equal(t, ps.Overridable, *options.Overridable) + assert.Equal(t, ps.Overridable, options.Overridable) assert.Equal(t, ps.Kind, OPA) assert.True(t, ps.Global) }) @@ -203,7 +203,7 @@ func TestPolicySetsCreate_Beta(t *testing.T) { assert.Equal(t, ps.Name, *options.Name) assert.Equal(t, ps.Description, *options.Description) - assert.Equal(t, ps.Overridable, false) + assert.Equal(t, ps.Overridable, Bool(false)) assert.Equal(t, ps.Kind, OPA) assert.True(t, ps.Global) }) From f656b579e23e77cd01700a2d7d27a1249fde54ee Mon Sep 17 00:00:00 2001 From: mrinalirao Date: Thu, 3 Nov 2022 08:45:32 +1100 Subject: [PATCH 6/6] Add CHANELOG entry and fix minor typo --- CHANGELOG.md | 1 + policy_set.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db2a2088f..2f0f366bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Enhancements * Add `NotificationTriggerAssessmentCheckFailed` notification trigger type by @rexredinger [#549](https://github.com/hashicorp/go-tfe/pull/549) +* Add OPA support to the Policy Set API's by @mrinalirao [#575](https://github.com/hashicorp/go-tfe/pull/575) # v1.11.0 diff --git a/policy_set.go b/policy_set.go index 637488747..663c83484 100644 --- a/policy_set.go +++ b/policy_set.go @@ -11,7 +11,7 @@ import ( var _ PolicySets = (*policySets)(nil) // PolicyKind is an indicator of the underlying technology that the policy or policy set supports. -// There are two Policykinds documented in the enum. +// There are two kinds documented in the enum. type PolicyKind string const (