From cc912c39e56cd70e9a3d12eed6f84328ce7c11e8 Mon Sep 17 00:00:00 2001 From: Andreas Skarmutsos Lindh Date: Fri, 15 Jul 2016 13:54:36 +0200 Subject: [PATCH] AWS Application AutoScaling Initial work on two new resource types: * `aws_appautoscaling_target` * `aws_appautoscaling_policy` Fix acc tests --- builtin/providers/aws/config.go | 3 + builtin/providers/aws/provider.go | 2 + .../aws/resource_aws_appautoscaling_policy.go | 331 ++++++++++++++++++ ...resource_aws_appautoscaling_policy_test.go | 148 ++++++++ .../aws/resource_aws_appautoscaling_target.go | 202 +++++++++++ ...resource_aws_appautoscaling_target_test.go | 209 +++++++++++ .../aws/r/appautoscaling_policy.html.markdown | 74 ++++ .../aws/r/appautoscaling_target.html.markdown | 40 +++ 8 files changed, 1009 insertions(+) create mode 100644 builtin/providers/aws/resource_aws_appautoscaling_policy.go create mode 100644 builtin/providers/aws/resource_aws_appautoscaling_policy_test.go create mode 100644 builtin/providers/aws/resource_aws_appautoscaling_target.go create mode 100644 builtin/providers/aws/resource_aws_appautoscaling_target_test.go create mode 100644 website/source/docs/providers/aws/r/appautoscaling_policy.html.markdown create mode 100644 website/source/docs/providers/aws/r/appautoscaling_target.html.markdown diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index 88bf4d0e7..434bdffdd 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -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) diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 689335889..69e264dd9 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -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(), diff --git a/builtin/providers/aws/resource_aws_appautoscaling_policy.go b/builtin/providers/aws/resource_aws_appautoscaling_policy.go new file mode 100644 index 000000000..44461e3a7 --- /dev/null +++ b/builtin/providers/aws/resource_aws_appautoscaling_policy.go @@ -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(¶ms) + 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(¶ms) + 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(¶ms); 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(¶ms) + 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()) +} diff --git a/builtin/providers/aws/resource_aws_appautoscaling_policy_test.go b/builtin/providers/aws/resource_aws_appautoscaling_policy_test.go new file mode 100644 index 000000000..cef3d7d74 --- /dev/null +++ b/builtin/providers/aws/resource_aws_appautoscaling_policy_test.go @@ -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(¶ms) + + 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 = < 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 +} diff --git a/builtin/providers/aws/resource_aws_appautoscaling_target_test.go b/builtin/providers/aws/resource_aws_appautoscaling_target_test.go new file mode 100644 index 000000000..c53262336 --- /dev/null +++ b/builtin/providers/aws/resource_aws_appautoscaling_target_test.go @@ -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 = <