AWS Application AutoScaling

Initial work on two new resource types:
* `aws_appautoscaling_target`
* `aws_appautoscaling_policy`

Fix acc tests
This commit is contained in:
Andreas Skarmutsos Lindh 2016-07-15 13:54:36 +02:00 committed by stack72
parent a61687add1
commit cc912c39e5
No known key found for this signature in database
GPG Key ID: 8619A619B085CB16
8 changed files with 1009 additions and 0 deletions

View File

@ -18,6 +18,7 @@ import (
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/apigateway"
"github.com/aws/aws-sdk-go/service/applicationautoscaling"
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/aws/aws-sdk-go/service/cloudfront"
@ -94,6 +95,7 @@ type AWSClient struct {
emrconn *emr.EMR
esconn *elasticsearch.ElasticsearchService
apigateway *apigateway.APIGateway
appautoscalingconn *applicationautoscaling.ApplicationAutoScaling
autoscalingconn *autoscaling.AutoScaling
s3conn *s3.S3
sesConn *ses.SES
@ -213,6 +215,7 @@ func (c *Config) Client() (interface{}, error) {
}
client.apigateway = apigateway.New(sess)
client.appautoscalingconn = applicationautoscaling.New(sess)
client.autoscalingconn = autoscaling.New(sess)
client.cfconn = cloudformation.New(sess)
client.cloudfrontconn = cloudfront.New(sess)

View File

@ -135,6 +135,8 @@ func Provider() terraform.ResourceProvider {
"aws_api_gateway_resource": resourceAwsApiGatewayResource(),
"aws_api_gateway_rest_api": resourceAwsApiGatewayRestApi(),
"aws_app_cookie_stickiness_policy": resourceAwsAppCookieStickinessPolicy(),
"aws_appautoscaling_target": resourceAwsAppautoscalingTarget(),
"aws_appautoscaling_policy": resourceAwsAppautoscalingPolicy(),
"aws_autoscaling_group": resourceAwsAutoscalingGroup(),
"aws_autoscaling_notification": resourceAwsAutoscalingNotification(),
"aws_autoscaling_policy": resourceAwsAutoscalingPolicy(),

View File

@ -0,0 +1,331 @@
package aws
import (
"bytes"
"fmt"
"log"
"strconv"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/applicationautoscaling"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsAppautoscalingPolicy() *schema.Resource {
return &schema.Resource{
Create: resourceAwsAppautoscalingPolicyCreate,
Read: resourceAwsAppautoscalingPolicyRead,
Update: resourceAwsAppautoscalingPolicyUpdate,
Delete: resourceAwsAppautoscalingPolicyDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
// https://github.com/boto/botocore/blob/9f322b1/botocore/data/autoscaling/2011-01-01/service-2.json#L1862-L1873
value := v.(string)
if len(value) > 255 {
errors = append(errors, fmt.Errorf("q cannot be longer than 255 characters", k))
}
return
},
},
"arn": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"policy_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "StepScaling",
},
"resource_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"scalable_dimension": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "ecs:service:DesiredCount",
ForceNew: true,
},
"service_namespace": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "ecs",
ForceNew: true,
},
"adjustment_type": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"cooldown": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"metric_aggregation_type": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"min_adjustment_magnitude": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},
"alarms": &schema.Schema{
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"step_adjustment": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"metric_interval_lower_bound": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"metric_interval_upper_bound": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"scaling_adjustment": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
},
},
Set: resourceAwsAppautoscalingAdjustmentHash,
},
},
}
}
func resourceAwsAppautoscalingPolicyCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).appautoscalingconn
params, err := getAwsAppautoscalingPutScalingPolicyInput(d)
if err != nil {
return err
}
log.Printf("[DEBUG] ApplicationAutoScaling PutScalingPolicy: %#v", params)
resp, err := conn.PutScalingPolicy(&params)
if err != nil {
return fmt.Errorf("Error putting scaling policy: %s", err)
}
d.Set("arn", resp.PolicyARN)
d.SetId(d.Get("name").(string))
log.Printf("[INFO] ApplicationAutoScaling scaling PolicyARN: %s", d.Get("arn").(string))
return resourceAwsAppautoscalingPolicyRead(d, meta)
}
func resourceAwsAppautoscalingPolicyRead(d *schema.ResourceData, meta interface{}) error {
p, err := getAwsAppautoscalingPolicy(d, meta)
if err != nil {
return err
}
if p == nil {
d.SetId("")
return nil
}
log.Printf("[DEBUG] Read ApplicationAutoScaling policy: %s, SP: %s, Obj: %s", d.Get("name"), d.Get("name"), p)
d.Set("arn", p.PolicyARN)
d.Set("name", p.PolicyName)
d.Set("policy_type", p.PolicyType)
d.Set("resource_id", p.ResourceId)
d.Set("scalable_dimension", p.ScalableDimension)
d.Set("service_namespace", p.ServiceNamespace)
d.Set("alarms", p.Alarms)
d.Set("step_scaling_policy_configuration", p.StepScalingPolicyConfiguration)
return nil
}
func resourceAwsAppautoscalingPolicyUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).appautoscalingconn
params, inputErr := getAwsAppautoscalingPutScalingPolicyInput(d)
if inputErr != nil {
return inputErr
}
log.Printf("[DEBUG] Application Autoscaling Update Scaling Policy: %#v", params)
_, err := conn.PutScalingPolicy(&params)
if err != nil {
return err
}
return resourceAwsAppautoscalingPolicyRead(d, meta)
}
func resourceAwsAppautoscalingPolicyDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).appautoscalingconn
p, err := getAwsAppautoscalingPolicy(d, meta)
if err != nil {
return fmt.Errorf("Error getting policy: %s", err)
}
if p == nil {
return nil
}
params := applicationautoscaling.DeleteScalingPolicyInput{
PolicyName: aws.String(d.Get("name").(string)),
ResourceId: aws.String(d.Get("resource_id").(string)),
ScalableDimension: aws.String(d.Get("scalable_dimension").(string)),
ServiceNamespace: aws.String(d.Get("service_namespace").(string)),
}
log.Printf("[DEBUG] Deleting Application AutoScaling Policy opts: %#v", params)
if _, err := conn.DeleteScalingPolicy(&params); err != nil {
return fmt.Errorf("Application AutoScaling Policy: %s", err)
}
d.SetId("")
return nil
}
// Takes the result of flatmap.Expand for an array of step adjustments and
// returns a []*applicationautoscaling.StepAdjustment.
func expandAppautoscalingStepAdjustments(configured []interface{}) ([]*applicationautoscaling.StepAdjustment, error) {
var adjustments []*applicationautoscaling.StepAdjustment
// Loop over our configured step adjustments and create an array
// of aws-sdk-go compatible objects. We're forced to convert strings
// to floats here because there's no way to detect whether or not
// an uninitialized, optional schema element is "0.0" deliberately.
// With strings, we can test for "", which is definitely an empty
// struct value.
for _, raw := range configured {
data := raw.(map[string]interface{})
a := &applicationautoscaling.StepAdjustment{
ScalingAdjustment: aws.Int64(int64(data["scaling_adjustment"].(int))),
}
if data["metric_interval_lower_bound"] != "" {
bound := data["metric_interval_lower_bound"]
switch bound := bound.(type) {
case string:
f, err := strconv.ParseFloat(bound, 64)
if err != nil {
return nil, fmt.Errorf(
"metric_interval_lower_bound must be a float value represented as a string")
}
a.MetricIntervalLowerBound = aws.Float64(f)
default:
return nil, fmt.Errorf(
"metric_interval_lower_bound isn't a string. This is a bug. Please file an issue.")
}
}
if data["metric_interval_upper_bound"] != "" {
bound := data["metric_interval_upper_bound"]
switch bound := bound.(type) {
case string:
f, err := strconv.ParseFloat(bound, 64)
if err != nil {
return nil, fmt.Errorf(
"metric_interval_upper_bound must be a float value represented as a string")
}
a.MetricIntervalUpperBound = aws.Float64(f)
default:
return nil, fmt.Errorf(
"metric_interval_upper_bound isn't a string. This is a bug. Please file an issue.")
}
}
adjustments = append(adjustments, a)
}
return adjustments, nil
}
func getAwsAppautoscalingPutScalingPolicyInput(d *schema.ResourceData) (applicationautoscaling.PutScalingPolicyInput, error) {
var params = applicationautoscaling.PutScalingPolicyInput{
PolicyName: aws.String(d.Get("name").(string)),
ResourceId: aws.String(d.Get("resource_id").(string)),
}
if v, ok := d.GetOk("policy_type"); ok {
params.PolicyType = aws.String(v.(string))
}
if v, ok := d.GetOk("service_namespace"); ok {
params.ServiceNamespace = aws.String(v.(string))
}
if v, ok := d.GetOk("policy_type"); ok {
params.PolicyType = aws.String(v.(string))
}
if v, ok := d.GetOk("scalable_dimension"); ok {
params.ScalableDimension = aws.String(v.(string))
}
var adjustmentSteps []*applicationautoscaling.StepAdjustment
if v, ok := d.GetOk("step_adjustment"); ok {
steps, err := expandAppautoscalingStepAdjustments(v.(*schema.Set).List())
if err != nil {
return params, fmt.Errorf("metric_interval_lower_bound and metric_interval_upper_bound must be strings!")
}
adjustmentSteps = steps
}
// build StepScalingPolicyConfiguration
params.StepScalingPolicyConfiguration = &applicationautoscaling.StepScalingPolicyConfiguration{
AdjustmentType: aws.String(d.Get("adjustment_type").(string)),
Cooldown: aws.Int64(int64(d.Get("cooldown").(int))),
MetricAggregationType: aws.String(d.Get("metric_aggregation_type").(string)),
StepAdjustments: adjustmentSteps,
}
if v, ok := d.GetOk("min_adjustment_magnitude"); ok {
params.StepScalingPolicyConfiguration.MinAdjustmentMagnitude = aws.Int64(int64(v.(int)))
}
return params, nil
}
func getAwsAppautoscalingPolicy(d *schema.ResourceData, meta interface{}) (*applicationautoscaling.ScalingPolicy, error) {
conn := meta.(*AWSClient).appautoscalingconn
params := applicationautoscaling.DescribeScalingPoliciesInput{
PolicyNames: []*string{aws.String(d.Get("name").(string))},
ServiceNamespace: aws.String(d.Get("service_namespace").(string)),
}
log.Printf("[DEBUG] Application AutoScaling Policy Describe Params: %#v", params)
resp, err := conn.DescribeScalingPolicies(&params)
if err != nil {
return nil, fmt.Errorf("Error retrieving scaling policies: %s", err)
}
// find scaling policy
name := d.Get("name")
for idx, sp := range resp.ScalingPolicies {
if *sp.PolicyName == name {
return resp.ScalingPolicies[idx], nil
}
}
// policy not found
return nil, nil
}
func resourceAwsAppautoscalingAdjustmentHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
if v, ok := m["metric_interval_lower_bound"]; ok {
buf.WriteString(fmt.Sprintf("%f-", v))
}
if v, ok := m["metric_interval_upper_bound"]; ok {
buf.WriteString(fmt.Sprintf("%f-", v))
}
buf.WriteString(fmt.Sprintf("%d-", m["scaling_adjustment"].(int)))
return hashcode.String(buf.String())
}

