Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

r/aws_appautoscaling_scheduled_action: Fix bugs #8777

Merged
merged 8 commits into from
Mar 8, 2021
74 changes: 58 additions & 16 deletions aws/resource_aws_appautoscaling_scheduled_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package aws
import (
"fmt"
"log"
"strconv"
"time"

"github.com/aws/aws-sdk-go/aws"
Expand All @@ -17,6 +18,7 @@ func resourceAwsAppautoscalingScheduledAction() *schema.Resource {
return &schema.Resource{
Create: resourceAwsAppautoscalingScheduledActionPut,
Read: resourceAwsAppautoscalingScheduledActionRead,
Update: resourceAwsAppautoscalingScheduledActionPut,
Delete: resourceAwsAppautoscalingScheduledActionDelete,

Schema: map[string]*schema.Schema{
Expand All @@ -43,37 +45,43 @@ func resourceAwsAppautoscalingScheduledAction() *schema.Resource {
"scalable_target_action": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
ForceNew: false,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"max_capacity": {
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
// Use TypeString to allow an "unspecified" value,
// since TypeInt only has allows numbers with 0 as default.
Type: schema.TypeString,
Optional: true,
ForceNew: false,
ValidateFunc: validateTypeStringNullableInteger,
},
"min_capacity": {
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
// Use TypeString to allow an "unspecified" value,
// since TypeInt only has allows numbers with 0 as default.
Type: schema.TypeString,
Optional: true,
ForceNew: false,
ValidateFunc: validateTypeStringNullableInteger,
},
},
},
},
"schedule": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ForceNew: false,
},
"start_time": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ForceNew: false,
},
"end_time": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ForceNew: false,
},
"arn": {
Type: schema.TypeString,
Expand All @@ -100,11 +108,19 @@ func resourceAwsAppautoscalingScheduledActionPut(d *schema.ResourceData, meta in
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 max, ok := raw["max_capacity"]; ok && max.(string) != "" {
maxInt, err := strconv.ParseInt(max.(string), 10, 64)
if err != nil {
return fmt.Errorf("error converting max_capacity %q from string to integer: %s", v.(string), err)
}
sta.MaxCapacity = aws.Int64(maxInt)
}
if min, ok := raw["min_capacity"]; ok {
sta.MinCapacity = aws.Int64(int64(min.(int)))
if min, ok := raw["min_capacity"]; ok && min.(string) != "" {
minInt, err := strconv.ParseInt(min.(string), 10, 64)
if err != nil {
return fmt.Errorf("error converting min_capacity %q from string to integer: %s", v.(string), err)
}
sta.MinCapacity = aws.Int64(minInt)
}
input.ScalableTargetAction = sta
}
Expand Down Expand Up @@ -149,11 +165,13 @@ func resourceAwsAppautoscalingScheduledActionRead(d *schema.ResourceData, meta i
input := &applicationautoscaling.DescribeScheduledActionsInput{
ScheduledActionNames: []*string{aws.String(saName)},
ServiceNamespace: aws.String(d.Get("service_namespace").(string)),
ResourceId: aws.String(d.Get("resource_id").(string)),
}
resp, err := conn.DescribeScheduledActions(input)
if err != nil {
return err
}

if len(resp.ScheduledActions) < 1 {
log.Printf("[WARN] Application Autoscaling Scheduled Action (%s) not found, removing from state", d.Id())
d.SetId("")
Expand All @@ -162,10 +180,18 @@ func resourceAwsAppautoscalingScheduledActionRead(d *schema.ResourceData, meta i
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 {
sa := resp.ScheduledActions[0]
if *sa.ScheduledActionName != saName {
return fmt.Errorf("Scheduled Action (%s) not found", saName)
}
d.Set("arn", resp.ScheduledActions[0].ScheduledActionARN)

if err := d.Set("scalable_target_action", flattenScalableTargetActionConfiguration(sa.ScalableTargetAction)); err != nil {
return fmt.Errorf("error setting scalable_target_action: %s", err)
}
d.Set("schedule", sa.Schedule)
d.Set("start_time", sa.StartTime)
d.Set("end_time", sa.EndTime)
d.Set("arn", sa.ScheduledActionARN)
return nil
}

Expand All @@ -191,3 +217,19 @@ func resourceAwsAppautoscalingScheduledActionDelete(d *schema.ResourceData, meta

return nil
}

func flattenScalableTargetActionConfiguration(cfg *applicationautoscaling.ScalableTargetAction) []interface{} {
if cfg == nil {
return []interface{}{}
}

m := make(map[string]interface{})
if cfg.MaxCapacity != nil {
m["max_capacity"] = strconv.FormatInt(aws.Int64Value(cfg.MaxCapacity), 10)
}
if cfg.MinCapacity != nil {
m["min_capacity"] = strconv.FormatInt(aws.Int64Value(cfg.MinCapacity), 10)
}

return []interface{}{m}
}
26 changes: 18 additions & 8 deletions aws/resource_aws_appautoscaling_scheduled_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,24 @@ import (
)

func TestAccAWSAppautoscalingScheduledAction_dynamo(t *testing.T) {
ts := time.Now().AddDate(0, 0, 1).Format("2006-01-02T15:04:05")
rName := acctest.RandString(5)
schedule1 := fmt.Sprintf("at(%s)", time.Now().AddDate(0, 0, 1).Format("2006-01-02T15:04:05"))
schedule2 := fmt.Sprintf("at(%s)", time.Now().AddDate(0, 0, 2).Format("2006-01-02T15:04:05"))
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAwsAppautoscalingScheduledActionDestroy,
Steps: []resource.TestStep{
{
Config: testAccAppautoscalingScheduledActionConfig_DynamoDB(acctest.RandString(5), ts),
Config: testAccAppautoscalingScheduledActionConfig_DynamoDB(rName, schedule1),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsAppautoscalingScheduledActionExists("aws_appautoscaling_scheduled_action.hoge"),
testAccCheckAwsAppautoscalingScheduledActionExists("aws_appautoscaling_scheduled_action.scale_down"),
),
},
{
Config: testAccAppautoscalingScheduledActionConfig_DynamoDB(rName, schedule2),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("aws_appautoscaling_scheduled_action.scale_down", "schedule", schedule2),
),
},
},
Expand Down Expand Up @@ -113,7 +121,7 @@ func testAccCheckAwsAppautoscalingScheduledActionExists(name string) resource.Te
}
}

func testAccAppautoscalingScheduledActionConfig_DynamoDB(rName, ts string) string {
func testAccAppautoscalingScheduledActionConfig_DynamoDB(rName, schedule string) string {
return fmt.Sprintf(`
resource "aws_dynamodb_table" "hoge" {
name = "tf-ddb-%s"
Expand All @@ -135,19 +143,21 @@ resource "aws_appautoscaling_target" "read" {
max_capacity = 10
}

resource "aws_appautoscaling_scheduled_action" "hoge" {
resource "aws_appautoscaling_scheduled_action" "scale_down" {
name = "tf-appauto-%s"
service_namespace = "${aws_appautoscaling_target.read.service_namespace}"
resource_id = "${aws_appautoscaling_target.read.resource_id}"
scalable_dimension = "${aws_appautoscaling_target.read.scalable_dimension}"
schedule = "at(%s)"
schedule = "%s"

scalable_target_action {
min_capacity = 1
max_capacity = 10
# max_capacity is omitted and shall thus be omitted from the request
# if it is not omitted and instead sent with the value 0, then the following error would be returned:
# "ValidationException: Maximum capacity cannot be less than minimum capacity"
}
}
`, rName, rName, ts)
`, rName, rName, schedule)
}

func testAccAppautoscalingScheduledActionConfig_ECS(rName, ts string) string {
Expand Down
20 changes: 20 additions & 0 deletions aws/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,26 @@ func validateTypeStringNullableFloat(v interface{}, k string) (ws []string, es [
return
}

// validateTypeStringNullableInteger provides custom error messaging for TypeString integers
// Some arguments require an integer value or an unspecified, empty field.
func validateTypeStringNullableInteger(v interface{}, k string) (ws []string, es []error) {
value, ok := v.(string)
if !ok {
es = append(es, fmt.Errorf("expected type of %s to be string", k))
return
}

if value == "" {
return
}

if _, err := strconv.ParseInt(value, 10, 64); err != nil {
es = append(es, fmt.Errorf("%s: cannot parse '%s' as int: %s", k, value, err))
}

return
}

func validateTransferServerID(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)

Expand Down