github.com/hobbeswalsh/terraform@v0.3.7-0.20150619183303-ad17cf55a0fa/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 }