View File

@ -0,0 +1,148 @@
package aws
import (
"fmt"
"os"
"testing"
"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 TestAccAWSAppautoscalingPolicy_basic(t *testing.T) {
var policy applicationautoscaling.ScalingPolicy
var awsAccountId = os.Getenv("AWS_ACCOUNT_ID")
randClusterName := fmt.Sprintf("cluster-%s", acctest.RandString(10))
// randResourceId := fmt.Sprintf("service/%s/%s", randClusterName, acctest.RandString(10))
randPolicyName := fmt.Sprintf("terraform-test-foobar-%s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSAppautoscalingPolicyDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSAppautoscalingPolicyConfig(randClusterName, randPolicyName, awsAccountId),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSAppautoscalingPolicyExists("aws_appautoscaling_policy.foobar_simple", &policy),
resource.TestCheckResourceAttr("aws_appautoscaling_policy.foobar_simple", "adjustment_type", "ChangeInCapacity"),
resource.TestCheckResourceAttr("aws_appautoscaling_policy.foobar_simple", "policy_type", "StepScaling"),
resource.TestCheckResourceAttr("aws_appautoscaling_policy.foobar_simple", "cooldown", "60"),
resource.TestCheckResourceAttr("aws_appautoscaling_policy.foobar_simple", "name", randPolicyName),
resource.TestCheckResourceAttr("aws_appautoscaling_policy.foobar_simple", "resource_id", fmt.Sprintf("service/%s/foobar", randClusterName)),
resource.TestCheckResourceAttr("aws_appautoscaling_policy.foobar_simple", "service_namespace", "ecs"),
resource.TestCheckResourceAttr("aws_appautoscaling_policy.foobar_simple", "scalable_dimension", "ecs:service:DesiredCount"),
),
},
},
})
}
func testAccCheckAWSAppautoscalingPolicyExists(n string, policy *applicationautoscaling.ScalingPolicy) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
conn := testAccProvider.Meta().(*AWSClient).appautoscalingconn
params := &applicationautoscaling.DescribeScalingPoliciesInput{
ServiceNamespace: aws.String(rs.Primary.Attributes["service_namespace"]),
PolicyNames: []*string{aws.String(rs.Primary.ID)},
}
resp, err := conn.DescribeScalingPolicies(params)
if err != nil {
return err
}
if len(resp.ScalingPolicies) == 0 {
return fmt.Errorf("ScalingPolicy %s not found", rs.Primary.ID)
}
return nil
}
}
func testAccCheckAWSAppautoscalingPolicyDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).appautoscalingconn
for _, rs := range s.RootModule().Resources {
params := applicationautoscaling.DescribeScalingPoliciesInput{
ServiceNamespace: aws.String(rs.Primary.Attributes["service_namespace"]),
PolicyNames: []*string{aws.String(rs.Primary.ID)},
}
resp, err := conn.DescribeScalingPolicies(&params)
if err == nil {
if len(resp.ScalingPolicies) != 0 &&
*resp.ScalingPolicies[0].PolicyName == rs.Primary.ID {
return fmt.Errorf("Application autoscaling policy still exists: %s", rs.Primary.ID)
}
}
}
return nil
}
func testAccAWSAppautoscalingPolicyConfig(
randClusterName string,
randPolicyName string,
awsAccountId string) string {
return fmt.Sprintf(`
resource "aws_ecs_cluster" "foo" {
name = "%s"
}
resource "aws_ecs_task_definition" "task" {
family = "foobar"
container_definitions = <<EOF
[
{
"name": "busybox",
"image": "busybox:latest",
"cpu": 10,
"memory": 128,
"essential": true
}
]
EOF
}
resource "aws_ecs_service" "service" {
name = "foobar"
cluster = "${aws_ecs_cluster.foo.id}"
task_definition = "${aws_ecs_task_definition.task.arn}"
desired_count = 1
deployment_maximum_percent = 200
deployment_minimum_healthy_percent = 50
}
resource "aws_appautoscaling_target" "tgt" {
service_namespace = "ecs"
resource_id = "service/${aws_ecs_cluster.foo.name}/${aws_ecs_service.service.name}"
scalable_dimension = "ecs:service:DesiredCount"
role_arn = "arn:aws:iam::%s:role/ecsAutoscaleRole"
min_capacity = 1
max_capacity = 4
}
resource "aws_appautoscaling_policy" "foobar_simple" {
name = "%s"
service_namespace = "ecs"
resource_id = "service/${aws_ecs_cluster.foo.name}/${aws_ecs_service.service.name}"
scalable_dimension = "ecs:service:DesiredCount"
adjustment_type = "ChangeInCapacity"
cooldown = 60
metric_aggregation_type = "Average"
step_adjustment {
metric_interval_lower_bound = 0
scaling_adjustment = 1
}
depends_on = ["aws_appautoscaling_target.tgt"]
}
`, randClusterName, awsAccountId, randPolicyName)
}

