Skip to content

Commit

Permalink
Merge pull request #9092 from mozamimy/cost-filter-multiple-values
Browse files Browse the repository at this point in the history
Support multiple values per one cost filter in aws_budgets_budget resource
  • Loading branch information
ewbankkit committed Jul 27, 2021
2 parents ef9c89c + 174d265 commit f5a1bb5
Show file tree
Hide file tree
Showing 13 changed files with 1,093 additions and 853 deletions.
19 changes: 19 additions & 0 deletions .changelog/9092.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
```release-note:enhancement
resource/aws_budgets_budget: Add the `cost_filter` argument which allows multiple `values` to be specified per filter. This new argument will eventually replace the `cost_filters` argument
```

```release-note:enhancement
resource/aws_budgets_budget: Change `time_period_start` to an optional argument. If you don't specify a start date, AWS defaults to the start of your chosen time period
```

```release-note:bug
resource/aws_budgets_budget: Change the service name in the `arn` attribute from `budgetservice` to `budgets`
```

```release-note:bug
resource/aws_budgets_budget: Suppress plan differences with trailing zeroes for `limit_amount`
```

```release-note:bug
resource/aws_budgets_budget_action: Change the service name in the `arn` attribute from `budgetservice` to `budgets`
```
143 changes: 136 additions & 7 deletions aws/internal/service/budgets/finder/finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,154 @@ package finder
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/budgets"
tfbudgets "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/budgets"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func ActionById(conn *budgets.Budgets, id string) (*budgets.DescribeBudgetActionOutput, error) {
accountID, actionID, budgetName, err := tfbudgets.DecodeBudgetsBudgetActionID(id)
func ActionByAccountIDActionIDAndBudgetName(conn *budgets.Budgets, accountID, actionID, budgetName string) (*budgets.Action, error) {
input := &budgets.DescribeBudgetActionInput{
AccountId: aws.String(accountID),
ActionId: aws.String(actionID),
BudgetName: aws.String(budgetName),
}

output, err := conn.DescribeBudgetAction(input)

if tfawserr.ErrCodeEquals(err, budgets.ErrCodeNotFoundException) {
return nil, &resource.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if err != nil {
return nil, err
}

input := &budgets.DescribeBudgetActionInput{
if output == nil || output.Action == nil {
return nil, &resource.NotFoundError{
Message: "Empty result",
LastRequest: input,
}
}

return output.Action, nil
}

func BudgetByAccountIDAndBudgetName(conn *budgets.Budgets, accountID, budgetName string) (*budgets.Budget, error) {
input := &budgets.DescribeBudgetInput{
AccountId: aws.String(accountID),
BudgetName: aws.String(budgetName),
}

output, err := conn.DescribeBudget(input)

if tfawserr.ErrCodeEquals(err, budgets.ErrCodeNotFoundException) {
return nil, &resource.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if err != nil {
return nil, err
}

if output == nil || output.Budget == nil {
return nil, &resource.NotFoundError{
Message: "Empty result",
LastRequest: input,
}
}

return output.Budget, nil
}

func NotificationsByAccountIDAndBudgetName(conn *budgets.Budgets, accountID, budgetName string) ([]*budgets.Notification, error) {
input := &budgets.DescribeNotificationsForBudgetInput{
AccountId: aws.String(accountID),
ActionId: aws.String(actionID),
BudgetName: aws.String(budgetName),
}
var output []*budgets.Notification

err := conn.DescribeNotificationsForBudgetPages(input, func(page *budgets.DescribeNotificationsForBudgetOutput, lastPage bool) bool {
if page == nil {
return !lastPage
}

for _, notification := range page.Notifications {
if notification == nil {
continue
}

output = append(output, notification)
}

return !lastPage
})

if tfawserr.ErrCodeEquals(err, budgets.ErrCodeNotFoundException) {
return nil, &resource.NotFoundError{
LastError: err,
LastRequest: input,
}
}

out, err := conn.DescribeBudgetAction(input)
if err != nil {
return nil, err
}

return out, nil
if len(output) == 0 {
return nil, &resource.NotFoundError{
Message: "Empty result",
LastRequest: input,
}
}

return output, nil
}

func SubscribersByAccountIDBudgetNameAndNotification(conn *budgets.Budgets, accountID, budgetName string, notification *budgets.Notification) ([]*budgets.Subscriber, error) {
input := &budgets.DescribeSubscribersForNotificationInput{
AccountId: aws.String(accountID),
BudgetName: aws.String(budgetName),
Notification: notification,
}
var output []*budgets.Subscriber

err := conn.DescribeSubscribersForNotificationPages(input, func(page *budgets.DescribeSubscribersForNotificationOutput, lastPage bool) bool {
if page == nil {
return !lastPage
}

for _, subscriber := range page.Subscribers {
if subscriber == nil {
continue
}

output = append(output, subscriber)
}

return !lastPage
})

if tfawserr.ErrCodeEquals(err, budgets.ErrCodeNotFoundException) {
return nil, &resource.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if err != nil {
return nil, err
}

if len(output) == 0 {
return nil, &resource.NotFoundError{
Message: "Empty result",
LastRequest: input,
}
}

return output, nil
}
42 changes: 36 additions & 6 deletions aws/internal/service/budgets/id.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
package glue
package budgets

import (
"fmt"
"strings"
)

func DecodeBudgetsBudgetActionID(id string) (string, string, string, error) {
parts := strings.Split(id, ":")
if len(parts) != 3 {
return "", "", "", fmt.Errorf("Unexpected format of ID (%q), expected AccountID:ActionID:BudgetName", id)
const budgetActionResourceIDSeparator = ":"

func BudgetActionCreateResourceID(accountID, actionID, budgetName string) string {
parts := []string{accountID, actionID, budgetName}
id := strings.Join(parts, budgetActionResourceIDSeparator)

return id
}

func BudgetActionParseResourceID(id string) (string, string, string, error) {
parts := strings.Split(id, budgetActionResourceIDSeparator)

if len(parts) == 3 && parts[0] != "" && parts[1] != "" && parts[2] != "" {
return parts[0], parts[1], parts[2], nil
}
return parts[0], parts[1], parts[2], nil

return "", "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected AccountID%[2]sActionID%[2]sBudgetName", id, budgetActionResourceIDSeparator)
}

const budgetResourceIDSeparator = ":"

func BudgetCreateResourceID(accountID, budgetName string) string {
parts := []string{accountID, budgetName}
id := strings.Join(parts, budgetResourceIDSeparator)

return id
}

func BudgetParseResourceID(id string) (string, string, error) {
parts := strings.Split(id, budgetResourceIDSeparator)

if len(parts) == 2 && parts[0] != "" && parts[1] != "" {
return parts[0], parts[1], nil
}

return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected AccountID%[2]sBudgetName", id, budgetActionResourceIDSeparator)
}
44 changes: 44 additions & 0 deletions aws/internal/service/budgets/time.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package budgets

import (
"fmt"
"time"

"github.com/aws/aws-sdk-go/aws"
)

const (
timePeriodLayout = "2006-01-02_15:04"
)

func TimePeriodTimestampFromString(s string) (*time.Time, error) {
if s == "" {
return nil, nil
}

ts, err := time.Parse(timePeriodLayout, s)

if err != nil {
return nil, err
}

return aws.Time(ts), nil
}

func TimePeriodTimestampToString(ts *time.Time) string {
if ts == nil {
return ""
}

return aws.TimeValue(ts).Format(timePeriodLayout)
}

func ValidateTimePeriodTimestamp(v interface{}, k string) (ws []string, errors []error) {
_, err := time.Parse(timePeriodLayout, v.(string))

if err != nil {
errors = append(errors, fmt.Errorf("%q cannot be parsed as %q: %w", k, timePeriodLayout, err))
}

return
}
17 changes: 9 additions & 8 deletions aws/internal/service/budgets/waiter/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@ package waiter
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/budgets"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/budgets/finder"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource"
)

func ActionStatus(conn *budgets.Budgets, id string) resource.StateRefreshFunc {
func ActionStatus(conn *budgets.Budgets, accountID, actionID, budgetName string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
out, err := finder.ActionById(conn, id)
output, err := finder.ActionByAccountIDActionIDAndBudgetName(conn, accountID, actionID, budgetName)

if tfresource.NotFound(err) {
return nil, "", nil
}

if err != nil {
if tfawserr.ErrCodeEquals(err, budgets.ErrCodeNotFoundException) {
return nil, "", nil
}
return nil, "", err
}

action := out.Action
return action, aws.StringValue(action.Status), err
return output, aws.StringValue(output.Status), nil
}
}
4 changes: 2 additions & 2 deletions aws/internal/service/budgets/waiter/waiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const (
ActionAvailableTimeout = 2 * time.Minute
)

func ActionAvailable(conn *budgets.Budgets, id string) (*budgets.Action, error) {
func ActionAvailable(conn *budgets.Budgets, accountID, actionID, budgetName string) (*budgets.Action, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{
budgets.ActionStatusExecutionInProgress,
Expand All @@ -22,7 +22,7 @@ func ActionAvailable(conn *budgets.Budgets, id string) (*budgets.Action, error)
budgets.ActionStatusExecutionFailure,
budgets.ActionStatusPending,
},
Refresh: ActionStatus(conn, id),
Refresh: ActionStatus(conn, accountID, actionID, budgetName),
Timeout: ActionAvailableTimeout,
}

Expand Down
Loading

0 comments on commit f5a1bb5

Please sign in to comment.