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