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