diff --git a/aws/provider.go b/aws/provider.go index 219efe2fe33..6ef07b28141 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -261,6 +261,7 @@ func Provider() terraform.ResourceProvider { "aws_app_cookie_stickiness_policy": resourceAwsAppCookieStickinessPolicy(), "aws_appautoscaling_target": resourceAwsAppautoscalingTarget(), "aws_appautoscaling_policy": resourceAwsAppautoscalingPolicy(), + "aws_appautoscaling_scheduled_action": resourceAwsAppautoscalingScheduledAction(), "aws_athena_database": resourceAwsAthenaDatabase(), "aws_athena_named_query": resourceAwsAthenaNamedQuery(), "aws_autoscaling_attachment": resourceAwsAutoscalingAttachment(), diff --git a/aws/resource_aws_appautoscaling_scheduled_action.go b/aws/resource_aws_appautoscaling_scheduled_action.go new file mode 100644 index 00000000000..0cccbeb4eba --- /dev/null +++ b/aws/resource_aws_appautoscaling_scheduled_action.go @@ -0,0 +1,191 @@ +package aws + +import ( + "fmt" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/applicationautoscaling" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +const awsAppautoscalingScheduleTimeLayout = "2006-01-02T15:04:05Z" + +func resourceAwsAppautoscalingScheduledAction() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsAppautoscalingScheduledActionPut, + Read: resourceAwsAppautoscalingScheduledActionRead, + Delete: resourceAwsAppautoscalingScheduledActionDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "service_namespace": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "resource_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "scalable_dimension": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "scalable_target_action": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max_capacity": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "min_capacity": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + "schedule": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "start_time": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "end_time": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "arn": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsAppautoscalingScheduledActionPut(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appautoscalingconn + + input := &applicationautoscaling.PutScheduledActionInput{ + ScheduledActionName: aws.String(d.Get("name").(string)), + ServiceNamespace: aws.String(d.Get("service_namespace").(string)), + ResourceId: aws.String(d.Get("resource_id").(string)), + } + if v, ok := d.GetOk("scalable_dimension"); ok { + input.ScalableDimension = aws.String(v.(string)) + } + if v, ok := d.GetOk("schedule"); ok { + input.Schedule = aws.String(v.(string)) + } + if v, ok := d.GetOk("scalable_target_action"); ok { + sta := &applicationautoscaling.ScalableTargetAction{} + raw := v.([]interface{})[0].(map[string]interface{}) + if max, ok := raw["max_capacity"]; ok { + sta.MaxCapacity = aws.Int64(int64(max.(int))) + } + if min, ok := raw["min_capacity"]; ok { + sta.MaxCapacity = aws.Int64(int64(min.(int))) + } + input.ScalableTargetAction = sta + } + if v, ok := d.GetOk("start_time"); ok { + t, err := time.Parse(awsAppautoscalingScheduleTimeLayout, v.(string)) + if err != nil { + return fmt.Errorf("Error Parsing Appautoscaling Scheduled Action Start Time: %s", err.Error()) + } + input.StartTime = aws.Time(t) + } + if v, ok := d.GetOk("end_time"); ok { + t, err := time.Parse(awsAppautoscalingScheduleTimeLayout, v.(string)) + if err != nil { + return fmt.Errorf("Error Parsing Appautoscaling Scheduled Action End Time: %s", err.Error()) + } + input.EndTime = aws.Time(t) + } + + err := resource.Retry(5*time.Minute, func() *resource.RetryError { + _, err := conn.PutScheduledAction(input) + if err != nil { + if isAWSErr(err, applicationautoscaling.ErrCodeObjectNotFoundException, "") { + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + } + return nil + }) + + if err != nil { + return err + } + + d.SetId(d.Get("name").(string) + "-" + d.Get("service_namespace").(string) + "-" + d.Get("resource_id").(string)) + return resourceAwsAppautoscalingScheduledActionRead(d, meta) +} + +func resourceAwsAppautoscalingScheduledActionRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appautoscalingconn + + saName := d.Get("name").(string) + input := &applicationautoscaling.DescribeScheduledActionsInput{ + ScheduledActionNames: []*string{aws.String(saName)}, + ServiceNamespace: aws.String(d.Get("service_namespace").(string)), + } + resp, err := conn.DescribeScheduledActions(input) + if err != nil { + return err + } + if len(resp.ScheduledActions) < 1 { + d.SetId("") + return nil + } + if len(resp.ScheduledActions) != 1 { + return fmt.Errorf("Expected 1 scheduled action under %s, found %d", saName, len(resp.ScheduledActions)) + } + if *resp.ScheduledActions[0].ScheduledActionName != saName { + return fmt.Errorf("Scheduled Action (%s) not found", saName) + } + d.Set("arn", resp.ScheduledActions[0].ScheduledActionARN) + return nil +} + +func resourceAwsAppautoscalingScheduledActionDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appautoscalingconn + + input := &applicationautoscaling.DeleteScheduledActionInput{ + ScheduledActionName: aws.String(d.Get("name").(string)), + ServiceNamespace: aws.String(d.Get("service_namespace").(string)), + ResourceId: aws.String(d.Get("resource_id").(string)), + } + if v, ok := d.GetOk("scalable_dimension"); ok { + input.ScalableDimension = aws.String(v.(string)) + } + _, err := conn.DeleteScheduledAction(input) + if err != nil { + if isAWSErr(err, applicationautoscaling.ErrCodeObjectNotFoundException, "") { + d.SetId("") + return nil + } + return err + } + d.SetId("") + return nil +} diff --git a/aws/resource_aws_appautoscaling_scheduled_action_test.go b/aws/resource_aws_appautoscaling_scheduled_action_test.go new file mode 100644 index 00000000000..bff9dfea906 --- /dev/null +++ b/aws/resource_aws_appautoscaling_scheduled_action_test.go @@ -0,0 +1,679 @@ +package aws + +import ( + "fmt" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/applicationautoscaling" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAwsAppautoscalingScheduledAction_dynamo(t *testing.T) { + ts := time.Now().AddDate(0, 0, 1).Format("2006-01-02T15:04:05") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppautoscalingScheduledActionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppautoscalingScheduledActionConfig_DynamoDB(acctest.RandString(5), ts), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppautoscalingScheduledActionExists("aws_appautoscaling_scheduled_action.hoge"), + ), + }, + }, + }) +} + +func TestAccAwsAppautoscalingScheduledAction_ECS(t *testing.T) { + ts := time.Now().AddDate(0, 0, 1).Format("2006-01-02T15:04:05") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppautoscalingScheduledActionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppautoscalingScheduledActionConfig_ECS(acctest.RandString(5), ts), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppautoscalingScheduledActionExists("aws_appautoscaling_scheduled_action.hoge"), + ), + }, + }, + }) +} + +func TestAccAwsAppautoscalingScheduledAction_EMR(t *testing.T) { + ts := time.Now().AddDate(0, 0, 1).Format("2006-01-02T15:04:05") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppautoscalingScheduledActionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppautoscalingScheduledActionConfig_EMR(acctest.RandString(5), ts), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppautoscalingScheduledActionExists("aws_appautoscaling_scheduled_action.hoge"), + ), + }, + }, + }) +} + +func TestAccAwsAppautoscalingScheduledAction_SpotFleet(t *testing.T) { + ts := time.Now().AddDate(0, 0, 1).Format("2006-01-02T15:04:05") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppautoscalingScheduledActionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppautoscalingScheduledActionConfig_SpotFleet(acctest.RandString(5), ts), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppautoscalingScheduledActionExists("aws_appautoscaling_scheduled_action.hoge"), + ), + }, + }, + }) +} + +func testAccCheckAwsAppautoscalingScheduledActionDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).appautoscalingconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_appautoscaling_scheduled_action" { + continue + } + + input := &applicationautoscaling.DescribeScheduledActionsInput{ + ScheduledActionNames: []*string{aws.String(rs.Primary.Attributes["name"])}, + ServiceNamespace: aws.String(rs.Primary.Attributes["service_namespace"]), + } + resp, err := conn.DescribeScheduledActions(input) + if err != nil { + return err + } + if len(resp.ScheduledActions) > 0 { + return fmt.Errorf("Appautoscaling Scheduled Action (%s) not deleted", rs.Primary.Attributes["name"]) + } + } + return nil +} + +func testAccCheckAwsAppautoscalingScheduledActionExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + return nil + } +} + +func testAccAppautoscalingScheduledActionConfig_DynamoDB(rName, ts string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "hoge" { + name = "tf-ddb-%s" + read_capacity = 5 + write_capacity = 5 + hash_key = "UserID" + + attribute { + name = "UserID" + type = "S" + } +} + +resource "aws_iam_role" "hoge" { + assume_role_policy = <> aws_appautoscaling_policy - + > + aws_appautoscaling_scheduled_action + > aws_appautoscaling_target diff --git a/website/docs/r/appautoscaling_scheduled_action.html.markdown b/website/docs/r/appautoscaling_scheduled_action.html.markdown new file mode 100644 index 00000000000..4b300af9b5b --- /dev/null +++ b/website/docs/r/appautoscaling_scheduled_action.html.markdown @@ -0,0 +1,89 @@ +--- +layout: "aws" +page_title: "AWS: aws_appautoscaling_scheduled_action" +sidebar_current: "docs-aws-resource-appautoscaling-scheduled-action" +description: |- + Provides an Application AutoScaling ScheduledAction resource. +--- + +# aws_appautoscaling_scheduled_action + +Provides an Application AutoScaling ScheduledAction resource. + +## Example Usage + +### DynamoDB Table Autoscaling + +```hcl +resource "aws_appautoscaling_target" "dynamodb" { + max_capacity = 100 + min_capacity = 5 + resource_id = "table/tableName" + role_arn = "${data.aws_iam_role.DynamoDBAutoscaleRole.arn}" + scalable_dimension = "dynamodb:table:ReadCapacityUnits" + service_namespace = "dynamodb" +} + +resource "aws_appautoscaling_scheduled_action" "dynamodb" { + name = "dynamodb" + service_namespace = "${aws_appautoscaling_target.dynamodb.service_namespace}" + resource_id = "${aws_appautoscaling_target.dynamodb.resource_id}" + scalable_dimension = "${aws_appautoscaling_target.dynamodb.scalable_dimension}" + schedule = "at(2006-01-02T15:04:05)" + + scalable_target_action { + min_capacity = 1 + max_capacity = 200 + } +} +``` + +### ECS Service Autoscaling + +```hcl +resource "aws_appautoscaling_target" "ecs" { + max_capacity = 4 + min_capacity = 1 + resource_id = "service/clusterName/serviceName" + role_arn = "${var.ecs_iam_role}" + scalable_dimension = "ecs:service:DesiredCount" + service_namespace = "ecs" +} + +resource "aws_appautoscaling_scheduled_action" "ecs" { + name = "ecs" + service_namespace = "${aws_appautoscaling_target.ecs.service_namespace}" + resource_id = "${aws_appautoscaling_target.ecs.resource_id}" + scalable_dimension = "${aws_appautoscaling_target.ecs.scalable_dimension}" + schedule = "at(2006-01-02T15:04:05)" + + scalable_target_action { + min_capacity = 1 + max_capacity = 10 + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the scheduled action. +* `service_namespace` - (Required) The namespace of the AWS service. Documentation can be found in the parameter at: [AWS Application Auto Scaling API Reference](https://docs.aws.amazon.com/ApplicationAutoScaling/latest/APIReference/API_PutScheduledAction.html#ApplicationAutoScaling-PutScheduledAction-request-ServiceNamespace) Example: ecs +* `resource_id` - (Required) The identifier of the resource associated with the scheduled action. Documentation can be found in the parameter at: [AWS Application Auto Scaling API Reference](https://docs.aws.amazon.com/ApplicationAutoScaling/latest/APIReference/API_PutScheduledAction.html#ApplicationAutoScaling-PutScheduledAction-request-ResourceId) +* `scalable_dimension` - (Optional) The scalable dimension. Documentation can be found in the parameter at: [AWS Application Auto Scaling API Reference](https://docs.aws.amazon.com/ApplicationAutoScaling/latest/APIReference/API_PutScheduledAction.html#ApplicationAutoScaling-PutScheduledAction-request-ScalableDimension) Example: ecs:service:DesiredCount +* `scalable_target_action` - (Optional) The new minimum and maximum capacity. You can set both values or just one. See [below](#scalable-target-action-arguments) +* `schedule` - (Optional) The schedule for this action. The following formats are supported: At expressions - at(yyyy-mm-ddThh:mm:ss), Rate expressions - rate(valueunit), Cron expressions - cron(fields). In UTC. Documentation can be found in the parameter at: [AWS Application Auto Scaling API Reference](https://docs.aws.amazon.com/ApplicationAutoScaling/latest/APIReference/API_PutScheduledAction.html#ApplicationAutoScaling-PutScheduledAction-request-Schedule) +* `start_time` - (Optional) The date and time for the scheduled action to start. Specify the following format: 2006-01-02T15:04:05Z +* `end_time` - (Optional) The date and time for the scheduled action to end. Specify the following format: 2006-01-02T15:04:05Z + +### Scalable Target Action Arguments + +* `max_capacity` - (Optional) The maximum capacity. +* `min_capacity` - (Optional) The minimum capacity. + +## Attributes Reference + +The following attributes are exported: + +* `arn` - The Amazon Resource Name (ARN) of the scheduled action.