Skip to content

Commit

Permalink
Feat: environment scheduling resource (#208)
Browse files Browse the repository at this point in the history
  • Loading branch information
yaronya authored Jan 25, 2022
1 parent ad162f3 commit 282700a
Show file tree
Hide file tree
Showing 14 changed files with 332 additions and 15 deletions.
8 changes: 5 additions & 3 deletions client/environment_scheduling.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ func (self *ApiClient) EnvironmentScheduling(environmentId string) (EnvironmentS
func (self *ApiClient) EnvironmentSchedulingUpdate(environmentId string, payload EnvironmentScheduling) (EnvironmentScheduling, error) {
var result EnvironmentScheduling

if payload.Deploy.Cron == payload.Destroy.Cron {
return EnvironmentScheduling{}, errors.New("deploy and destroy cron expressions must not be the same")
if payload.Deploy != nil && payload.Destroy != nil {
if payload.Deploy.Cron == payload.Destroy.Cron {
return EnvironmentScheduling{}, errors.New("deploy and destroy cron expressions must not be the same")
}
}

err := self.http.Put("/scheduling/environments/"+environmentId, payload, &result)
Expand All @@ -28,7 +30,7 @@ func (self *ApiClient) EnvironmentSchedulingUpdate(environmentId string, payload
}

func (self *ApiClient) EnvironmentSchedulingDelete(environmentId string) error {
err := self.http.Put("/scheduling/environments/"+environmentId, EnvironmentScheduling{}, nil)
err := self.http.Put("/scheduling/environments/"+environmentId, EnvironmentScheduling{}, &EnvironmentScheduling{})
if err != nil {
return err
}
Expand Down
10 changes: 5 additions & 5 deletions client/environment_scheduling_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ var _ = Describe("EnvironmentScheduling", func() {
mockDestroyCronPayload := EnvironmentSchedulingExpression{Cron: "0 * * * *", Enabled: true}

mockEnvironmentSchedulingPayload := EnvironmentScheduling{
Deploy: mockDeployCronPayload,
Destroy: mockDestroyCronPayload,
Deploy: &mockDeployCronPayload,
Destroy: &mockDestroyCronPayload,
}

var environmentSchedulingResponse EnvironmentScheduling
Expand Down Expand Up @@ -46,8 +46,8 @@ var _ = Describe("EnvironmentScheduling", func() {
Describe("Failure", func() {
It("Should fail if cron expressions are the same", func() {
mockFailedEnvironmentSchedulingPayload := EnvironmentScheduling{
Deploy: mockDeployCronPayload,
Destroy: mockDeployCronPayload,
Deploy: &mockDeployCronPayload,
Destroy: &mockDeployCronPayload,
}
_, err := apiClient.EnvironmentSchedulingUpdate(mockEnvironmentId, mockFailedEnvironmentSchedulingPayload)
Expect(err).To(BeEquivalentTo(errors.New("deploy and destroy cron expressions must not be the same")))
Expand Down Expand Up @@ -115,7 +115,7 @@ var _ = Describe("EnvironmentScheduling", func() {
Describe("Delete", func() {
Describe("Success", func() {
BeforeEach(func() {
httpCall = mockHttpClient.EXPECT().Put("/scheduling/environments/"+mockEnvironmentId, EnvironmentScheduling{}, nil)
httpCall = mockHttpClient.EXPECT().Put("/scheduling/environments/"+mockEnvironmentId, EnvironmentScheduling{}, &EnvironmentScheduling{})
apiClient.EnvironmentSchedulingDelete(mockEnvironmentId)
})

Expand Down
4 changes: 2 additions & 2 deletions client/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,8 @@ type EnvironmentSchedulingExpression struct {
}

type EnvironmentScheduling struct {
Deploy EnvironmentSchedulingExpression `json:"deploy,omitempty"`
Destroy EnvironmentSchedulingExpression `json:"destroy,omitempty"`
Deploy *EnvironmentSchedulingExpression `json:"deploy,omitempty"`
Destroy *EnvironmentSchedulingExpression `json:"destroy,omitempty"`
}

type WorkflowTrigger struct {
Expand Down
7 changes: 2 additions & 5 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,8 @@ provider "env0" {
<!-- schema generated by tfplugindocs -->
## Schema

### Required

- **api_key** (String, Sensitive) env0 api key (https://developer.env0.com/docs/api/YXBpOjY4Njc2-env0-api#creating-an-api-key)
- **api_secret** (String, Sensitive) env0 api key secret

### Optional

- **api_endpoint** (String) override api endpoint (used for testing)
- **api_key** (String, Sensitive) env0 api key (https://developer.env0.com/docs/api/YXBpOjY4Njc2-env0-api#creating-an-api-key)
- **api_secret** (String, Sensitive) env0 api key secret
40 changes: 40 additions & 0 deletions docs/resources/environment_scheduling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "env0_environment_scheduling Resource - terraform-provider-env0"
subcategory: ""
description: |-
---

# env0_environment_scheduling (Resource)



## Example Usage

```terraform
data "env0_environment" "example" {
name = "Environment Name"
}
resource "env0_environment_schedling" "example" {
environment_id = data.env0_environment.example.id
deploy_cron = "5 * * * *"
destroy_cron = "10 * * * *"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- **environment_id** (String) The environment's id

### Optional

- **deploy_cron** (String) Cron expression for scheduled deploy of the environment. Destroy and Deploy cron expressions must not be the same.
- **destroy_cron** (String) Cron expression for scheduled destroy of the environment. Destroy and Deploy cron expressions must not be the same.
- **id** (String) The ID of this resource.


1 change: 1 addition & 0 deletions env0/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func Provider(version string) plugin.ProviderFunc {
"env0_team": resourceTeam(),
"env0_environment": resourceEnvironment(),
"env0_workflow_triggers": resourceWorkflowTriggers(),
"env0_environment_scheduling": resourceEnvironmentScheduling(),
},
}

Expand Down
128 changes: 128 additions & 0 deletions env0/resource_environment_scheduling.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package env0

import (
"context"
"github.com/adhocore/gronx"
. "github.com/env0/terraform-provider-env0/client"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceEnvironmentScheduling() *schema.Resource {

validateCronExpression := func(i interface{}, path cty.Path) diag.Diagnostics {
expr := i.(string)
parser := gronx.New()
isValid := parser.IsValid(expr)

if isValid != true {
return diag.Diagnostics{
diag.Diagnostic{
Severity: diag.Error,
Summary: "Invalid cron expression",
AttributePath: path,
}}
}

return nil
}

return &schema.Resource{
CreateContext: resourceEnvironmentSchedulingCreateOrUpdate,
ReadContext: resourceEnvironmentSchedulingRead,
UpdateContext: resourceEnvironmentSchedulingCreateOrUpdate,
DeleteContext: resourceEnvironmentSchedulingDelete,

Schema: map[string]*schema.Schema{
"environment_id": {
Type: schema.TypeString,
Description: "The environment's id",
Required: true,
ForceNew: true,
},
"destroy_cron": {
Type: schema.TypeString,
Description: "Cron expression for scheduled destroy of the environment. Destroy and Deploy cron expressions must not be the same.",
AtLeastOneOf: []string{"destroy_cron", "deploy_cron"},
Optional: true,
ValidateDiagFunc: validateCronExpression,
},
"deploy_cron": {
Type: schema.TypeString,
Description: "Cron expression for scheduled deploy of the environment. Destroy and Deploy cron expressions must not be the same.",
AtLeastOneOf: []string{"destroy_cron", "deploy_cron"},
Optional: true,
ValidateDiagFunc: validateCronExpression,
},
},
}
}

func resourceEnvironmentSchedulingRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
apiClient := meta.(ApiClientInterface)

environmentId := d.Id()

environmentScheduling, err := apiClient.EnvironmentScheduling(environmentId)

if err != nil {
return diag.Errorf("could not get environment scheduling: %v", err)
}

if environmentScheduling.Deploy != nil {
d.Set("deploy_cron", environmentScheduling.Deploy.Cron)
} else {
d.Set("deploy_cron", "")
}

if environmentScheduling.Destroy != nil {
d.Set("destroy_cron", environmentScheduling.Destroy.Cron)
} else {
d.Set("destroy_cron", "")

}

return nil
}

func resourceEnvironmentSchedulingCreateOrUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
apiClient := meta.(ApiClientInterface)

environmentId := d.Get("environment_id").(string)
deployCron := d.Get("deploy_cron").(string)
destroyCron := d.Get("destroy_cron").(string)

payload := EnvironmentScheduling{}

if deployCron != "" {
payload.Deploy = &EnvironmentSchedulingExpression{Cron: deployCron, Enabled: true}
}

if destroyCron != "" {
payload.Destroy = &EnvironmentSchedulingExpression{Cron: destroyCron, Enabled: true}
}

_, err := apiClient.EnvironmentSchedulingUpdate(environmentId, payload)

if err != nil {
return diag.Errorf("could not create or update environment scheduling: %v", err)
}

d.SetId(environmentId)
return nil
}

func resourceEnvironmentSchedulingDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
apiClient := meta.(ApiClientInterface)

environmentId := d.Id()

err := apiClient.EnvironmentSchedulingDelete(environmentId)

if err != nil {
return diag.Errorf("could not delete environment scheduling: %v", err)
}

return nil
}
82 changes: 82 additions & 0 deletions env0/resource_environment_scheduling_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package env0

import (
"fmt"
"github.com/env0/terraform-provider-env0/client"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"regexp"
"testing"
)

func TestUnitEnvironmentSchedulingResource(t *testing.T) {
environmentId := "environment0"
resourceType := "env0_environment_scheduling"
resourceName := "test"
accessor := resourceAccessor(resourceType, resourceName)
environmentScheduling := client.EnvironmentScheduling{
Deploy: &client.EnvironmentSchedulingExpression{Cron: "1 * * * *", Enabled: true},
Destroy: &client.EnvironmentSchedulingExpression{Cron: "2 * * * *", Enabled: true},
}

cronExprKeys := []string{"deploy_cron", "destroy_cron"}

t.Run("Success", func(t *testing.T) {
testCase := resource.TestCase{
Steps: []resource.TestStep{
{
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{
"environment_id": environmentId,
"deploy_cron": environmentScheduling.Deploy.Cron,
"destroy_cron": environmentScheduling.Destroy.Cron,
}),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "environment_id", environmentId),
resource.TestCheckResourceAttr(accessor, "deploy_cron", environmentScheduling.Deploy.Cron),
resource.TestCheckResourceAttr(accessor, "destroy_cron", environmentScheduling.Destroy.Cron),
),
},
},
}

runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) {
mock.EXPECT().EnvironmentSchedulingUpdate(environmentId, environmentScheduling).Times(1).Return(environmentScheduling, nil)
mock.EXPECT().EnvironmentScheduling(environmentId).Times(1).Return(environmentScheduling, nil)
mock.EXPECT().EnvironmentSchedulingDelete(environmentId).Times(1).Return(nil)
})
})

for _, key := range cronExprKeys {
t.Run(fmt.Sprintf("Failure due to invalid cron expression for %s", key), func(t *testing.T) {
testCase := resource.TestCase{
Steps: []resource.TestStep{
{
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{
"environment_id": environmentId,
key: "not_a_valid_cron",
}),
ExpectError: regexp.MustCompile("Invalid cron expression"),
},
},
}

runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) {})

})
}

t.Run("Failure when both deploy_cron and destroy_cron attributes are missing", func(t *testing.T) {
testCase := resource.TestCase{
Steps: []resource.TestStep{
{
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{
"environment_id": environmentId,
}),
ExpectError: regexp.MustCompile("AtLeastOne"),
},
},
}

runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) {})

})
}
9 changes: 9 additions & 0 deletions examples/resources/env0_environment_scheduling/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
data "env0_environment" "example" {
name = "Environment Name"
}

resource "env0_environment_schedling" "example" {
environment_id = data.env0_environment.example.id
deploy_cron = "5 * * * *"
destroy_cron = "10 * * * *"
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ module github.com/env0/terraform-provider-env0
go 1.16 // please change also in `ci.yml` and `release.yml`

require (
github.com/adhocore/gronx v0.2.6
github.com/go-resty/resty/v2 v2.6.0
github.com/golang/mock v1.4.3
github.com/google/uuid v1.2.0
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
github.com/hashicorp/terraform-exec v0.15.0 // indirect
github.com/hashicorp/terraform-plugin-docs v0.5.1 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.4.4
github.com/jarcoal/httpmock v1.0.8
github.com/jinzhu/copier v0.3.2
Expand Down
Loading

0 comments on commit 282700a

Please sign in to comment.