Skip to content

Commit

Permalink
Feat: enhance approval policy (#833)
Browse files Browse the repository at this point in the history
* Feat: support enhanced approval policies and custom flows

* Added unassign

* fix from template to blueprint
  • Loading branch information
TomerHeber authored Apr 24, 2024
1 parent 2a04559 commit cdaed47
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 53 deletions.
2 changes: 1 addition & 1 deletion client/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ type ApiClientInterface interface {
UserRoleEnvironmentAssignments(environmentId string) ([]UserRoleEnvironmentAssignment, error)
ApprovalPolicies(name string) ([]ApprovalPolicy, error)
ApprovalPolicyAssign(assignment *ApprovalPolicyAssignment) (*ApprovalPolicyAssignment, error)
ApprovalPolicyUnassign(scope string, scopeId string) error
ApprovalPolicyUnassign(id string) error
ApprovalPolicyByScope(scope string, scopeId string) ([]ApprovalPolicyByScope, error)
ApprovalPolicyCreate(payload *ApprovalPolicyCreatePayload) (*ApprovalPolicy, error)
ApprovalPolicyUpdate(payload *ApprovalPolicyUpdatePayload) (*ApprovalPolicy, error)
Expand Down
8 changes: 4 additions & 4 deletions client/api_client_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 7 additions & 8 deletions client/approval_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,15 @@ type ApprovalPolicyUpdatePayload struct {
IsAzureDevOps bool `json:"isAzureDevOps" tfschema:"is_azure_devops"`
}

type ApprovalPolicyAssignmentScope string

const (
ApprovalPolicyProjectScope ApprovalPolicyAssignmentScope = "PROJECT"
ApprovalPolicyProjectScope string = "PROJECT"
ApprovalPolicyTemplateScope string = "BLUEPRINT"
)

type ApprovalPolicyAssignment struct {
Scope ApprovalPolicyAssignmentScope `json:"scope"`
ScopeId string `json:"scopeId"`
BlueprintId string `json:"blueprintId"`
Scope string `json:"scope"`
ScopeId string `json:"scopeId"`
BlueprintId string `json:"blueprintId"`
}

func (client *ApiClient) ApprovalPolicyCreate(payload *ApprovalPolicyCreatePayload) (*ApprovalPolicy, error) {
Expand Down Expand Up @@ -124,8 +123,8 @@ func (client *ApiClient) ApprovalPolicyAssign(assignment *ApprovalPolicyAssignme
return &result, nil
}

func (client *ApiClient) ApprovalPolicyUnassign(scope string, scopeId string) error {
return client.http.Delete(fmt.Sprintf("/approval-policy/assignment/%s/%s", scope, scopeId), nil)
func (client *ApiClient) ApprovalPolicyUnassign(id string) error {
return client.http.Delete("/approval-policy/assignment", map[string]string{"id": id})
}

func (client *ApiClient) ApprovalPolicyByScope(scope string, scopeId string) ([]ApprovalPolicyByScope, error) {
Expand Down
5 changes: 3 additions & 2 deletions client/approval_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,12 @@ var _ = Describe("Approval Policy Client", func() {

Describe("Unassign Custom Flow", func() {
var err error
id := fmt.Sprintf("%s#%s#%s", mockAssignment.Scope, mockAssignment.ScopeId, mockAssignment.BlueprintId)

BeforeEach(func() {
httpCall = mockHttpClient.EXPECT().Delete(fmt.Sprintf("/approval-policy/assignment/%s/%s", ApprovalPolicyProjectScope, "scope_id"), nil)
httpCall = mockHttpClient.EXPECT().Delete("/approval-policy/assignment", map[string]string{"id": id})
httpCall.Times(1)
err = apiClient.ApprovalPolicyUnassign(string(ApprovalPolicyProjectScope), "scope_id")
err = apiClient.ApprovalPolicyUnassign(id)
})

It("Should not return an error", func() {
Expand Down
60 changes: 26 additions & 34 deletions env0/resource_approval_policy_assignment.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ func resourceApprovalPolicyAssignment() *schema.Resource {

Schema: map[string]*schema.Schema{
"scope": {
Type: schema.TypeString,
Description: "the type of the scope. Valid values: PROJECT. Default value: PROJECT",
Optional: true,
Default: client.ApprovalPolicyProjectScope,
ForceNew: true,
Type: schema.TypeString,
Description: "the type of the scope. Valid values: PROJECT or BLUEPRINT. Default value: PROJECT",
Optional: true,
Default: client.ApprovalPolicyProjectScope,
ForceNew: true,
ValidateDiagFunc: NewStringInValidator([]string{"PROJECT", "BLUEPRINT"}),
},
"scope_id": {
Type: schema.TypeString,
Description: "the id of the scope (E.g. project id)",
Description: "the id of the scope (E.g. project id or template id)",
Required: true,
ForceNew: true,
},
Expand All @@ -47,27 +48,22 @@ func resourceApprovalPolicyAssignment() *schema.Resource {
func resourceApprovalPolicyAssignmentCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
apiClient := meta.(client.ApiClientInterface)

scope := d.Get("scope").(string)
scopeId := d.Get("scope_id").(string)
blueprintId := d.Get("blueprint_id").(string)
var assignment client.ApprovalPolicyAssignment
if err := readResourceData(&assignment, d); err != nil {
return diag.Errorf("schema resource data deserialization failed: %v", err)
}

template, err := apiClient.Template(blueprintId)
template, err := apiClient.Template(assignment.BlueprintId)
if err != nil {
return diag.Errorf("unable to get template with id %s: %v", blueprintId, err)
return diag.Errorf("unable to get template with id %s: %v", assignment.BlueprintId, err)
}

if template.Type != string(ApprovalPolicy) {
return diag.Errorf("template with id %s is of type %s, but %s type is required", blueprintId, template.Type, ApprovalPolicy)
}

assignment := client.ApprovalPolicyAssignment{
Scope: client.ApprovalPolicyAssignmentScope(scope),
ScopeId: scopeId,
BlueprintId: blueprintId,
return diag.Errorf("template with id %s is of type %s, but %s type is required", assignment.BlueprintId, template.Type, ApprovalPolicy)
}

if _, err := apiClient.ApprovalPolicyAssign(&assignment); err != nil {
return diag.Errorf("could not assign approval policy %s to scope %s %s: %v", blueprintId, scope, scopeId, err)
return diag.Errorf("could not assign approval policy %s to scope %s %s: %v", assignment.BlueprintId, assignment.Scope, assignment.ScopeId, err)
}

setApprovalPolicyAssignmentId(d, &assignment)
Expand All @@ -78,18 +74,19 @@ func resourceApprovalPolicyAssignmentCreate(ctx context.Context, d *schema.Resou
func resourceApprovalPolicyAssignmentRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
apiClient := meta.(client.ApiClientInterface)

scope := d.Get("scope").(string)
scopeId := d.Get("scope_id").(string)
blueprintId := d.Get("blueprint_id").(string)
var assignment client.ApprovalPolicyAssignment
if err := readResourceData(&assignment, d); err != nil {
return diag.Errorf("schema resource data deserialization failed: %v", err)
}

approvalPolicyByScopeArr, err := apiClient.ApprovalPolicyByScope(scope, scopeId)
approvalPolicyByScopeArr, err := apiClient.ApprovalPolicyByScope(assignment.Scope, assignment.ScopeId)
if err != nil {
return ResourceGetFailure(ctx, "approval policy assignment", d, err)
}

found := false
for _, approvalPolicyByScope := range approvalPolicyByScopeArr {
if approvalPolicyByScope.ApprovalPolicy.Id == blueprintId {
if approvalPolicyByScope.ApprovalPolicy.Id == assignment.BlueprintId {
found = true
break
}
Expand All @@ -101,14 +98,6 @@ func resourceApprovalPolicyAssignmentRead(ctx context.Context, d *schema.Resourc
return nil
}

assignment := client.ApprovalPolicyAssignment{
Scope: client.ApprovalPolicyAssignmentScope(scope),
ScopeId: scopeId,
BlueprintId: blueprintId,
}

setApprovalPolicyAssignmentId(d, &assignment)

return nil
}

Expand All @@ -117,9 +106,12 @@ func resourceApprovalPolicyAssignmentDelete(ctx context.Context, d *schema.Resou

scope := d.Get("scope").(string)
scopeId := d.Get("scope_id").(string)
bluePrintId := d.Get("blueprint_id").(string)

id := fmt.Sprintf("%s#%s#%s", scope, scopeId, bluePrintId)

if err := apiClient.ApprovalPolicyUnassign(scope, scopeId); err != nil {
return diag.Errorf("failed to unassign approval policy from scope %s %s: %v", scope, scopeId, err)
if err := apiClient.ApprovalPolicyUnassign(id); err != nil {
return diag.Errorf("failed to unassign approval policy %s: %v", id, err)
}

return nil
Expand Down
9 changes: 5 additions & 4 deletions env0/resource_approval_policy_assignment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ func TestUnitResourceApprovalPolicyAssignmentResource(t *testing.T) {
BlueprintId: "blueprint_id",
}

id := fmt.Sprintf("%s#%s#%s", assignment.Scope, assignment.ScopeId, assignment.BlueprintId)

stepConfig := resourceConfigCreate(resourceType, resourceName, map[string]interface{}{
"scope_id": assignment.ScopeId,
"blueprint_id": assignment.BlueprintId,
Expand Down Expand Up @@ -50,7 +52,6 @@ func TestUnitResourceApprovalPolicyAssignmentResource(t *testing.T) {
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", fmt.Sprintf("%s|%s|%s", assignment.BlueprintId, assignment.Scope, assignment.ScopeId)),
resource.TestCheckResourceAttr(accessor, "scope_id", assignment.ScopeId),
resource.TestCheckResourceAttr(accessor, "scope", "PROJECT"),
resource.TestCheckResourceAttr(accessor, "blueprint_id", assignment.BlueprintId),
),
},
Expand All @@ -62,7 +63,7 @@ func TestUnitResourceApprovalPolicyAssignmentResource(t *testing.T) {
mock.EXPECT().Template(assignment.BlueprintId).Times(1).Return(validTemplate, nil),
mock.EXPECT().ApprovalPolicyAssign(&assignment).Times(1).Return(&assignment, nil),
mock.EXPECT().ApprovalPolicyByScope(string(assignment.Scope), assignment.ScopeId).Times(1).Return([]client.ApprovalPolicyByScope{approvalPolicyByScope}, nil),
mock.EXPECT().ApprovalPolicyUnassign(string(assignment.Scope), assignment.ScopeId).Times(1).Return(nil),
mock.EXPECT().ApprovalPolicyUnassign(id).Times(1).Return(nil),
)
})
})
Expand Down Expand Up @@ -143,7 +144,7 @@ func TestUnitResourceApprovalPolicyAssignmentResource(t *testing.T) {
mock.EXPECT().Template(assignment.BlueprintId).Times(1).Return(validTemplate, nil),
mock.EXPECT().ApprovalPolicyAssign(&assignment).Times(1).Return(&assignment, nil),
mock.EXPECT().ApprovalPolicyByScope(string(assignment.Scope), assignment.ScopeId).Times(1).Return(nil, &client.NotFoundError{}),
mock.EXPECT().ApprovalPolicyUnassign(string(assignment.Scope), assignment.ScopeId).Times(1).Return(nil),
mock.EXPECT().ApprovalPolicyUnassign(id).Times(1).Return(nil),
)
})
})
Expand Down Expand Up @@ -178,7 +179,7 @@ func TestUnitResourceApprovalPolicyAssignmentResource(t *testing.T) {
mock.EXPECT().Template(assignment.BlueprintId).Times(1).Return(validTemplate, nil),
mock.EXPECT().ApprovalPolicyAssign(&assignment).Times(1).Return(&assignment, nil),
mock.EXPECT().ApprovalPolicyByScope(string(assignment.Scope), assignment.ScopeId).Times(1).Return([]client.ApprovalPolicyByScope{approvalPolicyByScopeMismatch}, nil),
mock.EXPECT().ApprovalPolicyUnassign(string(assignment.Scope), assignment.ScopeId).Times(1).Return(nil),
mock.EXPECT().ApprovalPolicyUnassign(id).Times(1).Return(nil),
)
})
})
Expand Down

0 comments on commit cdaed47

Please sign in to comment.