github.com/econnell/terraform@v0.5.4-0.20150722160631-78eb236786a4/builtin/providers/aws/resource_aws_ecs_service.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"regexp"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/aws/aws-sdk-go/aws"
    12  	"github.com/aws/aws-sdk-go/aws/awsutil"
    13  	"github.com/aws/aws-sdk-go/service/ecs"
    14  	"github.com/aws/aws-sdk-go/service/iam"
    15  	"github.com/hashicorp/terraform/helper/hashcode"
    16  	"github.com/hashicorp/terraform/helper/resource"
    17  	"github.com/hashicorp/terraform/helper/schema"
    18  )
    19  
    20  var taskDefinitionRE = regexp.MustCompile("^([a-zA-Z0-9_-]+):([0-9]+)$")
    21  
    22  func resourceAwsEcsService() *schema.Resource {
    23  	return &schema.Resource{
    24  		Create: resourceAwsEcsServiceCreate,
    25  		Read:   resourceAwsEcsServiceRead,
    26  		Update: resourceAwsEcsServiceUpdate,
    27  		Delete: resourceAwsEcsServiceDelete,
    28  
    29  		Schema: map[string]*schema.Schema{
    30  			"name": &schema.Schema{
    31  				Type:     schema.TypeString,
    32  				Required: true,
    33  				ForceNew: true,
    34  			},
    35  
    36  			"cluster": &schema.Schema{
    37  				Type:     schema.TypeString,
    38  				Optional: true,
    39  				Computed: true,
    40  				ForceNew: true,
    41  			},
    42  
    43  			"task_definition": &schema.Schema{
    44  				Type:     schema.TypeString,
    45  				Required: true,
    46  			},
    47  
    48  			"desired_count": &schema.Schema{
    49  				Type:     schema.TypeInt,
    50  				Optional: true,
    51  			},
    52  
    53  			"iam_role": &schema.Schema{
    54  				Type:     schema.TypeString,
    55  				Optional: true,
    56  			},
    57  
    58  			"load_balancer": &schema.Schema{
    59  				Type:     schema.TypeSet,
    60  				Optional: true,
    61  				Elem: &schema.Resource{
    62  					Schema: map[string]*schema.Schema{
    63  						"elb_name": &schema.Schema{
    64  							Type:     schema.TypeString,
    65  							Required: true,
    66  						},
    67  
    68  						"container_name": &schema.Schema{
    69  							Type:     schema.TypeString,
    70  							Required: true,
    71  						},
    72  
    73  						"container_port": &schema.Schema{
    74  							Type:     schema.TypeInt,
    75  							Required: true,
    76  						},
    77  					},
    78  				},
    79  				Set: resourceAwsEcsLoadBalancerHash,
    80  			},
    81  		},
    82  	}
    83  }
    84  
    85  func resourceAwsEcsServiceCreate(d *schema.ResourceData, meta interface{}) error {
    86  	conn := meta.(*AWSClient).ecsconn
    87  
    88  	input := ecs.CreateServiceInput{
    89  		ServiceName:    aws.String(d.Get("name").(string)),
    90  		TaskDefinition: aws.String(d.Get("task_definition").(string)),
    91  		DesiredCount:   aws.Long(int64(d.Get("desired_count").(int))),
    92  		ClientToken:    aws.String(resource.UniqueId()),
    93  	}
    94  
    95  	if v, ok := d.GetOk("cluster"); ok {
    96  		input.Cluster = aws.String(v.(string))
    97  	}
    98  
    99  	loadBalancers := expandEcsLoadBalancers(d.Get("load_balancer").(*schema.Set).List())
   100  	if len(loadBalancers) > 0 {
   101  		log.Printf("[DEBUG] Adding ECS load balancers: %s", awsutil.StringValue(loadBalancers))
   102  		input.LoadBalancers = loadBalancers
   103  	}
   104  	if v, ok := d.GetOk("iam_role"); ok {
   105  		input.Role = aws.String(v.(string))
   106  	}
   107  
   108  	log.Printf("[DEBUG] Creating ECS service: %s", awsutil.StringValue(input))
   109  	out, err := conn.CreateService(&input)
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	service := *out.Service
   115  
   116  	log.Printf("[DEBUG] ECS service created: %s", *service.ServiceARN)
   117  	d.SetId(*service.ServiceARN)
   118  	d.Set("cluster", *service.ClusterARN)
   119  
   120  	return resourceAwsEcsServiceUpdate(d, meta)
   121  }
   122  
   123  func resourceAwsEcsServiceRead(d *schema.ResourceData, meta interface{}) error {
   124  	conn := meta.(*AWSClient).ecsconn
   125  
   126  	log.Printf("[DEBUG] Reading ECS service %s", d.Id())
   127  	input := ecs.DescribeServicesInput{
   128  		Services: []*string{aws.String(d.Id())},
   129  		Cluster:  aws.String(d.Get("cluster").(string)),
   130  	}
   131  
   132  	out, err := conn.DescribeServices(&input)
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	if len(out.Services) < 1 {
   138  		return nil
   139  	}
   140  
   141  	service := out.Services[0]
   142  	log.Printf("[DEBUG] Received ECS service %s", awsutil.StringValue(service))
   143  
   144  	d.SetId(*service.ServiceARN)
   145  	d.Set("name", *service.ServiceName)
   146  
   147  	// Save task definition in the same format
   148  	if strings.HasPrefix(d.Get("task_definition").(string), "arn:aws:ecs:") {
   149  		d.Set("task_definition", *service.TaskDefinition)
   150  	} else {
   151  		taskDefinition := buildFamilyAndRevisionFromARN(*service.TaskDefinition)
   152  		d.Set("task_definition", taskDefinition)
   153  	}
   154  
   155  	d.Set("desired_count", *service.DesiredCount)
   156  	d.Set("cluster", *service.ClusterARN)
   157  
   158  	if service.RoleARN != nil {
   159  		d.Set("iam_role", *service.RoleARN)
   160  	}
   161  
   162  	if service.LoadBalancers != nil {
   163  		d.Set("load_balancers", flattenEcsLoadBalancers(service.LoadBalancers))
   164  	}
   165  
   166  	return nil
   167  }
   168  
   169  func resourceAwsEcsServiceUpdate(d *schema.ResourceData, meta interface{}) error {
   170  	conn := meta.(*AWSClient).ecsconn
   171  
   172  	log.Printf("[DEBUG] Updating ECS service %s", d.Id())
   173  	input := ecs.UpdateServiceInput{
   174  		Service: aws.String(d.Id()),
   175  		Cluster: aws.String(d.Get("cluster").(string)),
   176  	}
   177  
   178  	if d.HasChange("desired_count") {
   179  		_, n := d.GetChange("desired_count")
   180  		input.DesiredCount = aws.Long(int64(n.(int)))
   181  	}
   182  	if d.HasChange("task_definition") {
   183  		_, n := d.GetChange("task_definition")
   184  		input.TaskDefinition = aws.String(n.(string))
   185  	}
   186  
   187  	out, err := conn.UpdateService(&input)
   188  	if err != nil {
   189  		return err
   190  	}
   191  	service := out.Service
   192  	log.Printf("[DEBUG] Updated ECS service %s", awsutil.StringValue(service))
   193  
   194  	return resourceAwsEcsServiceRead(d, meta)
   195  }
   196  
   197  func resourceAwsEcsServiceDelete(d *schema.ResourceData, meta interface{}) error {
   198  	conn := meta.(*AWSClient).ecsconn
   199  
   200  	// Check if it's not already gone
   201  	resp, err := conn.DescribeServices(&ecs.DescribeServicesInput{
   202  		Services: []*string{aws.String(d.Id())},
   203  		Cluster:  aws.String(d.Get("cluster").(string)),
   204  	})
   205  	if err != nil {
   206  		return err
   207  	}
   208  	log.Printf("[DEBUG] ECS service %s is currently %s", d.Id(), *resp.Services[0].Status)
   209  
   210  	if *resp.Services[0].Status == "INACTIVE" {
   211  		return nil
   212  	}
   213  
   214  	// Drain the ECS service
   215  	if *resp.Services[0].Status != "DRAINING" {
   216  		log.Printf("[DEBUG] Draining ECS service %s", d.Id())
   217  		_, err = conn.UpdateService(&ecs.UpdateServiceInput{
   218  			Service:      aws.String(d.Id()),
   219  			Cluster:      aws.String(d.Get("cluster").(string)),
   220  			DesiredCount: aws.Long(int64(0)),
   221  		})
   222  		if err != nil {
   223  			return err
   224  		}
   225  	}
   226  
   227  	input := ecs.DeleteServiceInput{
   228  		Service: aws.String(d.Id()),
   229  		Cluster: aws.String(d.Get("cluster").(string)),
   230  	}
   231  
   232  	log.Printf("[DEBUG] Deleting ECS service %s", awsutil.StringValue(input))
   233  	out, err := conn.DeleteService(&input)
   234  	if err != nil {
   235  		return err
   236  	}
   237  
   238  	// Wait until it's deleted
   239  	wait := resource.StateChangeConf{
   240  		Pending:    []string{"DRAINING"},
   241  		Target:     "INACTIVE",
   242  		Timeout:    5 * time.Minute,
   243  		MinTimeout: 1 * time.Second,
   244  		Refresh: func() (interface{}, string, error) {
   245  			log.Printf("[DEBUG] Checking if ECS service %s is INACTIVE", d.Id())
   246  			resp, err := conn.DescribeServices(&ecs.DescribeServicesInput{
   247  				Services: []*string{aws.String(d.Id())},
   248  				Cluster:  aws.String(d.Get("cluster").(string)),
   249  			})
   250  			if err != nil {
   251  				return resp, "FAILED", err
   252  			}
   253  
   254  			return resp, *resp.Services[0].Status, nil
   255  		},
   256  	}
   257  
   258  	_, err = wait.WaitForState()
   259  	if err != nil {
   260  		return err
   261  	}
   262  
   263  	log.Printf("[DEBUG] ECS service %s deleted.", *out.Service.ServiceARN)
   264  	return nil
   265  }
   266  
   267  func resourceAwsEcsLoadBalancerHash(v interface{}) int {
   268  	var buf bytes.Buffer
   269  	m := v.(map[string]interface{})
   270  	buf.WriteString(fmt.Sprintf("%s-", m["elb_name"].(string)))
   271  	buf.WriteString(fmt.Sprintf("%s-", m["container_name"].(string)))
   272  	buf.WriteString(fmt.Sprintf("%d-", m["container_port"].(int)))
   273  
   274  	return hashcode.String(buf.String())
   275  }
   276  
   277  func buildFamilyAndRevisionFromARN(arn string) string {
   278  	return strings.Split(arn, "/")[1]
   279  }
   280  
   281  func buildTaskDefinitionARN(taskDefinition string, meta interface{}) (string, error) {
   282  	// If it's already an ARN, just return it
   283  	if strings.HasPrefix(taskDefinition, "arn:aws:ecs:") {
   284  		return taskDefinition, nil
   285  	}
   286  
   287  	// Parse out family & revision
   288  	family, revision, err := parseTaskDefinition(taskDefinition)
   289  	if err != nil {
   290  		return "", err
   291  	}
   292  
   293  	iamconn := meta.(*AWSClient).iamconn
   294  	region := meta.(*AWSClient).region
   295  
   296  	// An zero value GetUserInput{} defers to the currently logged in user
   297  	resp, err := iamconn.GetUser(&iam.GetUserInput{})
   298  	if err != nil {
   299  		return "", fmt.Errorf("GetUser ERROR: %#v", err)
   300  	}
   301  
   302  	// arn:aws:iam::0123456789:user/username
   303  	userARN := *resp.User.ARN
   304  	accountID := strings.Split(userARN, ":")[4]
   305  
   306  	// arn:aws:ecs:us-west-2:01234567890:task-definition/mongodb:3
   307  	arn := fmt.Sprintf("arn:aws:ecs:%s:%s:task-definition/%s:%s",
   308  		region, accountID, family, revision)
   309  	log.Printf("[DEBUG] Built task definition ARN: %s", arn)
   310  	return arn, nil
   311  }
   312  
   313  func parseTaskDefinition(taskDefinition string) (string, string, error) {
   314  	matches := taskDefinitionRE.FindAllStringSubmatch(taskDefinition, 2)
   315  
   316  	if len(matches) == 0 || len(matches[0]) != 3 {
   317  		return "", "", fmt.Errorf(
   318  			"Invalid task definition format, family:rev or ARN expected (%#v)",
   319  			taskDefinition)
   320  	}
   321  
   322  	return matches[0][1], matches[0][2], nil
   323  }