diff --git a/.changelog/13474.txt b/.changelog/13474.txt new file mode 100644 index 00000000000..04eccad08e9 --- /dev/null +++ b/.changelog/13474.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_redshift_scheduled_action +``` \ No newline at end of file diff --git a/aws/internal/service/redshift/errors.go b/aws/internal/service/redshift/errors.go new file mode 100644 index 00000000000..53a43bd1d80 --- /dev/null +++ b/aws/internal/service/redshift/errors.go @@ -0,0 +1,5 @@ +package redshift + +const ( + ErrCodeInvalidParameterValue = "InvalidParameterValue" +) diff --git a/aws/internal/service/redshift/finder/finder.go b/aws/internal/service/redshift/finder/finder.go index 10cf6fabee6..2688b090b88 100644 --- a/aws/internal/service/redshift/finder/finder.go +++ b/aws/internal/service/redshift/finder/finder.go @@ -36,3 +36,32 @@ func ClusterByID(conn *redshift.Redshift, id string) (*redshift.Cluster, error) return output.Clusters[0], nil } + +func ScheduledActionByName(conn *redshift.Redshift, name string) (*redshift.ScheduledAction, error) { + input := &redshift.DescribeScheduledActionsInput{ + ScheduledActionName: aws.String(name), + } + + output, err := conn.DescribeScheduledActions(input) + + if tfawserr.ErrCodeEquals(err, redshift.ErrCodeScheduledActionNotFoundFault) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.ScheduledActions) == 0 || output.ScheduledActions[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output.ScheduledActions); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output.ScheduledActions[0], nil +} diff --git a/aws/provider.go b/aws/provider.go index 6e990105ddf..ffc11c05617 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -1010,6 +1010,7 @@ func Provider() *schema.Provider { "aws_redshift_snapshot_schedule": resourceAwsRedshiftSnapshotSchedule(), "aws_redshift_snapshot_schedule_association": resourceAwsRedshiftSnapshotScheduleAssociation(), "aws_redshift_event_subscription": resourceAwsRedshiftEventSubscription(), + "aws_redshift_scheduled_action": resourceAwsRedshiftScheduledAction(), "aws_resourcegroups_group": resourceAwsResourceGroupsGroup(), "aws_route53_delegation_set": resourceAwsRoute53DelegationSet(), "aws_route53_hosted_zone_dnssec": resourceAwsRoute53HostedZoneDnssec(), diff --git a/aws/resource_aws_iam_role_test.go b/aws/resource_aws_iam_role_test.go index 7d0fab32827..2b664b958ed 100644 --- a/aws/resource_aws_iam_role_test.go +++ b/aws/resource_aws_iam_role_test.go @@ -42,6 +42,7 @@ func init() { "aws_lambda_function", "aws_launch_configuration", "aws_redshift_cluster", + "aws_redshift_scheduled_action", "aws_spot_fleet_request", }, F: testSweepIamRoles, diff --git a/aws/resource_aws_redshift_scheduled_action.go b/aws/resource_aws_redshift_scheduled_action.go new file mode 100644 index 00000000000..697d9136029 --- /dev/null +++ b/aws/resource_aws_redshift_scheduled_action.go @@ -0,0 +1,471 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/redshift" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + iamwaiter "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter" + tfredshift "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/redshift" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/redshift/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" +) + +func resourceAwsRedshiftScheduledAction() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsRedshiftScheduledActionCreate, + Read: resourceAwsRedshiftScheduledActionRead, + Update: resourceAwsRedshiftScheduledActionUpdate, + Delete: resourceAwsRedshiftScheduledActionDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "description": { + Type: schema.TypeString, + Optional: true, + }, + "enable": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "end_time": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.IsRFC3339Time, + }, + "iam_role": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "schedule": { + Type: schema.TypeString, + Required: true, + }, + "start_time": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.IsRFC3339Time, + }, + "target_action": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "pause_cluster": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cluster_identifier": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + ExactlyOneOf: []string{ + "target_action.0.pause_cluster", + "target_action.0.resize_cluster", + "target_action.0.resume_cluster", + }, + }, + "resize_cluster": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "classic": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "cluster_identifier": { + Type: schema.TypeString, + Required: true, + }, + "cluster_type": { + Type: schema.TypeString, + Optional: true, + }, + "node_type": { + Type: schema.TypeString, + Optional: true, + }, + "number_of_nodes": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + ExactlyOneOf: []string{ + "target_action.0.pause_cluster", + "target_action.0.resize_cluster", + "target_action.0.resume_cluster", + }, + }, + "resume_cluster": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cluster_identifier": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + ExactlyOneOf: []string{ + "target_action.0.pause_cluster", + "target_action.0.resize_cluster", + "target_action.0.resume_cluster", + }, + }, + }, + }, + }, + }, + } +} + +func resourceAwsRedshiftScheduledActionCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).redshiftconn + + name := d.Get("name").(string) + input := &redshift.CreateScheduledActionInput{ + Enable: aws.Bool(d.Get("enable").(bool)), + IamRole: aws.String(d.Get("iam_role").(string)), + Schedule: aws.String(d.Get("schedule").(string)), + ScheduledActionName: aws.String(name), + TargetAction: expandRedshiftScheduledActionType(d.Get("target_action").([]interface{})[0].(map[string]interface{})), + } + + if v, ok := d.GetOk("description"); ok { + input.ScheduledActionDescription = aws.String(v.(string)) + } + + if v, ok := d.GetOk("end_time"); ok { + t, _ := time.Parse(time.RFC3339, v.(string)) + + input.EndTime = aws.Time(t) + } + + if v, ok := d.GetOk("start_time"); ok { + t, _ := time.Parse(time.RFC3339, v.(string)) + + input.StartTime = aws.Time(t) + } + + log.Printf("[DEBUG] Creating Redshift Scheduled Action: %s", input) + outputRaw, err := tfresource.RetryWhen( + iamwaiter.PropagationTimeout, + func() (interface{}, error) { + return conn.CreateScheduledAction(input) + }, + func(err error) (bool, error) { + if tfawserr.ErrMessageContains(err, tfredshift.ErrCodeInvalidParameterValue, "The IAM role must delegate access to Amazon Redshift scheduler") { + return true, err + } + + return false, err + }, + ) + + if err != nil { + return fmt.Errorf("error creating Redshift Scheduled Action (%s): %w", name, err) + } + + d.SetId(aws.StringValue(outputRaw.(*redshift.CreateScheduledActionOutput).ScheduledActionName)) + + return resourceAwsRedshiftScheduledActionRead(d, meta) +} + +func resourceAwsRedshiftScheduledActionRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).redshiftconn + + scheduledAction, err := finder.ScheduledActionByName(conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Redshift Scheduled Action (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading Redshift Scheduled Action (%s): %w", d.Id(), err) + } + + d.Set("description", scheduledAction.ScheduledActionDescription) + if aws.StringValue(scheduledAction.State) == redshift.ScheduledActionStateActive { + d.Set("enable", true) + } else { + d.Set("enable", false) + } + if scheduledAction.EndTime != nil { + d.Set("end_time", aws.TimeValue(scheduledAction.EndTime).Format(time.RFC3339)) + } else { + d.Set("end_time", nil) + } + d.Set("iam_role", scheduledAction.IamRole) + d.Set("name", scheduledAction.ScheduledActionName) + d.Set("schedule", scheduledAction.Schedule) + if scheduledAction.StartTime != nil { + d.Set("start_time", aws.TimeValue(scheduledAction.StartTime).Format(time.RFC3339)) + } else { + d.Set("start_time", nil) + } + + if scheduledAction.TargetAction != nil { + if err := d.Set("target_action", []interface{}{flattenRedshiftScheduledActionType(scheduledAction.TargetAction)}); err != nil { + return fmt.Errorf("error setting target_action: %w", err) + } + } else { + d.Set("target_action", nil) + } + + return nil +} + +func resourceAwsRedshiftScheduledActionUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).redshiftconn + + input := &redshift.ModifyScheduledActionInput{ + ScheduledActionName: aws.String(d.Get("name").(string)), + } + + if d.HasChange("description") { + input.ScheduledActionDescription = aws.String(d.Get("description").(string)) + } + + if d.HasChange("enable") { + input.Enable = aws.Bool(d.Get("enable").(bool)) + } + + if hasChange, v := d.HasChange("end_time"), d.Get("end_time").(string); hasChange && v != "" { + t, _ := time.Parse(time.RFC3339, v) + + input.EndTime = aws.Time(t) + } + + if d.HasChange("iam_role") { + input.IamRole = aws.String(d.Get("iam_role").(string)) + } + + if d.HasChange("schedule") { + input.Schedule = aws.String(d.Get("schedule").(string)) + } + + if hasChange, v := d.HasChange("start_time"), d.Get("start_time").(string); hasChange && v != "" { + t, _ := time.Parse(time.RFC3339, v) + + input.StartTime = aws.Time(t) + } + + if d.HasChange("target_action") { + input.TargetAction = expandRedshiftScheduledActionType(d.Get("target_action").([]interface{})[0].(map[string]interface{})) + } + + log.Printf("[DEBUG] Updating Redshift Scheduled Action: %s", input) + _, err := conn.ModifyScheduledAction(input) + + if err != nil { + return fmt.Errorf("error updating Redshift Scheduled Action (%s): %w", d.Id(), err) + } + + return nil +} + +func resourceAwsRedshiftScheduledActionDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).redshiftconn + + log.Printf("[DEBUG] Deleting Redshift Scheduled Action: %s", d.Id()) + _, err := conn.DeleteScheduledAction(&redshift.DeleteScheduledActionInput{ + ScheduledActionName: aws.String(d.Id()), + }) + + if tfawserr.ErrCodeEquals(err, redshift.ErrCodeScheduledActionNotFoundFault) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting Redshift Scheduled Action (%s): %w", d.Id(), err) + } + + return nil +} + +func expandRedshiftScheduledActionType(tfMap map[string]interface{}) *redshift.ScheduledActionType { + if tfMap == nil { + return nil + } + + apiObject := &redshift.ScheduledActionType{} + + if v, ok := tfMap["pause_cluster"].([]interface{}); ok && len(v) > 0 { + apiObject.PauseCluster = expandRedshiftPauseClusterMessage(v[0].(map[string]interface{})) + } + + if v, ok := tfMap["resize_cluster"].([]interface{}); ok && len(v) > 0 { + apiObject.ResizeCluster = expandRedshiftResizeClusterMessage(v[0].(map[string]interface{})) + } + + if v, ok := tfMap["resume_cluster"].([]interface{}); ok && len(v) > 0 { + apiObject.ResumeCluster = expandRedshiftResumeClusterMessage(v[0].(map[string]interface{})) + } + + return apiObject +} + +func expandRedshiftPauseClusterMessage(tfMap map[string]interface{}) *redshift.PauseClusterMessage { + if tfMap == nil { + return nil + } + + apiObject := &redshift.PauseClusterMessage{} + + if v, ok := tfMap["cluster_identifier"].(string); ok && v != "" { + apiObject.ClusterIdentifier = aws.String(v) + } + + return apiObject +} + +func expandRedshiftResizeClusterMessage(tfMap map[string]interface{}) *redshift.ResizeClusterMessage { + if tfMap == nil { + return nil + } + + apiObject := &redshift.ResizeClusterMessage{} + + if v, ok := tfMap["classic"].(bool); ok { + apiObject.Classic = aws.Bool(v) + } + + if v, ok := tfMap["cluster_identifier"].(string); ok && v != "" { + apiObject.ClusterIdentifier = aws.String(v) + } + + if v, ok := tfMap["cluster_type"].(string); ok && v != "" { + apiObject.ClusterType = aws.String(v) + } + + if v, ok := tfMap["node_type"].(string); ok && v != "" { + apiObject.NodeType = aws.String(v) + } + + if v, ok := tfMap["number_of_nodes"].(int); ok && v != 0 { + apiObject.NumberOfNodes = aws.Int64(int64(v)) + } + + return apiObject +} + +func expandRedshiftResumeClusterMessage(tfMap map[string]interface{}) *redshift.ResumeClusterMessage { + if tfMap == nil { + return nil + } + + apiObject := &redshift.ResumeClusterMessage{} + + if v, ok := tfMap["cluster_identifier"].(string); ok && v != "" { + apiObject.ClusterIdentifier = aws.String(v) + } + + return apiObject +} + +func flattenRedshiftScheduledActionType(apiObject *redshift.ScheduledActionType) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.PauseCluster; v != nil { + tfMap["pause_cluster"] = []interface{}{flattenRedshiftPauseClusterMessage(v)} + } + + if v := apiObject.ResizeCluster; v != nil { + tfMap["resize_cluster"] = []interface{}{flattenRedshiftResizeClusterMessage(v)} + } + + if v := apiObject.ResumeCluster; v != nil { + tfMap["resume_cluster"] = []interface{}{flattenRedshiftResumeClusterMessage(v)} + } + + return tfMap +} + +func flattenRedshiftPauseClusterMessage(apiObject *redshift.PauseClusterMessage) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.ClusterIdentifier; v != nil { + tfMap["cluster_identifier"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenRedshiftResizeClusterMessage(apiObject *redshift.ResizeClusterMessage) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.Classic; v != nil { + tfMap["classic"] = aws.BoolValue(v) + } + + if v := apiObject.ClusterIdentifier; v != nil { + tfMap["cluster_identifier"] = aws.StringValue(v) + } + + if v := apiObject.ClusterType; v != nil { + tfMap["cluster_type"] = aws.StringValue(v) + } + + if v := apiObject.NodeType; v != nil { + tfMap["node_type"] = aws.StringValue(v) + } + + if v := apiObject.NumberOfNodes; v != nil { + tfMap["number_of_nodes"] = aws.Int64Value(v) + } + + return tfMap +} + +func flattenRedshiftResumeClusterMessage(apiObject *redshift.ResumeClusterMessage) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.ClusterIdentifier; v != nil { + tfMap["cluster_identifier"] = aws.StringValue(v) + } + + return tfMap +} diff --git a/aws/resource_aws_redshift_scheduled_action_test.go b/aws/resource_aws_redshift_scheduled_action_test.go new file mode 100644 index 00000000000..28dd1294f88 --- /dev/null +++ b/aws/resource_aws_redshift_scheduled_action_test.go @@ -0,0 +1,522 @@ +package aws + +import ( + "fmt" + "log" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/redshift" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/redshift/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" +) + +func init() { + resource.AddTestSweepers("aws_redshift_scheduled_action", &resource.Sweeper{ + Name: "aws_redshift_scheduled_action", + F: testSweepRedshiftScheduledActions, + }) +} + +func testSweepRedshiftScheduledActions(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*AWSClient).redshiftconn + input := &redshift.DescribeScheduledActionsInput{} + sweepResources := make([]*testSweepResource, 0) + + err = conn.DescribeScheduledActionsPages(input, func(page *redshift.DescribeScheduledActionsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, scheduledAction := range page.ScheduledActions { + r := resourceAwsRedshiftScheduledAction() + d := r.Data(nil) + d.SetId(aws.StringValue(scheduledAction.ScheduledActionName)) + + sweepResources = append(sweepResources, NewTestSweepResource(r, d, client)) + } + + return !lastPage + }) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping Redshift Scheduled Action sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing Redshift Scheduled Actions (%s): %w", region, err) + } + + err = testSweepResourceOrchestrator(sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping Redshift Scheduled Actions (%s): %w", region, err) + } + + return nil +} + +func TestAccAWSRedshiftScheduledAction_basicPauseCluster(t *testing.T) { + var v redshift.ScheduledAction + resourceName := "aws_redshift_scheduled_action.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRedshiftScheduledActionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRedshiftScheduledActionConfigPauseCluster(rName, "cron(00 23 * * ? *)"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRedshiftScheduledActionExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "enable", "true"), + resource.TestCheckResourceAttr(resourceName, "end_time", ""), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schedule", "cron(00 23 * * ? *)"), + resource.TestCheckResourceAttr(resourceName, "start_time", ""), + resource.TestCheckResourceAttr(resourceName, "target_action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.pause_cluster.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resize_cluster.#", "0"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resume_cluster.#", "0"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.pause_cluster.0.cluster_identifier", "tf-test-identifier"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSRedshiftScheduledActionConfigPauseCluster(rName, "at(2060-03-04T17:27:00)"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRedshiftScheduledActionExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "enable", "true"), + resource.TestCheckResourceAttr(resourceName, "end_time", ""), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schedule", "at(2060-03-04T17:27:00)"), + resource.TestCheckResourceAttr(resourceName, "start_time", ""), + resource.TestCheckResourceAttr(resourceName, "target_action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.pause_cluster.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resize_cluster.#", "0"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resume_cluster.#", "0"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.pause_cluster.0.cluster_identifier", "tf-test-identifier"), + ), + }, + }, + }) +} + +func TestAccAWSRedshiftScheduledAction_PauseClusterWithOptions(t *testing.T) { + var v redshift.ScheduledAction + resourceName := "aws_redshift_scheduled_action.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + startTime := time.Now().UTC().Add(1 * time.Hour).Format(time.RFC3339) + endTime := time.Now().UTC().Add(2 * time.Hour).Format(time.RFC3339) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRedshiftScheduledActionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRedshiftScheduledActionConfigPauseClusterWithFullOptions(rName, "cron(00 * * * ? *)", "This is test action", true, startTime, endTime), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRedshiftScheduledActionExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "description", "This is test action"), + resource.TestCheckResourceAttr(resourceName, "enable", "true"), + resource.TestCheckResourceAttr(resourceName, "end_time", endTime), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schedule", "cron(00 * * * ? *)"), + resource.TestCheckResourceAttr(resourceName, "start_time", startTime), + resource.TestCheckResourceAttr(resourceName, "target_action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.pause_cluster.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resize_cluster.#", "0"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resume_cluster.#", "0"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.pause_cluster.0.cluster_identifier", "tf-test-identifier"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSRedshiftScheduledAction_basicResumeCluster(t *testing.T) { + var v redshift.ScheduledAction + resourceName := "aws_redshift_scheduled_action.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRedshiftScheduledActionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRedshiftScheduledActionConfigResumeCluster(rName, "cron(00 23 * * ? *)"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRedshiftScheduledActionExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "enable", "true"), + resource.TestCheckResourceAttr(resourceName, "end_time", ""), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schedule", "cron(00 23 * * ? *)"), + resource.TestCheckResourceAttr(resourceName, "start_time", ""), + resource.TestCheckResourceAttr(resourceName, "target_action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.pause_cluster.#", "0"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resize_cluster.#", "0"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resume_cluster.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resume_cluster.0.cluster_identifier", "tf-test-identifier"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSRedshiftScheduledActionConfigResumeCluster(rName, "at(2060-03-04T17:27:00)"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRedshiftScheduledActionExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "enable", "true"), + resource.TestCheckResourceAttr(resourceName, "end_time", ""), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schedule", "at(2060-03-04T17:27:00)"), + resource.TestCheckResourceAttr(resourceName, "start_time", ""), + resource.TestCheckResourceAttr(resourceName, "target_action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.pause_cluster.#", "0"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resize_cluster.#", "0"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resume_cluster.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resume_cluster.0.cluster_identifier", "tf-test-identifier"), + ), + }, + }, + }) +} + +func TestAccAWSRedshiftScheduledAction_basicResizeCluster(t *testing.T) { + var v redshift.ScheduledAction + resourceName := "aws_redshift_scheduled_action.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRedshiftScheduledActionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRedshiftScheduledActionConfigResizeClusterBasic(rName, "cron(00 23 * * ? *)"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRedshiftScheduledActionExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "enable", "true"), + resource.TestCheckResourceAttr(resourceName, "end_time", ""), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schedule", "cron(00 23 * * ? *)"), + resource.TestCheckResourceAttr(resourceName, "start_time", ""), + resource.TestCheckResourceAttr(resourceName, "target_action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.pause_cluster.#", "0"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resize_cluster.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resume_cluster.#", "0"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resize_cluster.0.cluster_identifier", "tf-test-identifier"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSRedshiftScheduledActionConfigResizeClusterBasic(rName, "at(2060-03-04T17:27:00)"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRedshiftScheduledActionExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "enable", "true"), + resource.TestCheckResourceAttr(resourceName, "end_time", ""), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schedule", "at(2060-03-04T17:27:00)"), + resource.TestCheckResourceAttr(resourceName, "start_time", ""), + resource.TestCheckResourceAttr(resourceName, "target_action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.pause_cluster.#", "0"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resize_cluster.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resume_cluster.#", "0"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resize_cluster.0.cluster_identifier", "tf-test-identifier"), + ), + }, + }, + }) +} + +func TestAccAWSRedshiftScheduledAction_ResizeClusterWithOptions(t *testing.T) { + var v redshift.ScheduledAction + resourceName := "aws_redshift_scheduled_action.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRedshiftScheduledActionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRedshiftScheduledActionConfigResizeClusterWithFullOptions(rName, "cron(00 23 * * ? *)", true, "multi-node", "dc1.large", 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRedshiftScheduledActionExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "enable", "true"), + resource.TestCheckResourceAttr(resourceName, "end_time", ""), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schedule", "cron(00 23 * * ? *)"), + resource.TestCheckResourceAttr(resourceName, "start_time", ""), + resource.TestCheckResourceAttr(resourceName, "target_action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.pause_cluster.#", "0"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resize_cluster.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resume_cluster.#", "0"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resize_cluster.0.classic", "true"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resize_cluster.0.cluster_identifier", "tf-test-identifier"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resize_cluster.0.cluster_type", "multi-node"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resize_cluster.0.node_type", "dc1.large"), + resource.TestCheckResourceAttr(resourceName, "target_action.0.resize_cluster.0.number_of_nodes", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSRedshiftScheduledAction_disappears(t *testing.T) { + var v redshift.ScheduledAction + resourceName := "aws_redshift_scheduled_action.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRedshiftScheduledActionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRedshiftScheduledActionConfigPauseCluster(rName, "cron(00 23 * * ? *)"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRedshiftScheduledActionExists(resourceName, &v), + testAccCheckResourceDisappears(testAccProvider, resourceAwsRedshiftScheduledAction(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckAWSRedshiftScheduledActionDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).redshiftconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_redshift_scheduled_action" { + continue + } + + _, err := finder.ScheduledActionByName(conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Redshift Scheduled Action %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckAWSRedshiftScheduledActionExists(n string, v *redshift.ScheduledAction) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Redshift Scheduled Action ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).redshiftconn + + output, err := finder.ScheduledActionByName(conn, rs.Primary.ID) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccAWSRedshiftScheduledActionConfigBase(rName string) string { + return fmt.Sprintf(` +resource "aws_iam_role" "test" { + name = %[1]q + + assume_role_policy = < github.com/gdavison/terraform-plugin-sdk/v2 v2.7.1-0.20210913224932-c7c2dbd9e010 +replace github.com/hashicorp/terraform-plugin-sdk/v2 => github.com/gdavison/terraform-plugin-sdk/v2 v2.7.1-0.20210913224932-c7c2dbd9e010 \ No newline at end of file diff --git a/website/docs/r/redshift_scheduled_action.html.markdown b/website/docs/r/redshift_scheduled_action.html.markdown new file mode 100644 index 00000000000..cfae369a912 --- /dev/null +++ b/website/docs/r/redshift_scheduled_action.html.markdown @@ -0,0 +1,144 @@ +--- +subcategory: "Redshift" +layout: "aws" +page_title: "AWS: aws_redshift_scheduled_action" +description: |- + Provides a Redshift Scheduled Action resource. +--- + +# Resource: aws_redshift_scheduled_action + +## Example Usage + +### Pause Cluster Action + +```terraform +resource "aws_iam_role" "example" { + name = "redshift_scheduled_action" + assume_role_policy = <