View File

@ -0,0 +1,202 @@
package aws
import (
"fmt"
"log"
"time"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/applicationautoscaling"
)
func resourceAwsAppautoscalingTarget() *schema.Resource {
return &schema.Resource{
Create: resourceAwsAppautoscalingTargetCreate,
Read: resourceAwsAppautoscalingTargetRead,
Delete: resourceAwsAppautoscalingTargetDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
// https://github.com/boto/botocore/blob/9f322b1/botocore/data/autoscaling/2011-01-01/service-2.json#L1862-L1873
value := v.(string)
if len(value) > 255 {
errors = append(errors, fmt.Errorf(
"%q cannot be longer than 255 characters", k))
}
return
},
},
"arn": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"max_capacity": &schema.Schema{
Type: schema.TypeInt,
Required: true,
ForceNew: true,
},
"min_capacity": &schema.Schema{
Type: schema.TypeInt,
Required: true,
ForceNew: true,
},
"resource_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"role_arn": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"scalable_dimension": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "ecs:service:DesiredCount",
ForceNew: true,
},
"service_namespace": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "ecs",
ForceNew: true,
},
},
}
}
func resourceAwsAppautoscalingTargetCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).appautoscalingconn
var targetOpts applicationautoscaling.RegisterScalableTargetInput
targetOpts.MaxCapacity = aws.Int64(int64(d.Get("max_capacity").(int)))
targetOpts.MinCapacity = aws.Int64(int64(d.Get("min_capacity").(int)))
targetOpts.ResourceId = aws.String(d.Get("resource_id").(string))
targetOpts.RoleARN = aws.String(d.Get("role_arn").(string))
targetOpts.ScalableDimension = aws.String(d.Get("scalable_dimension").(string))
targetOpts.ServiceNamespace = aws.String(d.Get("service_namespace").(string))
log.Printf("[DEBUG] Application autoscaling target create configuration %#v", targetOpts)
_, err := conn.RegisterScalableTarget(&targetOpts)
if err != nil {
return fmt.Errorf("Error creating application autoscaling target: %s", err)
}
d.SetId(d.Get("resource_id").(string))
log.Printf("[INFO] Application AutoScaling Target ID: %s", d.Id())
return resourceAwsAppautoscalingTargetRead(d, meta)
}
func resourceAwsAppautoscalingTargetRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).appautoscalingconn
t, err := getAwsAppautoscalingTarget(d, conn)
if err != nil {
return err
}
if t == nil {
log.Printf("[INFO] Application AutoScaling Target %q not found", d.Id())
d.SetId("")
return nil
}
d.Set("max_capacity", t.MaxCapacity)
d.Set("min_capacity", t.MinCapacity)
d.Set("resource_id", t.ResourceId)
d.Set("role_arn", t.RoleARN)
d.Set("scalable_dimension", t.ScalableDimension)
d.Set("service_namespace", t.ServiceNamespace)
return nil
}
// Updating Target is not supported
// func getAwsAppautoscalingTargetUpdate(d *schema.ResourceData, meta interface{}) error {
// conn := meta.(*AWSClient).appautoscalingconn
// }
func resourceAwsAppautoscalingTargetDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).appautoscalingconn
t, err := getAwsAppautoscalingTarget(d, conn)
if err != nil {
return err
}
if t == nil {
log.Printf("[INFO] Application AutoScaling Target %q not found", d.Id())
d.SetId("")
return nil
}
log.Printf("[DEBUG] Application AutoScaling Target destroy: %#v", d.Id())
deleteOpts := applicationautoscaling.DeregisterScalableTargetInput{
ResourceId: aws.String(d.Get("resource_id").(string)),
ServiceNamespace: aws.String(d.Get("service_namespace").(string)),
ScalableDimension: aws.String(d.Get("scalable_dimension").(string)),
}
err = resource.Retry(5*time.Minute, func() *resource.RetryError {
if _, err := conn.DeregisterScalableTarget(&deleteOpts); err != nil {
if awserr, ok := err.(awserr.Error); ok {
// @TODO: We should do stuff here depending on the actual error returned
return resource.RetryableError(awserr)
}
// Non recognized error, no retry.
return resource.NonRetryableError(err)
}
// Successful delete
return nil
})
if err != nil {
return err
}
return resource.Retry(5*time.Minute, func() *resource.RetryError {
if t, _ = getAwsAppautoscalingTarget(d, conn); t != nil {
return resource.RetryableError(
fmt.Errorf("Application AutoScaling Target still exists"))
}
return nil
})
}
func getAwsAppautoscalingTarget(
d *schema.ResourceData,
conn *applicationautoscaling.ApplicationAutoScaling) (*applicationautoscaling.ScalableTarget, error) {
tgtName := d.Id()
describeOpts := applicationautoscaling.DescribeScalableTargetsInput{
ResourceIds: []*string{aws.String(tgtName)},
ServiceNamespace: aws.String(d.Get("service_namespace").(string)),
}
log.Printf("[DEBUG] Application AutoScaling Target describe configuration: %#v", describeOpts)
describeTargets, err := conn.DescribeScalableTargets(&describeOpts)
if err != nil {
// @TODO: We should probably send something else back if we're trying to access an unknown Resource ID
// targetserr, ok := err.(awserr.Error)
// if ok && targetserr.Code() == ""
return nil, fmt.Errorf("Error retrieving Application AutoScaling Target: %s", err)
}
for idx, tgt := range describeTargets.ScalableTargets {
if *tgt.ResourceId == tgtName {
return describeTargets.ScalableTargets[idx], nil
}
}
return nil, nil
}

