Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: environment scheduling resource #208

Merged
merged 29 commits into from
Jan 25, 2022
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
74c0200
add environment scheduling related models
yaronya Jan 18, 2022
4934b4d
add environment scheduling api calls
yaronya Jan 18, 2022
2211821
add environment scheduling api client method to interface
yaronya Jan 18, 2022
7dc987a
re-generate gomock file for env sched methods
yaronya Jan 18, 2022
3fb6639
move mock const to be non-global for client package
yaronya Jan 18, 2022
c291553
use the right model when deleting env sched
yaronya Jan 18, 2022
25fe33e
rename env sched models
yaronya Jan 18, 2022
f3ff832
add tests for environment scheduling
yaronya Jan 18, 2022
9431bc7
Merge branch 'main' of github.com:env0/terraform-provider-env0 into f…
yaronya Jan 18, 2022
09760c9
Merge branch 'main' of github.com:env0/terraform-provider-env0 into f…
yaronya Jan 18, 2022
c28f7b0
add environment scheduling resource
yaronya Jan 18, 2022
1029059
add env0_environment_scheduling resource
yaronya Jan 18, 2022
f85cbe2
add gronx cron parser
yaronya Jan 19, 2022
1a66b0a
add cron experssion validation
yaronya Jan 19, 2022
968a175
init test file
yaronya Jan 19, 2022
3a4cda5
set cron attrs as optional
yaronya Jan 19, 2022
62c8eb1
add tests for failures
yaronya Jan 19, 2022
9a6eae0
update mod after running mod tidy
yaronya Jan 19, 2022
af7828c
use pointers for environment scheduling expressions
yaronya Jan 19, 2022
86dbdd1
handle drifts properly when cron is removed in remote
yaronya Jan 19, 2022
736f828
fix code to use pointers in env sched expressions
yaronya Jan 19, 2022
acce8fd
fix tests after env sched expression pointer refactor
yaronya Jan 19, 2022
aa96052
add environment scheduling resource example
yaronya Jan 20, 2022
241c368
Merge branch 'main' of github.com:env0/terraform-provider-env0 into f…
yaronya Jan 25, 2022
2cbc05e
add cron dup restriction to description
yaronya Jan 25, 2022
dfce203
re run go mod tidy
yaronya Jan 25, 2022
8ab90a1
add docs for environment scheduling
yaronya Jan 25, 2022
810cf80
add harness tests for env sched
yaronya Jan 25, 2022
e1de49f
remove redundant if
yaronya Jan 25, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 != nil && payload.Deploy.Cron == payload.Destroy.Cron {
GiliFaroEnv0 marked this conversation as resolved.
Show resolved Hide resolved
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(),
eranelbaz marked this conversation as resolved.
Show resolved Hide resolved
},
}

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", "")
eranelbaz marked this conversation as resolved.
Show resolved Hide resolved
}

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}
}
eranelbaz marked this conversation as resolved.
Show resolved Hide resolved

_, 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