From 0f3e0f05a588ac70f7d417e6f09ff84799bfa994 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 14 Aug 2023 13:45:17 -0700 Subject: [PATCH 1/5] Moves tested resource to top --- .../service/budgets/budget_action_test.go | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/internal/service/budgets/budget_action_test.go b/internal/service/budgets/budget_action_test.go index 0a8911d978e..7fa6b4a9ec8 100644 --- a/internal/service/budgets/budget_action_test.go +++ b/internal/service/budgets/budget_action_test.go @@ -149,6 +149,40 @@ func testAccCheckBudgetActionDestroy(ctx context.Context) resource.TestCheckFunc func testAccBudgetActionConfig_basic(rName string) string { return fmt.Sprintf(` +resource "aws_budgets_budget_action" "test" { + budget_name = aws_budgets_budget.test.name + action_type = "APPLY_IAM_POLICY" + approval_model = "AUTOMATIC" + notification_type = "ACTUAL" + execution_role_arn = aws_iam_role.test.arn + + action_threshold { + action_threshold_type = "ABSOLUTE_VALUE" + action_threshold_value = 100 + } + + definition { + iam_action_definition { + policy_arn = aws_iam_policy.test.arn + roles = [aws_iam_role.test.name] + } + } + + subscriber { + address = "test@test.test" + subscription_type = "EMAIL" + } +} + +resource "aws_budgets_budget" "test" { + name = %[1]q + budget_type = "USAGE" + limit_amount = "1.0" + limit_unit = "dollars" + time_period_start = "2006-01-02_15:04" + time_unit = "MONTHLY" +} + resource "aws_iam_policy" "test" { name = %[1]q description = "My test policy" From cfebdf6e475e761f82a6b6a00fb522f0fc2cc328 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 14 Aug 2023 17:24:29 -0700 Subject: [PATCH 2/5] Removes unnecessary wait when creating Budget Action --- internal/service/budgets/budget_action.go | 49 ++---- .../service/budgets/budget_action_test.go | 143 ++++++++++++------ 2 files changed, 110 insertions(+), 82 deletions(-) diff --git a/internal/service/budgets/budget_action.go b/internal/service/budgets/budget_action.go index b97eedd7815..f33f263f529 100644 --- a/internal/service/budgets/budget_action.go +++ b/internal/service/budgets/budget_action.go @@ -38,8 +38,9 @@ func ResourceBudgetAction() *schema.Resource { }, Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(5 * time.Minute), - Update: schema.DefaultTimeout(5 * time.Minute), + Create: schema.DefaultTimeout(5 * time.Minute), // unneeded, but a breaking change to remove + Update: schema.DefaultTimeout(5 * time.Minute), // unneeded, but a breaking change to remove + Delete: schema.DefaultTimeout(5 * time.Minute), }, Schema: map[string]*schema.Schema{ @@ -253,10 +254,6 @@ func resourceBudgetActionCreate(ctx context.Context, d *schema.ResourceData, met d.SetId(BudgetActionCreateResourceID(accountID, actionID, budgetName)) - if _, err := waitActionAvailable(ctx, conn, accountID, actionID, budgetName, d.Timeout(schema.TimeoutCreate)); err != nil { - return diag.Errorf("waiting for Budget Action (%s) create: %s", d.Id(), err) - } - return resourceBudgetActionRead(ctx, d, meta) } @@ -354,10 +351,6 @@ func resourceBudgetActionUpdate(ctx context.Context, d *schema.ResourceData, met return diag.Errorf("updating Budget Action (%s): %s", d.Id(), err) } - if _, err := waitActionAvailable(ctx, conn, accountID, actionID, budgetName, d.Timeout(schema.TimeoutUpdate)); err != nil { - return diag.Errorf("waiting for Budget Action (%s) update: %s", d.Id(), err) - } - return resourceBudgetActionRead(ctx, d, meta) } @@ -371,11 +364,13 @@ func resourceBudgetActionDelete(ctx context.Context, d *schema.ResourceData, met } log.Printf("[DEBUG] Deleting Budget Action: %s", d.Id()) - _, err = conn.DeleteBudgetActionWithContext(ctx, &budgets.DeleteBudgetActionInput{ - AccountId: aws.String(accountID), - ActionId: aws.String(actionID), - BudgetName: aws.String(budgetName), - }) + _, err = tfresource.RetryWhenAWSErrCodeEquals(ctx, d.Timeout(schema.TimeoutDelete), func() (any, error) { + return conn.DeleteBudgetActionWithContext(ctx, &budgets.DeleteBudgetActionInput{ + AccountId: aws.String(accountID), + ActionId: aws.String(actionID), + BudgetName: aws.String(budgetName), + }) + }, budgets.ErrCodeResourceLockedException) if tfawserr.ErrCodeEquals(err, budgets.ErrCodeNotFoundException) { return nil @@ -454,30 +449,6 @@ func statusAction(ctx context.Context, conn *budgets.Budgets, accountID, actionI } } -func waitActionAvailable(ctx context.Context, conn *budgets.Budgets, accountID, actionID, budgetName string, timeout time.Duration) (*budgets.Action, error) { //nolint:unparam - stateConf := &retry.StateChangeConf{ - Pending: []string{ - budgets.ActionStatusExecutionInProgress, - budgets.ActionStatusStandby, - }, - Target: []string{ - budgets.ActionStatusExecutionSuccess, - budgets.ActionStatusExecutionFailure, - budgets.ActionStatusPending, - }, - Refresh: statusAction(ctx, conn, accountID, actionID, budgetName), - Timeout: timeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - - if v, ok := outputRaw.(*budgets.Action); ok { - return v, err - } - - return nil, err -} - func expandBudgetActionActionThreshold(l []interface{}) *budgets.ActionThreshold { if len(l) == 0 || l[0] == nil { return nil diff --git a/internal/service/budgets/budget_action_test.go b/internal/service/budgets/budget_action_test.go index 7fa6b4a9ec8..078f9edf666 100644 --- a/internal/service/budgets/budget_action_test.go +++ b/internal/service/budgets/budget_action_test.go @@ -25,6 +25,52 @@ func TestAccBudgetsBudgetAction_basic(t *testing.T) { resourceName := "aws_budgets_budget_action.test" var conf budgets.Action + const thresholdValue = "1000000000" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckPartitionHasService(t, budgets.EndpointsID) }, + ErrorCheck: acctest.ErrorCheck(t, budgets.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckBudgetActionDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccBudgetActionConfig_basic(rName, budgets.ApprovalModelAutomatic, thresholdValue), + Check: resource.ComposeTestCheckFunc( + testAccBudgetActionExists(ctx, resourceName, &conf), + acctest.MatchResourceAttrGlobalARN(resourceName, "arn", "budgets", regexp.MustCompile(fmt.Sprintf(`budget/%s/action/.+`, rName))), + resource.TestCheckResourceAttrPair(resourceName, "budget_name", "aws_budgets_budget.test", "name"), + resource.TestCheckResourceAttrPair(resourceName, "execution_role_arn", "aws_iam_role.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "action_type", "APPLY_IAM_POLICY"), + resource.TestCheckResourceAttr(resourceName, "approval_model", budgets.ApprovalModelAutomatic), + resource.TestCheckResourceAttr(resourceName, "notification_type", "ACTUAL"), + resource.TestCheckResourceAttr(resourceName, "action_threshold.#", "1"), + resource.TestCheckResourceAttr(resourceName, "action_threshold.0.action_threshold_type", "ABSOLUTE_VALUE"), + resource.TestCheckResourceAttr(resourceName, "action_threshold.0.action_threshold_value", thresholdValue), + resource.TestCheckResourceAttr(resourceName, "definition.#", "1"), + resource.TestCheckResourceAttr(resourceName, "definition.0.iam_action_definition.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "definition.0.iam_action_definition.0.policy_arn", "aws_iam_policy.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "definition.0.iam_action_definition.0.roles.#", "1"), + resource.TestCheckResourceAttr(resourceName, "subscriber.#", "1"), + resource.TestCheckResourceAttr(resourceName, "status", budgets.ActionStatusStandby), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccBudgetsBudgetAction_triggeredAutomatic(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_budgets_budget_action.test" + var conf budgets.Action + + const thresholdValue = "100" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckPartitionHasService(t, budgets.EndpointsID) }, ErrorCheck: acctest.ErrorCheck(t, budgets.EndpointsID), @@ -32,23 +78,68 @@ func TestAccBudgetsBudgetAction_basic(t *testing.T) { CheckDestroy: testAccCheckBudgetActionDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccBudgetActionConfig_basic(rName), + Config: testAccBudgetActionConfig_basic(rName, budgets.ApprovalModelAutomatic, thresholdValue), Check: resource.ComposeTestCheckFunc( testAccBudgetActionExists(ctx, resourceName, &conf), acctest.MatchResourceAttrGlobalARN(resourceName, "arn", "budgets", regexp.MustCompile(fmt.Sprintf(`budget/%s/action/.+`, rName))), resource.TestCheckResourceAttrPair(resourceName, "budget_name", "aws_budgets_budget.test", "name"), resource.TestCheckResourceAttrPair(resourceName, "execution_role_arn", "aws_iam_role.test", "arn"), resource.TestCheckResourceAttr(resourceName, "action_type", "APPLY_IAM_POLICY"), - resource.TestCheckResourceAttr(resourceName, "approval_model", "AUTOMATIC"), + resource.TestCheckResourceAttr(resourceName, "approval_model", budgets.ApprovalModelAutomatic), resource.TestCheckResourceAttr(resourceName, "notification_type", "ACTUAL"), resource.TestCheckResourceAttr(resourceName, "action_threshold.#", "1"), resource.TestCheckResourceAttr(resourceName, "action_threshold.0.action_threshold_type", "ABSOLUTE_VALUE"), - resource.TestCheckResourceAttr(resourceName, "action_threshold.0.action_threshold_value", "100"), + resource.TestCheckResourceAttr(resourceName, "action_threshold.0.action_threshold_value", thresholdValue), resource.TestCheckResourceAttr(resourceName, "definition.#", "1"), resource.TestCheckResourceAttr(resourceName, "definition.0.iam_action_definition.#", "1"), resource.TestCheckResourceAttrPair(resourceName, "definition.0.iam_action_definition.0.policy_arn", "aws_iam_policy.test", "arn"), resource.TestCheckResourceAttr(resourceName, "definition.0.iam_action_definition.0.roles.#", "1"), resource.TestCheckResourceAttr(resourceName, "subscriber.#", "1"), + resource.TestCheckResourceAttrSet(resourceName, "status"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccBudgetsBudgetAction_triggeredManual(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_budgets_budget_action.test" + var conf budgets.Action + + const thresholdValue = "100" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckPartitionHasService(t, budgets.EndpointsID) }, + ErrorCheck: acctest.ErrorCheck(t, budgets.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckBudgetActionDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccBudgetActionConfig_basic(rName, budgets.ApprovalModelManual, thresholdValue), + Check: resource.ComposeTestCheckFunc( + testAccBudgetActionExists(ctx, resourceName, &conf), + acctest.MatchResourceAttrGlobalARN(resourceName, "arn", "budgets", regexp.MustCompile(fmt.Sprintf(`budget/%s/action/.+`, rName))), + resource.TestCheckResourceAttrPair(resourceName, "budget_name", "aws_budgets_budget.test", "name"), + resource.TestCheckResourceAttrPair(resourceName, "execution_role_arn", "aws_iam_role.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "action_type", "APPLY_IAM_POLICY"), + resource.TestCheckResourceAttr(resourceName, "approval_model", budgets.ApprovalModelManual), + resource.TestCheckResourceAttr(resourceName, "notification_type", "ACTUAL"), + resource.TestCheckResourceAttr(resourceName, "action_threshold.#", "1"), + resource.TestCheckResourceAttr(resourceName, "action_threshold.0.action_threshold_type", "ABSOLUTE_VALUE"), + resource.TestCheckResourceAttr(resourceName, "action_threshold.0.action_threshold_value", thresholdValue), + resource.TestCheckResourceAttr(resourceName, "definition.#", "1"), + resource.TestCheckResourceAttr(resourceName, "definition.0.iam_action_definition.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "definition.0.iam_action_definition.0.policy_arn", "aws_iam_policy.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "definition.0.iam_action_definition.0.roles.#", "1"), + resource.TestCheckResourceAttr(resourceName, "subscriber.#", "1"), + resource.TestCheckResourceAttrSet(resourceName, "status"), // Race condition between "STANDBY" and "PENDING" ), }, { @@ -73,7 +164,7 @@ func TestAccBudgetsBudgetAction_disappears(t *testing.T) { CheckDestroy: testAccCheckBudgetActionDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccBudgetActionConfig_basic(rName), + Config: testAccBudgetActionConfig_basic(rName, budgets.ApprovalModelAutomatic, "100"), Check: resource.ComposeTestCheckFunc( testAccBudgetActionExists(ctx, resourceName, &conf), acctest.CheckResourceDisappears(ctx, acctest.Provider, tfbudgets.ResourceBudgetAction(), resourceName), @@ -147,18 +238,18 @@ func testAccCheckBudgetActionDestroy(ctx context.Context) resource.TestCheckFunc } } -func testAccBudgetActionConfig_basic(rName string) string { +func testAccBudgetActionConfig_basic(rName, approvalModel, thresholdValue string) string { return fmt.Sprintf(` resource "aws_budgets_budget_action" "test" { budget_name = aws_budgets_budget.test.name action_type = "APPLY_IAM_POLICY" - approval_model = "AUTOMATIC" + approval_model = %[2]q notification_type = "ACTUAL" execution_role_arn = aws_iam_role.test.arn action_threshold { action_threshold_type = "ABSOLUTE_VALUE" - action_threshold_value = 100 + action_threshold_value = %[3]s } definition { @@ -169,7 +260,7 @@ resource "aws_budgets_budget_action" "test" { } subscriber { - address = "test@test.test" + address = %[4]q subscription_type = "EMAIL" } } @@ -227,39 +318,5 @@ resource "aws_iam_role" "test" { } EOF } - -resource "aws_budgets_budget" "test" { - name = %[1]q - budget_type = "USAGE" - limit_amount = "10.0" - limit_unit = "dollars" - time_period_start = "2006-01-02_15:04" - time_unit = "MONTHLY" -} - -resource "aws_budgets_budget_action" "test" { - budget_name = aws_budgets_budget.test.name - action_type = "APPLY_IAM_POLICY" - approval_model = "AUTOMATIC" - notification_type = "ACTUAL" - execution_role_arn = aws_iam_role.test.arn - - action_threshold { - action_threshold_type = "ABSOLUTE_VALUE" - action_threshold_value = 100 - } - - definition { - iam_action_definition { - policy_arn = aws_iam_policy.test.arn - roles = [aws_iam_role.test.name] - } - } - - subscriber { - address = "test@test.test" - subscription_type = "EMAIL" - } -} -`, rName) +`, rName, approvalModel, thresholdValue, acctest.DefaultEmailAddress) } From 8984343c75bd5255047c715d8f302043094bab68 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 14 Aug 2023 17:35:22 -0700 Subject: [PATCH 3/5] Appends to `diags` --- internal/service/budgets/budget_action.go | 33 +++++++++++++---------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/internal/service/budgets/budget_action.go b/internal/service/budgets/budget_action.go index f33f263f529..0e4a9ce89f0 100644 --- a/internal/service/budgets/budget_action.go +++ b/internal/service/budgets/budget_action.go @@ -20,6 +20,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" @@ -222,6 +223,7 @@ func ResourceBudgetAction() *schema.Resource { } func resourceBudgetActionCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BudgetsConn(ctx) accountID := d.Get("account_id").(string) @@ -245,7 +247,7 @@ func resourceBudgetActionCreate(ctx context.Context, d *schema.ResourceData, met }, budgets.ErrCodeAccessDeniedException) if err != nil { - return diag.Errorf("creating Budget Action: %s", err) + return sdkdiag.AppendErrorf(diags, "creating Budget Action: %s", err) } output := outputRaw.(*budgets.CreateBudgetActionOutput) @@ -254,16 +256,17 @@ func resourceBudgetActionCreate(ctx context.Context, d *schema.ResourceData, met d.SetId(BudgetActionCreateResourceID(accountID, actionID, budgetName)) - return resourceBudgetActionRead(ctx, d, meta) + return append(diags, resourceBudgetActionRead(ctx, d, meta)...) } func resourceBudgetActionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BudgetsConn(ctx) accountID, actionID, budgetName, err := BudgetActionParseResourceID(d.Id()) if err != nil { - return diag.FromErr(err) + return sdkdiag.AppendFromErr(diags, err) } output, err := FindActionByThreePartKey(ctx, conn, accountID, actionID, budgetName) @@ -275,13 +278,13 @@ func resourceBudgetActionRead(ctx context.Context, d *schema.ResourceData, meta } if err != nil { - return diag.Errorf("reading Budget Action (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading Budget Action (%s): %s", d.Id(), err) } d.Set("account_id", accountID) d.Set("action_id", actionID) if err := d.Set("action_threshold", flattenBudgetActionActionThreshold(output.ActionThreshold)); err != nil { - return diag.Errorf("setting action_threshold: %s", err) + return sdkdiag.AppendErrorf(diags, "setting action_threshold: %s", err) } d.Set("action_type", output.ActionType) d.Set("approval_model", output.ApprovalModel) @@ -294,25 +297,26 @@ func resourceBudgetActionRead(ctx context.Context, d *schema.ResourceData, meta d.Set("arn", arn.String()) d.Set("budget_name", budgetName) if err := d.Set("definition", flattenBudgetActionDefinition(output.Definition)); err != nil { - return diag.Errorf("setting definition: %s", err) + return sdkdiag.AppendErrorf(diags, "setting definition: %s", err) } d.Set("execution_role_arn", output.ExecutionRoleArn) d.Set("notification_type", output.NotificationType) d.Set("status", output.Status) if err := d.Set("subscriber", flattenBudgetActionSubscriber(output.Subscribers)); err != nil { - return diag.Errorf("setting subscriber: %s", err) + return sdkdiag.AppendErrorf(diags, "setting subscriber: %s", err) } - return nil + return diags } func resourceBudgetActionUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BudgetsConn(ctx) accountID, actionID, budgetName, err := BudgetActionParseResourceID(d.Id()) if err != nil { - return diag.FromErr(err) + return sdkdiag.AppendFromErr(diags, err) } input := &budgets.UpdateBudgetActionInput{ @@ -348,19 +352,20 @@ func resourceBudgetActionUpdate(ctx context.Context, d *schema.ResourceData, met _, err = conn.UpdateBudgetActionWithContext(ctx, input) if err != nil { - return diag.Errorf("updating Budget Action (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "updating Budget Action (%s): %s", d.Id(), err) } - return resourceBudgetActionRead(ctx, d, meta) + return append(diags, resourceBudgetActionRead(ctx, d, meta)...) } func resourceBudgetActionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BudgetsConn(ctx) accountID, actionID, budgetName, err := BudgetActionParseResourceID(d.Id()) if err != nil { - return diag.FromErr(err) + return sdkdiag.AppendFromErr(diags, err) } log.Printf("[DEBUG] Deleting Budget Action: %s", d.Id()) @@ -377,10 +382,10 @@ func resourceBudgetActionDelete(ctx context.Context, d *schema.ResourceData, met } if err != nil { - return diag.Errorf("deleting Budget Action (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "deleting Budget Action (%s): %s", d.Id(), err) } - return nil + return diags } const budgetActionResourceIDSeparator = ":" From 6c9e356c6876fda42f006df63491a9bcd6bbd845 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 14 Aug 2023 17:47:25 -0700 Subject: [PATCH 4/5] Adds CHANGELOG entry --- .changelog/33015.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/33015.txt diff --git a/.changelog/33015.txt b/.changelog/33015.txt new file mode 100644 index 00000000000..c2334e9b358 --- /dev/null +++ b/.changelog/33015.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_budgets_budget_action: No longer times out when creating a non-triggered action +``` From b7e69752fc0a68c47c42edec19637ceb7b6e9fb5 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 15 Aug 2023 10:50:24 -0700 Subject: [PATCH 5/5] Removes unused function --- internal/service/budgets/budget_action.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/internal/service/budgets/budget_action.go b/internal/service/budgets/budget_action.go index 0e4a9ce89f0..50ba698bf21 100644 --- a/internal/service/budgets/budget_action.go +++ b/internal/service/budgets/budget_action.go @@ -438,22 +438,6 @@ func FindActionByThreePartKey(ctx context.Context, conn *budgets.Budgets, accoun return output.Action, nil } -func statusAction(ctx context.Context, conn *budgets.Budgets, accountID, actionID, budgetName string) retry.StateRefreshFunc { - return func() (interface{}, string, error) { - output, err := FindActionByThreePartKey(ctx, conn, accountID, actionID, budgetName) - - if tfresource.NotFound(err) { - return nil, "", nil - } - - if err != nil { - return nil, "", err - } - - return output, aws.StringValue(output.Status), nil - } -} - func expandBudgetActionActionThreshold(l []interface{}) *budgets.ActionThreshold { if len(l) == 0 || l[0] == nil { return nil