-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
be93206
commit f0aade0
Showing
7 changed files
with
292 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package env0 | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/env0/terraform-provider-env0/client" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
) | ||
|
||
func resourceProjectBudget() *schema.Resource { | ||
return &schema.Resource{ | ||
CreateContext: resourceProjectBudgetCreateOrUpdate, | ||
UpdateContext: resourceProjectBudgetCreateOrUpdate, | ||
ReadContext: resourceProjectBudgetRead, | ||
DeleteContext: resourceProjectBudgetDelete, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"project_id": { | ||
Type: schema.TypeString, | ||
Description: "id of the project", | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
"amount": { | ||
Type: schema.TypeInt, | ||
Description: "amount of the project budget", | ||
Required: true, | ||
}, | ||
"timeframe": { | ||
Type: schema.TypeString, | ||
Description: "budget timeframe (valid values: WEEKLY, MONTHLY, QUARTERLY, YEARLY)", | ||
Required: true, | ||
ValidateDiagFunc: NewStringInValidator([]string{"WEEKLY", "MONTHLY", "QUARTERLY", "YEARLY"}), | ||
}, | ||
"thresholds": { | ||
Type: schema.TypeList, | ||
Description: "list of notification thresholds", | ||
Optional: true, | ||
Elem: &schema.Schema{ | ||
Type: schema.TypeInt, | ||
Description: "a threshold in %", | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceProjectBudgetCreateOrUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
var payload client.ProjectBudgetUpdatePayload | ||
if err := readResourceData(&payload, d); err != nil { | ||
return diag.Errorf("schema resource data deserialization failed: %v", err) | ||
} | ||
|
||
projectId := d.Get("project_id").(string) | ||
|
||
apiClient := meta.(client.ApiClientInterface) | ||
|
||
budget, err := apiClient.ProjectBudgetUpdate(projectId, &payload) | ||
if err != nil { | ||
return diag.Errorf("could not create or update budget: %v", err) | ||
} | ||
|
||
d.SetId(budget.Id) | ||
|
||
return nil | ||
} | ||
|
||
func resourceProjectBudgetRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
apiClient := meta.(client.ApiClientInterface) | ||
|
||
projectId := d.Get("project_id").(string) | ||
|
||
budget, err := apiClient.ProjectBudget(projectId) | ||
if err != nil { | ||
return ResourceGetFailure(ctx, "project budget", d, err) | ||
} | ||
|
||
if err := writeResourceData(budget, d); err != nil { | ||
return diag.Errorf("schema resource data serialization failed: %v", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func resourceProjectBudgetDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
projectId := d.Get("project_id").(string) | ||
|
||
apiClient := meta.(client.ApiClientInterface) | ||
|
||
if err := apiClient.ProjectBudgetDelete(projectId); err != nil { | ||
return diag.Errorf("could not delete project budget: %v", err) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
package env0 | ||
|
||
import ( | ||
"errors" | ||
"regexp" | ||
"strconv" | ||
"testing" | ||
|
||
"github.com/env0/terraform-provider-env0/client" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||
"go.uber.org/mock/gomock" | ||
) | ||
|
||
func TestUnitProjectBudgetResource(t *testing.T) { | ||
resourceType := "env0_project_budget" | ||
resourceName := "test" | ||
accessor := resourceAccessor(resourceType, resourceName) | ||
|
||
projectBudget := &client.ProjectBudget{ | ||
Id: "id", | ||
ProjectId: "pid", | ||
Amount: 10, | ||
Timeframe: "MONTHLY", | ||
Thresholds: []int{1}, | ||
} | ||
|
||
updatedProjectBudget := &client.ProjectBudget{ | ||
Id: "id", | ||
ProjectId: "pid", | ||
Amount: 20, | ||
Timeframe: "WEEKLY", | ||
Thresholds: []int{2}, | ||
} | ||
|
||
t.Run("create and update", func(t *testing.T) { | ||
testCase := resource.TestCase{ | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ | ||
"project_id": projectBudget.ProjectId, | ||
"amount": strconv.Itoa(projectBudget.Amount), | ||
"timeframe": projectBudget.Timeframe, | ||
"thresholds": projectBudget.Thresholds, | ||
}), | ||
Check: resource.ComposeAggregateTestCheckFunc( | ||
resource.TestCheckResourceAttr(accessor, "project_id", projectBudget.ProjectId), | ||
resource.TestCheckResourceAttr(accessor, "amount", strconv.Itoa(projectBudget.Amount)), | ||
resource.TestCheckResourceAttr(accessor, "timeframe", projectBudget.Timeframe), | ||
resource.TestCheckResourceAttr(accessor, "thresholds.0", strconv.Itoa(projectBudget.Thresholds[0])), | ||
), | ||
}, | ||
{ | ||
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ | ||
"project_id": updatedProjectBudget.ProjectId, | ||
"amount": strconv.Itoa(updatedProjectBudget.Amount), | ||
"timeframe": updatedProjectBudget.Timeframe, | ||
"thresholds": updatedProjectBudget.Thresholds, | ||
}), | ||
Check: resource.ComposeAggregateTestCheckFunc( | ||
resource.TestCheckResourceAttr(accessor, "project_id", updatedProjectBudget.ProjectId), | ||
resource.TestCheckResourceAttr(accessor, "amount", strconv.Itoa(updatedProjectBudget.Amount)), | ||
resource.TestCheckResourceAttr(accessor, "timeframe", updatedProjectBudget.Timeframe), | ||
resource.TestCheckResourceAttr(accessor, "thresholds.0", strconv.Itoa(updatedProjectBudget.Thresholds[0])), | ||
), | ||
}, | ||
}, | ||
} | ||
|
||
runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { | ||
gomock.InOrder( | ||
mock.EXPECT().ProjectBudgetUpdate(projectBudget.ProjectId, &client.ProjectBudgetUpdatePayload{ | ||
Amount: projectBudget.Amount, | ||
Timeframe: projectBudget.Timeframe, | ||
Thresholds: projectBudget.Thresholds, | ||
}).Times(1).Return(projectBudget, nil), | ||
mock.EXPECT().ProjectBudget(projectBudget.ProjectId).Times(2).Return(projectBudget, nil), | ||
mock.EXPECT().ProjectBudgetUpdate(updatedProjectBudget.ProjectId, &client.ProjectBudgetUpdatePayload{ | ||
Amount: updatedProjectBudget.Amount, | ||
Timeframe: updatedProjectBudget.Timeframe, | ||
Thresholds: updatedProjectBudget.Thresholds, | ||
}).Times(1).Return(updatedProjectBudget, nil), | ||
mock.EXPECT().ProjectBudget(updatedProjectBudget.ProjectId).Times(1).Return(updatedProjectBudget, nil), | ||
mock.EXPECT().ProjectBudgetDelete(projectBudget.ProjectId).Times(1).Return(nil), | ||
) | ||
}) | ||
}) | ||
|
||
t.Run("Create Failure - invalid timeframe", func(t *testing.T) { | ||
testCase := resource.TestCase{ | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ | ||
"project_id": projectBudget.ProjectId, | ||
"amount": strconv.Itoa(projectBudget.Amount), | ||
"timeframe": "invalid", | ||
"thresholds": projectBudget.Thresholds, | ||
}), | ||
ExpectError: regexp.MustCompile("must be one of: WEEKLY, MONTHLY, QUARTERLY, YEARLY"), | ||
}, | ||
}, | ||
} | ||
|
||
runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) {}) | ||
}) | ||
|
||
t.Run("Detect drift", func(t *testing.T) { | ||
testCase := resource.TestCase{ | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ | ||
"project_id": projectBudget.ProjectId, | ||
"amount": strconv.Itoa(projectBudget.Amount), | ||
"timeframe": projectBudget.Timeframe, | ||
"thresholds": projectBudget.Thresholds, | ||
}), | ||
Check: resource.ComposeAggregateTestCheckFunc( | ||
resource.TestCheckResourceAttr(accessor, "project_id", projectBudget.ProjectId), | ||
resource.TestCheckResourceAttr(accessor, "amount", strconv.Itoa(projectBudget.Amount)), | ||
resource.TestCheckResourceAttr(accessor, "timeframe", projectBudget.Timeframe), | ||
resource.TestCheckResourceAttr(accessor, "thresholds.0", strconv.Itoa(projectBudget.Thresholds[0])), | ||
), | ||
ExpectNonEmptyPlan: true, | ||
}, | ||
}, | ||
} | ||
|
||
runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { | ||
gomock.InOrder( | ||
mock.EXPECT().ProjectBudgetUpdate(projectBudget.ProjectId, &client.ProjectBudgetUpdatePayload{ | ||
Amount: projectBudget.Amount, | ||
Timeframe: projectBudget.Timeframe, | ||
Thresholds: projectBudget.Thresholds, | ||
}).Times(1).Return(projectBudget, nil), | ||
mock.EXPECT().ProjectBudget(projectBudget.ProjectId).Times(1).Return(nil, &client.NotFoundError{}), | ||
mock.EXPECT().ProjectBudgetDelete(projectBudget.ProjectId).Times(1).Return(nil), | ||
) | ||
}) | ||
}) | ||
|
||
t.Run("Failure in create", func(t *testing.T) { | ||
testCase := resource.TestCase{ | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ | ||
"project_id": projectBudget.ProjectId, | ||
"amount": strconv.Itoa(projectBudget.Amount), | ||
"timeframe": projectBudget.Timeframe, | ||
"thresholds": projectBudget.Thresholds, | ||
}), | ||
ExpectError: regexp.MustCompile("could not create or update budget: error"), | ||
}, | ||
}, | ||
} | ||
|
||
runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { | ||
mock.EXPECT().ProjectBudgetUpdate(projectBudget.ProjectId, &client.ProjectBudgetUpdatePayload{ | ||
Amount: projectBudget.Amount, | ||
Timeframe: projectBudget.Timeframe, | ||
Thresholds: projectBudget.Thresholds, | ||
}).Times(1).Return(nil, errors.New("error")) | ||
}) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
resource "env0_project" "project" { | ||
name = "example" | ||
} | ||
|
||
resource "env_project_budget" "project_budget" { | ||
project_id = env0_project.project.id | ||
amount = 1000 | ||
timeframe = "MONTHLY" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters