From 2a045592d279bee1df5c412d49a7bd8bb2e43978 Mon Sep 17 00:00:00 2001 From: Tomer Heber Date: Sun, 21 Apr 2024 18:52:52 -0500 Subject: [PATCH] Feat: add role validator (#832) * Feat: add role validator * added fixes --- client/builtin_roles.go | 21 +++++++--- client/user_project_assignment_test.go | 4 +- env0/resource_team_environment_assignment.go | 2 +- env0/resource_team_organization_assignment.go | 2 +- ...ource_team_organization_assignment_test.go | 16 ++++++++ env0/resource_team_project_assignment.go | 6 +-- env0/resource_team_project_assignment_test.go | 4 +- env0/resource_user_project_assignment.go | 4 +- env0/resource_user_project_assignment_test.go | 4 +- env0/validators.go | 38 +++++++++++++------ go.mod | 6 +-- go.sum | 12 +++--- 12 files changed, 79 insertions(+), 40 deletions(-) diff --git a/client/builtin_roles.go b/client/builtin_roles.go index 45761e4c..e88cbeb0 100644 --- a/client/builtin_roles.go +++ b/client/builtin_roles.go @@ -1,12 +1,21 @@ package client const ( - Admin string = "Admin" - Deployer string = "Deployer" - Planner string = "Planner" - Viewer string = "Viewer" + AdminRole string = "Admin" + DeployerRole string = "Deployer" + PlannerRole string = "Planner" + ViewerRole string = "Viewer" + UserRole string = "User" ) -func IsBuiltinProjectRole(role string) bool { - return role == Admin || role == Deployer || role == Planner || role == Viewer +func IsBuiltinRole(role string) bool { + return role == AdminRole || role == DeployerRole || role == PlannerRole || role == ViewerRole || role == UserRole } + +func IsCustomRole(role string) bool { + return !IsBuiltinRole(role) +} + +var ProjectBuiltinRoles = []string{AdminRole, DeployerRole, PlannerRole, ViewerRole} +var EnvironmentBuiltinRoles = []string{AdminRole, DeployerRole, PlannerRole, ViewerRole} +var OrganizationBuiltinRoles = []string{UserRole, AdminRole} diff --git a/client/user_project_assignment_test.go b/client/user_project_assignment_test.go index f1caed0c..f0b7e1e4 100644 --- a/client/user_project_assignment_test.go +++ b/client/user_project_assignment_test.go @@ -15,11 +15,11 @@ var _ = Describe("Agent Project Assignment", func() { assignPayload := &AssignUserToProjectPayload{ UserId: userId, - Role: string(Admin), + Role: string(AdminRole), } updatePayload := &UpdateUserProjectAssignmentPayload{ - Role: string(Admin), + Role: string(AdminRole), } expectedResponse := &UserProjectAssignment{ diff --git a/env0/resource_team_environment_assignment.go b/env0/resource_team_environment_assignment.go index 58469840..2b9fda65 100644 --- a/env0/resource_team_environment_assignment.go +++ b/env0/resource_team_environment_assignment.go @@ -33,7 +33,7 @@ func resourceTeamEnvironmentAssignment() *schema.Resource { Type: schema.TypeString, Description: "id of the assigned custom role. The following built-in roles can be passed as well: `Viewer`, `Planner`, `Deployer`, `Admin`", Required: true, - ValidateDiagFunc: ValidateNotEmptyString, + ValidateDiagFunc: NewRoleValidator(client.EnvironmentBuiltinRoles), }, }, } diff --git a/env0/resource_team_organization_assignment.go b/env0/resource_team_organization_assignment.go index 192ef703..7934ade2 100644 --- a/env0/resource_team_organization_assignment.go +++ b/env0/resource_team_organization_assignment.go @@ -29,7 +29,7 @@ func resourceTeamOrganizationAssignment() *schema.Resource { Type: schema.TypeString, Description: "id of the assigned custom role. The following built-in roles can be passed as well: `User`, `Admin`", Required: true, - ValidateDiagFunc: ValidateNotEmptyString, + ValidateDiagFunc: NewRoleValidator(client.OrganizationBuiltinRoles), }, }, } diff --git a/env0/resource_team_organization_assignment_test.go b/env0/resource_team_organization_assignment_test.go index 1d9d32ae..f66dd2e7 100644 --- a/env0/resource_team_organization_assignment_test.go +++ b/env0/resource_team_organization_assignment_test.go @@ -210,4 +210,20 @@ func TestUnitTeamOrganizationAssignmentResource(t *testing.T) { ) }) }) + + t.Run("unsupported built-in role", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "team_id": teamId, + "role_id": "Planner", + }), + ExpectError: regexp.MustCompile(`the following built-in role 'Planner' is not supported for this resource, must be one of \[User,Admin\]`), + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) {}) + }) } diff --git a/env0/resource_team_project_assignment.go b/env0/resource_team_project_assignment.go index 214737b3..1a19db8f 100644 --- a/env0/resource_team_project_assignment.go +++ b/env0/resource_team_project_assignment.go @@ -37,7 +37,7 @@ func resourceTeamProjectAssignment() *schema.Resource { Type: schema.TypeString, Description: "the assigned built-in role (Admin, Planner, Viewer, Deployer)", Optional: true, - ValidateDiagFunc: ValidateRole, + ValidateDiagFunc: NewRoleValidator(client.ProjectBuiltinRoles), ExactlyOneOf: []string{"custom_role_id", "role"}, }, "custom_role_id": { @@ -94,7 +94,7 @@ func resourceTeamProjectAssignmentRead(ctx context.Context, d *schema.ResourceDa return diag.Errorf("schema resource data serialization failed: %v", err) } - if client.IsBuiltinProjectRole(assignment.Role) { + if client.IsBuiltinRole(assignment.Role) { d.Set("role", assignment.Role) } else { d.Set("custom_role_id", assignment.Role) @@ -150,7 +150,7 @@ func resourceTeamProjectAssignmentImport(ctx context.Context, d *schema.Resource return nil, fmt.Errorf("schema resource data serialization failed: %w", err) } - if client.IsBuiltinProjectRole(assignment.Role) { + if client.IsBuiltinRole(assignment.Role) { d.Set("role", assignment.Role) } else { d.Set("custom_role_id", assignment.Role) diff --git a/env0/resource_team_project_assignment_test.go b/env0/resource_team_project_assignment_test.go index 62359161..89f7323a 100644 --- a/env0/resource_team_project_assignment_test.go +++ b/env0/resource_team_project_assignment_test.go @@ -24,13 +24,13 @@ func TestUnitTeamProjectAssignmentResource(t *testing.T) { assignment := client.TeamRoleAssignmentPayload{ Id: "assignmentId", TeamId: "teamId0", - Role: string(client.Admin), + Role: string(client.AdminRole), } updateAssignment := client.TeamRoleAssignmentPayload{ Id: "assignmentIdupdate", TeamId: "teamIdUupdate", - Role: string(client.Admin), + Role: string(client.AdminRole), } assignmentCustom := client.TeamRoleAssignmentPayload{ diff --git a/env0/resource_user_project_assignment.go b/env0/resource_user_project_assignment.go index a74f5dc2..6f6fb23f 100644 --- a/env0/resource_user_project_assignment.go +++ b/env0/resource_user_project_assignment.go @@ -33,7 +33,7 @@ func resourceUserProjectAssignment() *schema.Resource { Type: schema.TypeString, Description: "the assigned role (Admin, Planner, Viewer, Deployer)", Optional: true, - ValidateDiagFunc: ValidateRole, + ValidateDiagFunc: NewRoleValidator([]string{"Admin", "Planner", "Viewer", "Deployer"}), ExactlyOneOf: []string{"custom_role_id", "role"}, }, "custom_role_id": { @@ -90,7 +90,7 @@ func resourceUserProjectAssignmentRead(ctx context.Context, d *schema.ResourceDa return diag.Errorf("schema resource data serialization failed: %v", err) } - if client.IsBuiltinProjectRole(assignment.Role) { + if client.IsBuiltinRole(assignment.Role) { d.Set("role", assignment.Role) } else { d.Set("custom_role_id", assignment.Role) diff --git a/env0/resource_user_project_assignment_test.go b/env0/resource_user_project_assignment_test.go index 2c64c8cc..fc6b0480 100644 --- a/env0/resource_user_project_assignment_test.go +++ b/env0/resource_user_project_assignment_test.go @@ -14,8 +14,8 @@ func TestUnitUserProjectAssignmentResource(t *testing.T) { userId := "uid" projectId := "pid" id := "id" - role := client.Deployer - updatedRole := client.Viewer + role := client.DeployerRole + updatedRole := client.ViewerRole customRole := "id1" updatedCustomRole := "id2" diff --git a/env0/validators.go b/env0/validators.go index 726bcb7f..276048ab 100644 --- a/env0/validators.go +++ b/env0/validators.go @@ -55,18 +55,6 @@ func ValidateRetries(i interface{}, path cty.Path) diag.Diagnostics { return nil } -func ValidateRole(i interface{}, path cty.Path) diag.Diagnostics { - role := i.(string) - if role == "" || - role != client.Admin && - role != client.Deployer && - role != client.Viewer && - role != client.Planner { - return diag.Errorf("must be one of [Admin, Deployer, Viewer, Planner], got: %v", role) - } - return nil -} - func NewRegexValidator(r string) schema.SchemaValidateDiagFunc { cr := regexp.MustCompile(r) @@ -129,3 +117,29 @@ func ValidateTtl(i interface{}, path cty.Path) diag.Diagnostics { return nil } + +func NewRoleValidator(supportedBuiltInRoles []string) schema.SchemaValidateDiagFunc { + return func(i interface{}, p cty.Path) diag.Diagnostics { + role := i.(string) + + if role == "" { + return diag.Errorf("may not be empty") + } + + if client.IsCustomRole(role) { + // Custom role. + return nil + } + + // Built-in role. Verify it's in the supported list. + for _, supportedRole := range supportedBuiltInRoles { + if role == supportedRole { + // supported. + return nil + } + } + + // not supported. + return diag.Errorf("the following built-in role '%s' is not supported for this resource, must be one of %s", role, "["+strings.Join(supportedBuiltInRoles, ",")+"]") + } +} diff --git a/go.mod b/go.mod index 37279670..ed887cb4 100644 --- a/go.mod +++ b/go.mod @@ -71,11 +71,11 @@ require ( github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/zclconf/go-cty v1.14.1 // indirect - golang.org/x/crypto v0.17.0 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect diff --git a/go.sum b/go.sum index 9692f2a9..1c415a36 100644 --- a/go.sum +++ b/go.sum @@ -247,8 +247,8 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb h1:mIKbk8weKhSeLH2GmUTrvx8CjkyJmnU1wFmg59CUjFA= golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -269,8 +269,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -302,8 +302,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=