Skip to content

Commit

Permalink
Feat: approval policy support (#687)
Browse files Browse the repository at this point in the history
* Feat: approval policy support

* updated code to fix api issues

* added an example

* updates based on PR comments and Slack discussions

* minor changes based on last PR comments
  • Loading branch information
TomerHeber authored Aug 9, 2023
1 parent 3826095 commit 6beab51
Show file tree
Hide file tree
Showing 17 changed files with 1,386 additions and 87 deletions.
4 changes: 4 additions & 0 deletions client/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ type ApiClientInterface interface {
AssignTeamRoleToEnvironment(payload *AssignTeamRoleToEnvironmentPayload) (*TeamRoleEnvironmentAssignment, error)
RemoveTeamRoleFromEnvironment(environmentId string, teamId string) error
TeamRoleEnvironmentAssignments(environmentId string) ([]TeamRoleEnvironmentAssignment, error)
ApprovalPolicies(name string) ([]ApprovalPolicy, error)
ApprovalPolicyAssign(assignment *ApprovalPolicyAssignment) (*ApprovalPolicyAssignment, error)
ApprovalPolicyUnassign(scope string, scopeId string) error
ApprovalPolicyByScope(scope string, scopeId string) ([]ApprovalPolicyByScope, error)
}

func NewApiClient(client http.HttpClientInterface, defaultOrganizationId string) ApiClientInterface {
Expand Down
59 changes: 59 additions & 0 deletions client/api_client_mock.go

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

77 changes: 77 additions & 0 deletions client/approval_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package client

import "fmt"

type ApprovalPolicy struct {
Id string `json:"id"`
Name string `json:"name"`
Repository string `json:"repository"`
Path string `json:"path" tfschema:",omitempty"`
Revision string `json:"revision" tfschema:",omitempty"`
TokenId string `json:"tokenId" tfschema:",omitempty"`
SshKeys []TemplateSshKey `json:"sshKeys"`
GithubInstallationId int `json:"githubInstallationId" tfschema:",omitempty"`
BitbucketClientKey string `json:"bitbucketClientKey" tfschema:",omitempty"`
IsBitbucketServer bool `json:"isBitbucketServer"`
IsGitlabEnterprise bool `json:"isGitLabEnterprise"`
IsGithubEnterprise bool `json:"isGitHubEnterprise"`
IsGitLab bool `json:"isGitLab" tfschema:"is_gitlab"`
IsAzureDevOps bool `json:"isAzureDevOps" tfschema:"is_azure_devops"`
IsTerragruntRunAll bool `json:"isTerragruntRunAll"`
}

type ApprovalPolicyByScope struct {
Scope string `json:"scope"`
ScopeId string `json:"scopeId"`
ApprovalPolicy *ApprovalPolicy `json:"blueprint"`
}

type ApprovalPolicyAssignmentScope string

const (
ApprovalPolicyProjectScope ApprovalPolicyAssignmentScope = "PROJECT"
)

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

func (client *ApiClient) ApprovalPolicies(name string) ([]ApprovalPolicy, error) {
organizationId, err := client.OrganizationId()
if err != nil {
return nil, err
}

var result []ApprovalPolicy
if err := client.http.Get("/approval-policy", map[string]string{"organizationId": organizationId, "name": name}, &result); err != nil {
return nil, err
}

return result, err
}

func (client *ApiClient) ApprovalPolicyAssign(assignment *ApprovalPolicyAssignment) (*ApprovalPolicyAssignment, error) {
var result ApprovalPolicyAssignment

if err := client.http.Post("/approval-policy/assignment", assignment, &result); err != nil {
return nil, err
}

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) ApprovalPolicyByScope(scope string, scopeId string) ([]ApprovalPolicyByScope, error) {
var result []ApprovalPolicyByScope

if err := client.http.Get(fmt.Sprintf("/approval-policy/%s/%s", scope, scopeId), nil, &result); err != nil {
return nil, err
}

return result, nil
}
120 changes: 120 additions & 0 deletions client/approval_policy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package client_test

import (
"fmt"

. "github.com/env0/terraform-provider-env0/client"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("Approval Policy Client", func() {
mockApprovalPolicy := ApprovalPolicy{
Id: "id",
Name: "name",
Repository: "repository",
Path: "path",
Revision: "revision",
TokenId: "tokenId",
SshKeys: []TemplateSshKey{
{Id: "id", Name: "name"},
},
GithubInstallationId: 1,
BitbucketClientKey: "bitbucket-key",
IsBitbucketServer: true,
IsGitlabEnterprise: false,
IsGithubEnterprise: true,
IsGitLab: false,
IsAzureDevOps: true,
IsTerragruntRunAll: false,
}

Describe("Get Custom Flows By Name", func() {
var returnedApprovalPolicies []ApprovalPolicy
mockApprovalPolicies := []ApprovalPolicy{mockApprovalPolicy}

BeforeEach(func() {
mockOrganizationIdCall(organizationId)
httpCall = mockHttpClient.EXPECT().
Get("/approval-policy", map[string]string{"organizationId": organizationId, "name": mockApprovalPolicy.Name}, gomock.Any()).
Do(func(path string, request interface{}, response *[]ApprovalPolicy) {
*response = mockApprovalPolicies
})
organizationIdCall.Times(1)
httpCall.Times(1)
returnedApprovalPolicies, _ = apiClient.ApprovalPolicies(mockApprovalPolicy.Name)
})

It("Should return approval policies", func() {
Expect(returnedApprovalPolicies).To(Equal(mockApprovalPolicies))
})
})

mockAssignment := ApprovalPolicyAssignment{
Scope: ApprovalPolicyProjectScope,
ScopeId: "scope_id",
BlueprintId: "blueprint_id",
}

Describe("Assign Approval Policy", func() {
var returnedApprovalPolicyAssignment *ApprovalPolicyAssignment

BeforeEach(func() {
httpCall = mockHttpClient.EXPECT().
Post("/approval-policy/assignment", &mockAssignment, gomock.Any()).
Do(func(path string, request interface{}, response *ApprovalPolicyAssignment) {
*response = mockAssignment
})
httpCall.Times(1)
returnedApprovalPolicyAssignment, _ = apiClient.ApprovalPolicyAssign(&mockAssignment)
})

It("Should return approval policy assignment", func() {
Expect(*returnedApprovalPolicyAssignment).To(Equal(mockAssignment))
})
})

Describe("Unassign Custom Flow", func() {
var err error

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

It("Should not return an error", func() {
Expect(err).To(BeNil())
})
})

Describe("Get Approval Policy By Scope", func() {
var ret []ApprovalPolicyByScope

scope := string(mockAssignment.Scope)
scopeId := mockAssignment.ScopeId

mockApprovalPolicyByScope := ApprovalPolicyByScope{
Scope: scope,
ScopeId: scopeId,
ApprovalPolicy: &mockApprovalPolicy,
}

mockApprovalPolicyByScopeArr := []ApprovalPolicyByScope{mockApprovalPolicyByScope}

BeforeEach(func() {
httpCall = mockHttpClient.EXPECT().
Get(fmt.Sprintf("/approval-policy/%s/%s", scope, scopeId), nil, gomock.Any()).
Do(func(path string, request interface{}, response *[]ApprovalPolicyByScope) {
*response = mockApprovalPolicyByScopeArr
})
httpCall.Times(1)
ret, _ = apiClient.ApprovalPolicyByScope(scope, scopeId)
})

It("Should return approval policy assignment", func() {
Expect(ret).To(Equal(mockApprovalPolicyByScopeArr))
})
})
})
1 change: 1 addition & 0 deletions client/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type Template struct {
IsAzureDevOps bool `json:"isAzureDevOps" tfschema:"is_azure_devops"`
IsHelmRepository bool `json:"isHelmRepository"`
HelmChartName string `json:"helmChartName,omitempty" tfschema:",omitempty"`
IsGitLab bool `json:"isGitLab" tfschema:"is_gitlab"`
}

type TemplateCreatePayload struct {
Expand Down
115 changes: 115 additions & 0 deletions env0/configuration_template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package env0

import (
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

type TemplateType string

const (
CustomFlow TemplateType = "custom-flow"
ApprovalPolicy TemplateType = "approval-policy"
)

func getConfigurationTemplateSchema(templateType TemplateType) map[string]*schema.Schema {
var text string

switch templateType {
case CustomFlow:
text = "custom flow"
case ApprovalPolicy:
text = "approval policy"
}

s := map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Description: fmt.Sprintf("id of the %s", text),
Computed: true,
},
"repository": {
Type: schema.TypeString,
Description: fmt.Sprintf("repository url for the %s source code", text),
Required: true,
},
"path": {
Type: schema.TypeString,
Description: "terraform / terragrunt file folder inside source code. Should be the full path including the .yaml/.yml file",
Optional: true,
},
"revision": {
Type: schema.TypeString,
Description: "source code revision (branch / tag) to use",
Optional: true,
},
"token_id": {
Type: schema.TypeString,
Description: "the git token id to be used",
Optional: true,
},
"ssh_keys": {
Type: schema.TypeList,
Description: "an array of references to 'data_ssh_key' to use when accessing git over ssh",
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeMap,
Description: "a map of env0_ssh_key.id and env0_ssh_key.name for each project",
},
},
"gitlab_project_id": {
Type: schema.TypeInt,
Description: "the project id of the relevant repository",
Optional: true,
RequiredWith: []string{"token_id"},
},
"github_installation_id": {
Type: schema.TypeInt,
Description: "the env0 application installation id on the relevant github repository",
Optional: true,
},
"bitbucket_client_key": {
Type: schema.TypeString,
Description: "the bitbucket client key used for integration",
Optional: true,
},
"is_bitbucket_server": {
Type: schema.TypeBool,
Description: fmt.Sprintf("true if this %s uses bitbucket server repository", text),
Optional: true,
Default: false,
},
"is_gitlab_enterprise": {
Type: schema.TypeBool,
Description: fmt.Sprintf("true if this %s uses gitlab enterprise repository", text),
Optional: true,
Default: false,
},
"is_github_enterprise": {
Type: schema.TypeBool,
Description: fmt.Sprintf("true if this %s uses github enterprise repository", text),
Optional: true,
Default: false,
},
"is_gitlab": {
Type: schema.TypeBool,
Optional: true,
Description: fmt.Sprintf("true if this %s integrates with gitlab repository", text),
Default: false,
},
"is_azure_devops": {
Type: schema.TypeBool,
Optional: true,
Description: fmt.Sprintf("true if this %s integrates with azure dev ops repository", text),
Default: false,
},
"name": {
Type: schema.TypeString,
Description: fmt.Sprintf("name for the %s", text),
Required: true,
},
}

return s
}
Loading

0 comments on commit 6beab51

Please sign in to comment.