View File

@ -0,0 +1,209 @@
package aws
import (
"fmt"
"os"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"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 TestAccAWSAppautoScalingTarget_basic(t *testing.T) {
var target applicationautoscaling.ScalableTarget
var awsAccountId = os.Getenv("AWS_ACCOUNT_ID")
randClusterName := fmt.Sprintf("cluster-%s", acctest.RandString(10))
randResourceId := fmt.Sprintf("service/%s/%s", randClusterName, acctest.RandString(10))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_appautoscaling_target.bar",
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSAppautoscalingTargetDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSAppautoscalingTargetConfig(randClusterName, randResourceId, awsAccountId),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSAppautoscalingTargetExists("aws_appautoscaling_target.bar", &target),
testAccCheckAWSAppautoscalingTargetAttributes(&target, randResourceId),
resource.TestCheckResourceAttr("aws_appautoscaling_target.bar", "service_namespace", "ecs"),
resource.TestCheckResourceAttr("aws_appautoscaling_target.bar", "resource_id", fmt.Sprintf("service/%s/foobar", randClusterName)),
resource.TestCheckResourceAttr("aws_appautoscaling_target.bar", "scalable_dimension", "ecs:service:DesiredCount"),
resource.TestCheckResourceAttr("aws_appautoscaling_target.bar", "min_capacity", "1"),
resource.TestCheckResourceAttr("aws_appautoscaling_target.bar", "max_capacity", "3"),
),
},
resource.TestStep{
Config: testAccAWSAppautoscalingTargetConfigUpdate(randClusterName, randResourceId, awsAccountId),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSAppautoscalingTargetExists("aws_appautoscaling_target.bar", &target),
resource.TestCheckResourceAttr("aws_appautoscaling_target.bar", "min_capacity", "3"),
resource.TestCheckResourceAttr("aws_appautoscaling_target.bar", "max_capacity", "6"),
),
},
},
})
}
func testAccCheckAWSAppautoscalingTargetDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).appautoscalingconn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_appautoscaling_target" {
continue
}
// Try to find the target
describeTargets, err := conn.DescribeScalableTargets(
&applicationautoscaling.DescribeScalableTargetsInput{
ResourceIds: []*string{aws.String(rs.Primary.ID)},
},
)
if err == nil {
if len(describeTargets.ScalableTargets) != 0 &&
*describeTargets.ScalableTargets[0].ResourceId == rs.Primary.ID {
return fmt.Errorf("Application AutoScaling Target still exists")
}
}
// Verify error
e, ok := err.(awserr.Error)
if !ok {
return err
}
if e.Code() != "" {
return e
}
}
return nil
}
func testAccCheckAWSAppautoscalingTargetExists(n string, target *applicationautoscaling.ScalableTarget) 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 Application AutoScaling Target ID is set")
}
conn := testAccProvider.Meta().(*AWSClient).appautoscalingconn
describeTargets, err := conn.DescribeScalableTargets(
&applicationautoscaling.DescribeScalableTargetsInput{
ResourceIds: []*string{aws.String(rs.Primary.ID)},
},
)
if err != nil {
return err
}
if len(describeTargets.ScalableTargets) != 1 ||
*describeTargets.ScalableTargets[0].ResourceId != rs.Primary.ID {
return fmt.Errorf("Application AutoScaling ResourceId not found")
}
*target = *describeTargets.ScalableTargets[0]
return nil
}
}
func testAccCheckAWSAppautoscalingTargetAttributes(target *applicationautoscaling.ScalableTarget, resourceId string) resource.TestCheckFunc {
return nil
}
func testAccAWSAppautoscalingTargetConfig(
randClusterName string,
randResourceId string,
awsAccountId string) string {
return fmt.Sprintf(`
resource "aws_ecs_cluster" "foo" {
name = "%s"
}
resource "aws_ecs_task_definition" "task" {
family = "foobar"
container_definitions = <<EOF
[
{
"name": "busybox",
"image": "busybox:latest",
"cpu": 10,
"memory": 128,
"essential": true
}
]
EOF
}
resource "aws_ecs_service" "service" {
name = "foobar"
cluster = "${aws_ecs_cluster.foo.id}"
task_definition = "${aws_ecs_task_definition.task.arn}"
desired_count = 1
deployment_maximum_percent = 200
deployment_minimum_healthy_percent = 50
}
resource "aws_appautoscaling_target" "bar" {
service_namespace = "ecs"
resource_id = "service/${aws_ecs_cluster.foo.name}/${aws_ecs_service.service.name}"
scalable_dimension = "ecs:service:DesiredCount"
role_arn = "arn:aws:iam::%s:role/ecsAutoscaleRole"
min_capacity = 1
max_capacity = 4
}
`, randClusterName, awsAccountId)
}
func testAccAWSAppautoscalingTargetConfigUpdate(
randClusterName,
randResourceId string,
awsAccountId string) string {
return fmt.Sprintf(`
resource "aws_ecs_cluster" "foo" {
name = "%s"
}
resource "aws_ecs_task_definition" "task" {
family = "foobar"
container_definitions = <<EOF
[
{
"name": "busybox",
"image": "busybox:latest",
"cpu": 10,
"memory": 128,
"essential": true
}
]
EOF
}
resource "aws_ecs_service" "service" {
name = "foobar"
cluster = "${aws_ecs_cluster.foo.id}"
task_definition = "${aws_ecs_task_definition.task.arn}"
desired_count = 1
deployment_maximum_percent = 200
deployment_minimum_healthy_percent = 50
}
resource "aws_appautoscaling_target" "bar" {
service_namespace = "ecs"
resource_id = "service/${aws_ecs_cluster.foo.name}/${aws_ecs_service.service.name}"
scalable_dimension = "ecs:service:DesiredCount"
role_arn = "arn:aws:iam::%s:role/ecsAutoscaleRole"
min_capacity = 2
max_capacity = 8
}
`, randClusterName, awsAccountId)
}

