Skip to content

Commit 2133d39

Browse files
authored
Feat: New ABAC Attributes (#114)
## What * `codefresh_permission.resource` : `project` is a new possible value * `codefresh_permission.related_resource` — new attribute; only possible value is project * overhaul validation in `codefresh_permission` * Fix `ImportStateVerify` issue related to hashicorp/terraform-provider-aws#27049 ## Why New ABAC behavior in CF API ## Notes <!-- Add any notes here --> ## Checklist * [x] _I have read [CONTRIBUTING.md](https://github.com/codefresh-io/terraform-provider-codefresh/blob/master/README.md)._ * [x] _I have [allowed changes to my fork to be made](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork)._ * [x] _I have added tests, assuming new tests are warranted_. * [x] _I understand that the `/test` comment will be ignored by the CI trigger [unless it is made by a repo admin or collaborator](https://codefresh.io/docs/docs/pipelines/triggers/git-triggers/#support-for-building-pull-requests-from-forks)._
1 parent 93243ac commit 2133d39

10 files changed

+988
-495
lines changed

client/permission.go

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,30 @@ package client
22

33
import (
44
"fmt"
5-
//"log"
65
)
76

87
// Permission spec
98
type Permission struct {
10-
ID string `json:"id,omitempty"`
11-
Team string `json:"role,omitempty"`
12-
Resource string `json:"resource,omitempty"`
13-
Action string `json:"action,omitempty"`
14-
Account string `json:"account,omitempty"`
15-
Tags []string `json:"attributes,omitempty"`
9+
ID string `json:"id,omitempty"`
10+
Team string `json:"role,omitempty"`
11+
Resource string `json:"resource,omitempty"`
12+
RelatedResource string `json:"related_resource,omitempty"`
13+
Action string `json:"action,omitempty"`
14+
Account string `json:"account,omitempty"`
15+
Tags []string `json:"attributes,omitempty"`
1616
}
1717

18-
// NewPermission spec, diffs from Permission is `json:"team,omitempty"` vs `json:"role,omitempty"`
18+
// NewPermission spec, diffs from Permission: `json:"_id,omitempty"`, `json:"team,omitempty"`, `json:"tags,omitempty"`
1919
type NewPermission struct {
20-
ID string `json:"_id,omitempty"`
21-
Team string `json:"team,omitempty"`
22-
Resource string `json:"resource,omitempty"`
23-
Action string `json:"action,omitempty"`
24-
Account string `json:"account,omitempty"`
25-
Tags []string `json:"tags,omitempty"`
20+
ID string `json:"_id,omitempty"`
21+
Team string `json:"team,omitempty"`
22+
Resource string `json:"resource,omitempty"`
23+
RelatedResource string `json:"related_resource,omitempty"`
24+
Action string `json:"action,omitempty"`
25+
Account string `json:"account,omitempty"`
26+
Tags []string `json:"tags,omitempty"`
2627
}
2728

28-
// GetPermissionList -
2929
func (client *Client) GetPermissionList(teamID, action, resource string) ([]Permission, error) {
3030
fullPath := "/abac"
3131
opts := RequestOptions{
@@ -84,16 +84,16 @@ func (client *Client) GetPermissionByID(id string) (*Permission, error) {
8484
return &permission, nil
8585
}
8686

87-
// CreatePermision -
8887
func (client *Client) CreatePermission(permission *Permission) (*Permission, error) {
8988

9089
newPermission := &NewPermission{
91-
ID: permission.ID,
92-
Team: permission.Team,
93-
Resource: permission.Resource,
94-
Action: permission.Action,
95-
Account: permission.Account,
96-
Tags: permission.Tags,
90+
ID: permission.ID,
91+
Team: permission.Team,
92+
Resource: permission.Resource,
93+
RelatedResource: permission.RelatedResource,
94+
Action: permission.Action,
95+
Account: permission.Account,
96+
Tags: permission.Tags,
9797
}
9898

9999
body, err := EncodeToJSON(newPermission)
@@ -113,8 +113,6 @@ func (client *Client) CreatePermission(permission *Permission) (*Permission, err
113113
return nil, err
114114
}
115115

116-
// respStr := string(resp)
117-
// log.Printf("[DEBUG] createPermission responce body = %s", respStr)
118116
var permissionResp []Permission
119117
err = DecodeResponseInto(resp, &permissionResp)
120118
if err != nil {
@@ -129,7 +127,6 @@ func (client *Client) CreatePermission(permission *Permission) (*Permission, err
129127
return client.GetPermissionByID(newPermissionID)
130128
}
131129

132-
// DeletePermission -
133130
func (client *Client) DeletePermission(id string) error {
134131
fullPath := fmt.Sprintf("/abac/%s", id)
135132
opts := RequestOptions{

codefresh/resource_context_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ func testAccCheckCodefreshContextExists(resource string) resource.TestCheckFunc
162162
_, err := apiClient.GetContext(contextID)
163163

164164
if err != nil {
165-
return fmt.Errorf("error fetching context with resource %s. %s", resource, err)
165+
return fmt.Errorf("error fetching context with ID %s. %s", contextID, err)
166166
}
167167
return nil
168168
}

codefresh/resource_permission.go

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package codefresh
22

33
import (
4+
"context"
45
"fmt"
56
"log"
67

78
cfClient "github.com/codefresh-io/terraform-provider-codefresh/client"
89
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
11+
funk "github.com/thoas/go-funk"
912
)
1013

1114
func resourcePermission() *schema.Resource {
@@ -35,16 +38,26 @@ func resourcePermission() *schema.Resource {
3538
The type of resources the permission applies to. Possible values:
3639
* pipeline
3740
* cluster
41+
* project
3842
`,
3943
Type: schema.TypeString,
4044
Required: true,
41-
ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) {
42-
v := val.(string)
43-
if v != "cluster" && v != "pipeline" {
44-
errs = append(errs, fmt.Errorf("%q must be between \"pipeline\" or \"cluster\", got: %s", key, v))
45-
}
46-
return
47-
},
45+
ValidateFunc: validation.StringInSlice([]string{
46+
"pipeline",
47+
"cluster",
48+
"project",
49+
}, false),
50+
},
51+
"related_resource": {
52+
Description: `
53+
Specifies the resource to use when evaluating the tags. Possible values:
54+
* project
55+
`,
56+
Type: schema.TypeString,
57+
Optional: true,
58+
ValidateFunc: validation.StringInSlice([]string{
59+
"project",
60+
}, false),
4861
},
4962
"action": {
5063
Description: `
@@ -59,13 +72,15 @@ Action to be allowed. Possible values:
5972
`,
6073
Type: schema.TypeString,
6174
Required: true,
62-
ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) {
63-
v := val.(string)
64-
if v != "create" && v != "read" && v != "update" && v != "delete" && v != "run" && v != "approve" && v != "debug" {
65-
errs = append(errs, fmt.Errorf("%q must be between one of create,read,update,delete,approve,debug got: %s", key, v))
66-
}
67-
return
68-
},
75+
ValidateFunc: validation.StringInSlice([]string{
76+
"create",
77+
"read",
78+
"update",
79+
"delete",
80+
"run",
81+
"approve",
82+
"debug",
83+
}, false),
6984
},
7085
"tags": {
7186
Description: `
@@ -80,9 +95,24 @@ The effective tags to apply the permission. It supports 2 custom tags:
8095
},
8196
},
8297
},
98+
CustomizeDiff: resourcePermissionCustomDiff,
8399
}
84100
}
85101

102+
func resourcePermissionCustomDiff(ctx context.Context, diff *schema.ResourceDiff, v interface{}) error {
103+
if diff.HasChanges("resource", "related_resource") {
104+
if diff.Get("related_resource").(string) != "" && diff.Get("resource").(string) != "pipeline" {
105+
return fmt.Errorf("related_resource is only valid when resource is 'pipeline'")
106+
}
107+
}
108+
if diff.HasChanges("resource", "action") {
109+
if funk.Contains([]string{"run", "approve", "debug"}, diff.Get("action").(string)) && diff.Get("resource").(string) != "pipeline" {
110+
return fmt.Errorf("action %v is only valid when resource is 'pipeline'", diff.Get("action").(string))
111+
}
112+
}
113+
return nil
114+
}
115+
86116
func resourcePermissionCreate(d *schema.ResourceData, meta interface{}) error {
87117
client := meta.(*cfClient.Client)
88118

@@ -128,7 +158,6 @@ func resourcePermissionUpdate(d *schema.ResourceData, meta interface{}) error {
128158
client := meta.(*cfClient.Client)
129159

130160
permission := *mapResourceToPermission(d)
131-
permission.ID = ""
132161
resp, err := client.CreatePermission(&permission)
133162
if err != nil {
134163
return err

codefresh/resource_permission_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package codefresh
2+
3+
import (
4+
"fmt"
5+
cfClient "github.com/codefresh-io/terraform-provider-codefresh/client"
6+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
7+
"strings"
8+
"testing"
9+
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
11+
funk "github.com/thoas/go-funk"
12+
)
13+
14+
func TestAccCodefreshPermissionConfig(t *testing.T) {
15+
resourceName := "codefresh_permission.test"
16+
17+
resource.ParallelTest(t, resource.TestCase{
18+
PreCheck: func() { testAccPreCheck(t) },
19+
Providers: testAccProviders,
20+
CheckDestroy: testAccCheckCodefreshContextDestroy,
21+
Steps: []resource.TestStep{
22+
{
23+
Config: testAccCodefreshPermissionConfig("create", "pipeline", "null", []string{"production", "*"}),
24+
Check: resource.ComposeTestCheckFunc(
25+
testAccCheckCodefreshPermissionExists(resourceName),
26+
resource.TestCheckResourceAttr(resourceName, "action", "create"),
27+
resource.TestCheckResourceAttr(resourceName, "resource", "pipeline"),
28+
resource.TestCheckResourceAttr(resourceName, "tags.0", "*"),
29+
resource.TestCheckResourceAttr(resourceName, "tags.1", "production"),
30+
),
31+
},
32+
{
33+
ResourceName: resourceName,
34+
ImportState: true,
35+
ImportStateVerify: true,
36+
},
37+
},
38+
})
39+
}
40+
func testAccCheckCodefreshPermissionExists(resource string) resource.TestCheckFunc {
41+
return func(state *terraform.State) error {
42+
rs, ok := state.RootModule().Resources[resource]
43+
if !ok {
44+
return fmt.Errorf("Not found: %s", resource)
45+
}
46+
if rs.Primary.ID == "" {
47+
return fmt.Errorf("No Record ID is set")
48+
}
49+
50+
permissionID := rs.Primary.ID
51+
52+
apiClient := testAccProvider.Meta().(*cfClient.Client)
53+
_, err := apiClient.GetPermissionByID(permissionID)
54+
55+
if err != nil {
56+
return fmt.Errorf("error fetching permission with ID %s. %s", permissionID, err)
57+
}
58+
return nil
59+
}
60+
}
61+
62+
// CONFIGS
63+
func testAccCodefreshPermissionConfig(action, resource, relatedResource string, tags []string) string {
64+
escapeString := func(str string) string {
65+
if str == "null" {
66+
return str // null means Terraform should ignore this field
67+
}
68+
return fmt.Sprintf(`"%s"`, str)
69+
}
70+
tagsEscaped := funk.Map(tags, escapeString).([]string)
71+
72+
return fmt.Sprintf(`
73+
data "codefresh_team" "users" {
74+
name = "users"
75+
}
76+
77+
resource "codefresh_permission" "test" {
78+
team = data.codefresh_team.users.id
79+
action = %s
80+
resource = %s
81+
related_resource = %s
82+
tags = [%s]
83+
}
84+
`, escapeString(action), escapeString(resource), escapeString(relatedResource), strings.Join(tagsEscaped[:], ","))
85+
}

codefresh/resource_step_types.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,9 @@ func resourceStepTypesUpdate(ctx context.Context, d *schema.ResourceData, meta i
198198
}
199199

200200
// Parse current set: new versions that need to be created are added to a data structure
201-
// that will be sorted later for the creation
202-
// Updates are performed immediately
201+
// that will be sorted later for the creation.
202+
// Updates are performed immediately.
203+
// Also, see notes in TestAccCodefreshStepTypes regarding `version` and d.GetId()
203204
for _, version := range stepTypesVersions.Versions {
204205
versionNumber := version.VersionNumber
205206
versionsDefined[versionNumber] = versionNumber
@@ -215,7 +216,6 @@ func resourceStepTypesUpdate(ctx context.Context, d *schema.ResourceData, meta i
215216
_, err := client.UpdateStepTypes(&version.StepTypes)
216217
if err != nil {
217218
return diag.Errorf("[DEBUG] Error while updating stepTypes. Error = %v", err)
218-
219219
}
220220
}
221221
}

codefresh/resource_step_types_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ func TestAccCodefreshStepTypes(t *testing.T) {
143143
ResourceName: resourceName,
144144
ImportState: true,
145145
ImportStateVerify: true,
146+
// `codefresH_step_types` cannot retrieve `version` on Read only using d.GetId(),
147+
// hence ImportStateVerify will always retrieve an unequilvalent value
148+
// for `version`. We ignore this field for the purpose of the test.
149+
// See: https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/import#importstateverifyignore-1
150+
ImportStateVerifyIgnore: []string{"version"},
146151
},
147152
},
148153
})

docs/resources/permission.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,14 @@ resource "codefresh_permission" "developers" {
5151
- `resource` (String) The type of resources the permission applies to. Possible values:
5252
* pipeline
5353
* cluster
54+
* project
5455
- `team` (String) The Id of the team the permissions apply to.
5556

5657
### Optional
5758

5859
- `_id` (String) The permission ID.
60+
- `related_resource` (String) Specifies the resource to use when evaluating the tags. Possible values:
61+
* project
5962
- `tags` (Set of String) The effective tags to apply the permission. It supports 2 custom tags:
6063
* untagged is a “tag” which refers to all clusters that don't have any tag.
6164
* (the star character) means all tags.

examples/permissions/main.tf

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,25 @@ data "codefresh_team" "developers" {
88

99
resource "codefresh_permission" "dev_pipeline" {
1010
for_each = toset(["run", "create", "update", "delete", "read"])
11-
team = data.codefresh_team.developers.id
12-
action = each.value
11+
team = data.codefresh_team.developers.id
12+
action = each.value
1313
resource = "pipeline"
14-
tags = [ "dev", "untagged"]
14+
tags = ["dev", "untagged"]
1515
}
1616

1717
resource "codefresh_permission" "admin_pipeline" {
1818
for_each = toset(["run", "create", "update", "delete", "read", "approve"])
19-
team = data.codefresh_team.admins.id
20-
action = each.value
19+
team = data.codefresh_team.admins.id
20+
action = each.value
2121
resource = "pipeline"
22-
tags = [ "production", "*"]
22+
tags = ["production", "*"]
23+
}
24+
25+
resource "codefresh_permission" "admin_pipeline_related_resource" {
26+
for_each = toset(["run", "create", "update", "delete", "read", "approve"])
27+
team = data.codefresh_team.admins.id
28+
action = each.value
29+
resource = "pipeline"
30+
related_resource = "project"
31+
tags = ["production", "*"]
2332
}

0 commit comments

Comments
 (0)