View File

@ -0,0 +1,74 @@
---
layout: "aws"
page_title: "AWS: aws_appautoscaling_policy"
sidebar_current: "docs-aws-resource-appautoscaling-policy"
description: |-
Provides an Application AutoScaling Policy resource.
---
# aws\_appautoscaling\_policy
Provides an Application AutoScaling Policy resource.
## Example Usage
```
resource "aws_appautoscaling_policy" "down" {
name = "scale-down"
service_namespace = "ecs"
resource_id = "service/ecsclustername/servicename"
scalable_dimension = "ecs:service:DesiredCount"
adjustment_type = "ChangeInCapacity"
cooldown = 60
metric_aggregation_type = "Maximum"
step_adjustment {
metric_interval_lower_bound = 0
scaling_adjustment = -1
}
depends_on = ["aws_appautoscaling_target.target"]
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the policy.
* `policy_type` - (Optional) Defaults to "StepScaling" because it is the only option available.
* `resource_id` - (Required) The Resource ID on which you want the Application AutoScaling policy to apply to. For Amazon ECS services, this value is the resource type, followed by the cluster name and service name, such as `service/default/sample-webapp`.
* `scalable_dimension` - (Optional) The scalable dimension of the scalable target that this scaling policy applies to. The scalable dimension contains the service names- pace, resource type, and scaling property, such as `ecs:service:DesiredCount` for the desired task count of an Amazon ECS service. Defaults to `ecs:service:DesiredCount` since that is the only allowed value.
* `service_namespace` - (Optional) The AWS service namespace of the scalable target that this scaling policy applies to. Defaults to `ecs`, because that is currently the only supported option.
* `adjustment_type` - (Required) Specifies whether the adjustment is an absolute number or a percentage of the current capacity. Valid values are `ChangeInCapacity`, `ExactCapacity`, and `PercentChangeInCapacity`.
* `cooldown` - (Required) The amount of time, in seconds, after a scaling activity completes and before the next scaling activity can start.
* `metric_aggregation_type` - (Required) The aggregation type for the policy's metrics. Valid values are "Minimum", "Maximum", and "Average". Without a value, AWS will treat the aggregation type as "Average".
* `step_adjustments` - (Optional) A set of adjustments that manage scaling. These have the following structure:
```
step_adjustment {
scaling_adjustment = -1
metric_interval_lower_bound = 1.0
metric_interval_upper_bound = 2.0
}
step_adjustment {
scaling_adjustment = 1
metric_interval_lower_bound = 2.0
metric_interval_upper_bound = 3.0
}
```
* `scaling_adjustment` - (Required) The number of members by which to
scale, when the adjustment bounds are breached. A positive value scales
up. A negative value scales down.
* `metric_interval_lower_bound` - (Optional) The lower bound for the
difference between the alarm threshold and the CloudWatch metric.
Without a value, AWS will treat this bound as infinity.
* `metric_interval_upper_bound` - (Optional) The upper bound for the
difference between the alarm threshold and the CloudWatch metric.
Without a value, AWS will treat this bound as infinity. The upper bound
must be greater than the lower bound.
## Attribute Reference
* `arn` - The ARN assigned by AWS to the scaling policy.
* `name` - The scaling policy's name.
* `adjustment_type` - The scaling policy's adjustment type.
* `policy_type` - The scaling policy's type.

View File

@ -0,0 +1,40 @@
---
layout: "aws"
page_title: "AWS: aws_appautoscaling_target"
sidebar_current: "docs-aws-resource-appautoscaling-target"
description: |-
Provides an Application AutoScaling ScalableTarget resource.
---
# aws\_appautoscaling\_target
Provides an Application AutoScaling ScalableTarget resource.
## Example Usage
```
resource "aws_appautoscaling_target" "tgt" {
service_namespace = "ecs"
resource_id = "service/clusterName/serviceName"
scalable_dimension = "ecs:service:DesiredCount"
role_arn = "${var.ecs_iam_role}"
min_capacity = 1
max_capacity = 4
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the policy.
* `resource_id` - (Required) The Resource ID on which you want the Application AutoScaling policy to apply to. For Amazon ECS services, this value is the resource type, followed by the cluster name and service name, such as `service/default/sample-webapp`.
* `scalable_dimension` - (Optional) The scalable dimension of the scalable target. The scalable dimension contains the service namespace, resource type, and scaling property, such as `ecs:service:DesiredCount` for the desired task count of an Amazon ECS service. Defaults to `ecs:service:DesiredCount` since that is the only allowed value.
* `service_namespace` - (Optional) The AWS service namespace of the scalable target. Defaults to `ecs`, because that is currently the only supported option.
* `max_capacity` - (Required) The max capacity of the scalable target.
* `min_capacity` - (Required) The min capacity of the scalable target.
* `role_arn` - (Required) The ARN of the IAM role that allows Application AutoScaling to modify your scalable target on your behalf.
## Attribute Reference
* `arn` - The ARN assigned by AWS to the scaling policy.
* `name` - The scaling policy's name.