github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/builtin/providers/aws/resource_aws_emr_cluster.go (about) 1 package aws 2 3 import ( 4 "log" 5 6 "encoding/json" 7 "fmt" 8 "io/ioutil" 9 "net/http" 10 "strings" 11 "time" 12 13 "github.com/aws/aws-sdk-go/aws" 14 "github.com/aws/aws-sdk-go/aws/awserr" 15 "github.com/aws/aws-sdk-go/service/emr" 16 "github.com/hashicorp/terraform/helper/resource" 17 "github.com/hashicorp/terraform/helper/schema" 18 ) 19 20 func resourceAwsEMRCluster() *schema.Resource { 21 return &schema.Resource{ 22 Create: resourceAwsEMRClusterCreate, 23 Read: resourceAwsEMRClusterRead, 24 Update: resourceAwsEMRClusterUpdate, 25 Delete: resourceAwsEMRClusterDelete, 26 Schema: map[string]*schema.Schema{ 27 "name": &schema.Schema{ 28 Type: schema.TypeString, 29 ForceNew: true, 30 Required: true, 31 }, 32 "release_label": &schema.Schema{ 33 Type: schema.TypeString, 34 ForceNew: true, 35 Required: true, 36 }, 37 "master_instance_type": &schema.Schema{ 38 Type: schema.TypeString, 39 Required: true, 40 ForceNew: true, 41 }, 42 "core_instance_type": &schema.Schema{ 43 Type: schema.TypeString, 44 Optional: true, 45 ForceNew: true, 46 Computed: true, 47 }, 48 "core_instance_count": &schema.Schema{ 49 Type: schema.TypeInt, 50 Optional: true, 51 Default: 0, 52 }, 53 "cluster_state": &schema.Schema{ 54 Type: schema.TypeString, 55 Computed: true, 56 }, 57 "log_uri": &schema.Schema{ 58 Type: schema.TypeString, 59 ForceNew: true, 60 Optional: true, 61 }, 62 "master_public_dns": &schema.Schema{ 63 Type: schema.TypeString, 64 Computed: true, 65 }, 66 "applications": &schema.Schema{ 67 Type: schema.TypeSet, 68 Optional: true, 69 ForceNew: true, 70 Elem: &schema.Schema{Type: schema.TypeString}, 71 Set: schema.HashString, 72 }, 73 "ec2_attributes": &schema.Schema{ 74 Type: schema.TypeList, 75 MaxItems: 1, 76 Optional: true, 77 ForceNew: true, 78 Elem: &schema.Resource{ 79 Schema: map[string]*schema.Schema{ 80 "key_name": &schema.Schema{ 81 Type: schema.TypeString, 82 Optional: true, 83 }, 84 "subnet_id": &schema.Schema{ 85 Type: schema.TypeString, 86 Optional: true, 87 }, 88 "additional_master_security_groups": &schema.Schema{ 89 Type: schema.TypeString, 90 Optional: true, 91 }, 92 "additional_slave_security_groups": &schema.Schema{ 93 Type: schema.TypeString, 94 Optional: true, 95 }, 96 "emr_managed_master_security_group": &schema.Schema{ 97 Type: schema.TypeString, 98 Optional: true, 99 }, 100 "emr_managed_slave_security_group": &schema.Schema{ 101 Type: schema.TypeString, 102 Optional: true, 103 }, 104 "instance_profile": &schema.Schema{ 105 Type: schema.TypeString, 106 Required: true, 107 }, 108 }, 109 }, 110 }, 111 "bootstrap_action": &schema.Schema{ 112 Type: schema.TypeSet, 113 Optional: true, 114 ForceNew: true, 115 Elem: &schema.Resource{ 116 Schema: map[string]*schema.Schema{ 117 "name": &schema.Schema{ 118 Type: schema.TypeString, 119 Required: true, 120 }, 121 "path": &schema.Schema{ 122 Type: schema.TypeString, 123 Required: true, 124 }, 125 "args": &schema.Schema{ 126 Type: schema.TypeSet, 127 Optional: true, 128 Elem: &schema.Schema{Type: schema.TypeString}, 129 Set: schema.HashString, 130 }, 131 }, 132 }, 133 }, 134 "tags": tagsSchema(), 135 "configurations": &schema.Schema{ 136 Type: schema.TypeString, 137 ForceNew: true, 138 Optional: true, 139 }, 140 "service_role": &schema.Schema{ 141 Type: schema.TypeString, 142 ForceNew: true, 143 Required: true, 144 }, 145 "visible_to_all_users": &schema.Schema{ 146 Type: schema.TypeBool, 147 Optional: true, 148 ForceNew: true, 149 Default: true, 150 }, 151 }, 152 } 153 } 154 155 func resourceAwsEMRClusterCreate(d *schema.ResourceData, meta interface{}) error { 156 conn := meta.(*AWSClient).emrconn 157 158 log.Printf("[DEBUG] Creating EMR cluster") 159 masterInstanceType := d.Get("master_instance_type").(string) 160 coreInstanceType := masterInstanceType 161 if v, ok := d.GetOk("core_instance_type"); ok { 162 coreInstanceType = v.(string) 163 } 164 coreInstanceCount := d.Get("core_instance_count").(int) 165 166 applications := d.Get("applications").(*schema.Set).List() 167 168 instanceConfig := &emr.JobFlowInstancesConfig{ 169 MasterInstanceType: aws.String(masterInstanceType), 170 SlaveInstanceType: aws.String(coreInstanceType), 171 InstanceCount: aws.Int64(int64(coreInstanceCount)), 172 // Default values that we can open up in the future 173 KeepJobFlowAliveWhenNoSteps: aws.Bool(true), 174 TerminationProtected: aws.Bool(false), 175 } 176 177 var instanceProfile string 178 if a, ok := d.GetOk("ec2_attributes"); ok { 179 ec2Attributes := a.([]interface{}) 180 attributes := ec2Attributes[0].(map[string]interface{}) 181 182 if v, ok := attributes["key_name"]; ok { 183 instanceConfig.Ec2KeyName = aws.String(v.(string)) 184 } 185 if v, ok := attributes["subnet_id"]; ok { 186 instanceConfig.Ec2SubnetId = aws.String(v.(string)) 187 } 188 if v, ok := attributes["subnet_id"]; ok { 189 instanceConfig.Ec2SubnetId = aws.String(v.(string)) 190 } 191 192 if v, ok := attributes["additional_master_security_groups"]; ok { 193 strSlice := strings.Split(v.(string), ",") 194 for i, s := range strSlice { 195 strSlice[i] = strings.TrimSpace(s) 196 } 197 instanceConfig.AdditionalMasterSecurityGroups = aws.StringSlice(strSlice) 198 } 199 200 if v, ok := attributes["additional_slave_security_groups"]; ok { 201 strSlice := strings.Split(v.(string), ",") 202 for i, s := range strSlice { 203 strSlice[i] = strings.TrimSpace(s) 204 } 205 instanceConfig.AdditionalSlaveSecurityGroups = aws.StringSlice(strSlice) 206 } 207 208 if v, ok := attributes["emr_managed_master_security_group"]; ok { 209 instanceConfig.EmrManagedMasterSecurityGroup = aws.String(v.(string)) 210 } 211 if v, ok := attributes["emr_managed_slave_security_group"]; ok { 212 instanceConfig.EmrManagedSlaveSecurityGroup = aws.String(v.(string)) 213 } 214 215 if len(strings.TrimSpace(attributes["instance_profile"].(string))) != 0 { 216 instanceProfile = strings.TrimSpace(attributes["instance_profile"].(string)) 217 } 218 } 219 220 emrApps := expandApplications(applications) 221 222 params := &emr.RunJobFlowInput{ 223 Instances: instanceConfig, 224 Name: aws.String(d.Get("name").(string)), 225 Applications: emrApps, 226 227 ReleaseLabel: aws.String(d.Get("release_label").(string)), 228 ServiceRole: aws.String(d.Get("service_role").(string)), 229 VisibleToAllUsers: aws.Bool(d.Get("visible_to_all_users").(bool)), 230 } 231 232 if v, ok := d.GetOk("log_uri"); ok { 233 params.LogUri = aws.String(v.(string)) 234 } 235 236 if instanceProfile != "" { 237 params.JobFlowRole = aws.String(instanceProfile) 238 } 239 240 if v, ok := d.GetOk("bootstrap_action"); ok { 241 bootstrapActions := v.(*schema.Set).List() 242 params.BootstrapActions = expandBootstrapActions(bootstrapActions) 243 } 244 if v, ok := d.GetOk("tags"); ok { 245 tagsIn := v.(map[string]interface{}) 246 params.Tags = expandTags(tagsIn) 247 } 248 if v, ok := d.GetOk("configurations"); ok { 249 confUrl := v.(string) 250 params.Configurations = expandConfigures(confUrl) 251 } 252 253 log.Printf("[DEBUG] EMR Cluster create options: %s", params) 254 resp, err := conn.RunJobFlow(params) 255 256 if err != nil { 257 log.Printf("[ERROR] %s", err) 258 return err 259 } 260 261 d.SetId(*resp.JobFlowId) 262 263 log.Println( 264 "[INFO] Waiting for EMR Cluster to be available") 265 266 stateConf := &resource.StateChangeConf{ 267 Pending: []string{"STARTING", "BOOTSTRAPPING"}, 268 Target: []string{"WAITING", "RUNNING"}, 269 Refresh: resourceAwsEMRClusterStateRefreshFunc(d, meta), 270 Timeout: 40 * time.Minute, 271 MinTimeout: 10 * time.Second, 272 Delay: 30 * time.Second, // Wait 30 secs before starting 273 } 274 275 _, err = stateConf.WaitForState() 276 if err != nil { 277 return fmt.Errorf("[WARN] Error waiting for EMR Cluster state to be \"WAITING\" or \"RUNNING\": %s", err) 278 } 279 280 return resourceAwsEMRClusterRead(d, meta) 281 } 282 283 func resourceAwsEMRClusterRead(d *schema.ResourceData, meta interface{}) error { 284 emrconn := meta.(*AWSClient).emrconn 285 286 req := &emr.DescribeClusterInput{ 287 ClusterId: aws.String(d.Id()), 288 } 289 290 resp, err := emrconn.DescribeCluster(req) 291 if err != nil { 292 return fmt.Errorf("Error reading EMR cluster: %s", err) 293 } 294 295 if resp.Cluster == nil { 296 log.Printf("[DEBUG] EMR Cluster (%s) not found", d.Id()) 297 d.SetId("") 298 return nil 299 } 300 301 cluster := resp.Cluster 302 303 if cluster.Status != nil { 304 if *cluster.Status.State == "TERMINATED" { 305 log.Printf("[DEBUG] EMR Cluster (%s) was TERMINATED already", d.Id()) 306 d.SetId("") 307 return nil 308 } 309 310 if *cluster.Status.State == "TERMINATED_WITH_ERRORS" { 311 log.Printf("[DEBUG] EMR Cluster (%s) was TERMINATED_WITH_ERRORS already", d.Id()) 312 d.SetId("") 313 return nil 314 } 315 316 d.Set("cluster_state", cluster.Status.State) 317 } 318 319 instanceGroups, err := fetchAllEMRInstanceGroups(meta, d.Id()) 320 if err == nil { 321 coreGroup := findGroup(instanceGroups, "CORE") 322 if coreGroup != nil { 323 d.Set("core_instance_type", coreGroup.InstanceType) 324 } 325 } 326 327 d.Set("name", cluster.Name) 328 d.Set("service_role", cluster.ServiceRole) 329 d.Set("release_label", cluster.ReleaseLabel) 330 d.Set("log_uri", cluster.LogUri) 331 d.Set("master_public_dns", cluster.MasterPublicDnsName) 332 d.Set("visible_to_all_users", cluster.VisibleToAllUsers) 333 d.Set("tags", tagsToMapEMR(cluster.Tags)) 334 335 if err := d.Set("applications", flattenApplications(cluster.Applications)); err != nil { 336 log.Printf("[ERR] Error setting EMR Applications for cluster (%s): %s", d.Id(), err) 337 } 338 339 // Configurations is a JSON document. It's built with an expand method but a 340 // simple string should be returned as JSON 341 if err := d.Set("configurations", cluster.Configurations); err != nil { 342 log.Printf("[ERR] Error setting EMR configurations for cluster (%s): %s", d.Id(), err) 343 } 344 345 if err := d.Set("ec2_attributes", flattenEc2Attributes(cluster.Ec2InstanceAttributes)); err != nil { 346 log.Printf("[ERR] Error setting EMR Ec2 Attributes: %s", err) 347 } 348 return nil 349 } 350 351 func resourceAwsEMRClusterUpdate(d *schema.ResourceData, meta interface{}) error { 352 conn := meta.(*AWSClient).emrconn 353 354 if d.HasChange("core_instance_count") { 355 log.Printf("[DEBUG] Modify EMR cluster") 356 groups, err := fetchAllEMRInstanceGroups(meta, d.Id()) 357 if err != nil { 358 log.Printf("[DEBUG] Error finding all instance groups: %s", err) 359 return err 360 } 361 362 coreInstanceCount := d.Get("core_instance_count").(int) 363 coreGroup := findGroup(groups, "CORE") 364 if coreGroup == nil { 365 return fmt.Errorf("[ERR] Error finding core group") 366 } 367 368 params := &emr.ModifyInstanceGroupsInput{ 369 InstanceGroups: []*emr.InstanceGroupModifyConfig{ 370 { 371 InstanceGroupId: coreGroup.Id, 372 InstanceCount: aws.Int64(int64(coreInstanceCount)), 373 }, 374 }, 375 } 376 _, errModify := conn.ModifyInstanceGroups(params) 377 if errModify != nil { 378 log.Printf("[ERROR] %s", errModify) 379 return errModify 380 } 381 382 log.Printf("[DEBUG] Modify EMR Cluster done...") 383 } 384 385 log.Println( 386 "[INFO] Waiting for EMR Cluster to be available") 387 388 stateConf := &resource.StateChangeConf{ 389 Pending: []string{"STARTING", "BOOTSTRAPPING"}, 390 Target: []string{"WAITING", "RUNNING"}, 391 Refresh: resourceAwsEMRClusterStateRefreshFunc(d, meta), 392 Timeout: 40 * time.Minute, 393 MinTimeout: 10 * time.Second, 394 Delay: 5 * time.Second, 395 } 396 397 _, err := stateConf.WaitForState() 398 if err != nil { 399 return fmt.Errorf("[WARN] Error waiting for EMR Cluster state to be \"WAITING\" or \"RUNNING\" after modification: %s", err) 400 } 401 402 return resourceAwsEMRClusterRead(d, meta) 403 } 404 405 func resourceAwsEMRClusterDelete(d *schema.ResourceData, meta interface{}) error { 406 conn := meta.(*AWSClient).emrconn 407 408 req := &emr.TerminateJobFlowsInput{ 409 JobFlowIds: []*string{ 410 aws.String(d.Id()), 411 }, 412 } 413 414 _, err := conn.TerminateJobFlows(req) 415 if err != nil { 416 log.Printf("[ERROR], %s", err) 417 return err 418 } 419 420 err = resource.Retry(10*time.Minute, func() *resource.RetryError { 421 resp, err := conn.ListInstances(&emr.ListInstancesInput{ 422 ClusterId: aws.String(d.Id()), 423 }) 424 425 if err != nil { 426 return resource.NonRetryableError(err) 427 } 428 429 instanceCount := len(resp.Instances) 430 431 if resp == nil || instanceCount == 0 { 432 log.Printf("[DEBUG] No instances found for EMR Cluster (%s)", d.Id()) 433 return nil 434 } 435 436 // Collect instance status states, wait for all instances to be terminated 437 // before moving on 438 var terminated []string 439 for j, i := range resp.Instances { 440 if i.Status != nil { 441 if *i.Status.State == "TERMINATED" { 442 terminated = append(terminated, *i.Ec2InstanceId) 443 } 444 } else { 445 log.Printf("[DEBUG] Cluster instance (%d : %s) has no status", j, *i.Ec2InstanceId) 446 } 447 } 448 if len(terminated) == instanceCount { 449 log.Printf("[DEBUG] All (%d) EMR Cluster (%s) Instances terminated", instanceCount, d.Id()) 450 return nil 451 } 452 return resource.RetryableError(fmt.Errorf("[DEBUG] EMR Cluster (%s) has (%d) Instances remaining, retrying", d.Id(), len(resp.Instances))) 453 }) 454 455 if err != nil { 456 log.Printf("[ERR] Error waiting for EMR Cluster (%s) Instances to drain", d.Id()) 457 } 458 459 d.SetId("") 460 return nil 461 } 462 463 func expandApplications(apps []interface{}) []*emr.Application { 464 appOut := make([]*emr.Application, 0, len(apps)) 465 466 for _, appName := range expandStringList(apps) { 467 app := &emr.Application{ 468 Name: appName, 469 } 470 appOut = append(appOut, app) 471 } 472 return appOut 473 } 474 475 func flattenApplications(apps []*emr.Application) []interface{} { 476 appOut := make([]interface{}, 0, len(apps)) 477 478 for _, app := range apps { 479 appOut = append(appOut, *app.Name) 480 } 481 return appOut 482 } 483 484 func flattenEc2Attributes(ia *emr.Ec2InstanceAttributes) []map[string]interface{} { 485 attrs := map[string]interface{}{} 486 result := make([]map[string]interface{}, 0) 487 488 if ia.Ec2KeyName != nil { 489 attrs["key_name"] = *ia.Ec2KeyName 490 } 491 if ia.Ec2SubnetId != nil { 492 attrs["subnet_id"] = *ia.Ec2SubnetId 493 } 494 if ia.IamInstanceProfile != nil { 495 attrs["instance_profile"] = *ia.IamInstanceProfile 496 } 497 if ia.EmrManagedMasterSecurityGroup != nil { 498 attrs["emr_managed_master_security_group"] = *ia.EmrManagedMasterSecurityGroup 499 } 500 if ia.EmrManagedSlaveSecurityGroup != nil { 501 attrs["emr_managed_slave_security_group"] = *ia.EmrManagedSlaveSecurityGroup 502 } 503 504 if len(ia.AdditionalMasterSecurityGroups) > 0 { 505 strs := aws.StringValueSlice(ia.AdditionalMasterSecurityGroups) 506 attrs["additional_master_security_groups"] = strings.Join(strs, ",") 507 } 508 if len(ia.AdditionalSlaveSecurityGroups) > 0 { 509 strs := aws.StringValueSlice(ia.AdditionalSlaveSecurityGroups) 510 attrs["additional_slave_security_groups"] = strings.Join(strs, ",") 511 } 512 513 result = append(result, attrs) 514 515 return result 516 } 517 518 func loadGroups(d *schema.ResourceData, meta interface{}) ([]*emr.InstanceGroup, error) { 519 emrconn := meta.(*AWSClient).emrconn 520 reqGrps := &emr.ListInstanceGroupsInput{ 521 ClusterId: aws.String(d.Id()), 522 } 523 524 respGrps, errGrps := emrconn.ListInstanceGroups(reqGrps) 525 if errGrps != nil { 526 return nil, fmt.Errorf("Error reading EMR cluster: %s", errGrps) 527 } 528 return respGrps.InstanceGroups, nil 529 } 530 531 func findGroup(grps []*emr.InstanceGroup, typ string) *emr.InstanceGroup { 532 for _, grp := range grps { 533 if grp.InstanceGroupType != nil { 534 if *grp.InstanceGroupType == typ { 535 return grp 536 } 537 } 538 } 539 return nil 540 } 541 542 func expandTags(m map[string]interface{}) []*emr.Tag { 543 var result []*emr.Tag 544 for k, v := range m { 545 result = append(result, &emr.Tag{ 546 Key: aws.String(k), 547 Value: aws.String(v.(string)), 548 }) 549 } 550 551 return result 552 } 553 554 func tagsToMapEMR(ts []*emr.Tag) map[string]string { 555 result := make(map[string]string) 556 for _, t := range ts { 557 result[*t.Key] = *t.Value 558 } 559 560 return result 561 } 562 563 func expandBootstrapActions(bootstrapActions []interface{}) []*emr.BootstrapActionConfig { 564 actionsOut := []*emr.BootstrapActionConfig{} 565 566 for _, raw := range bootstrapActions { 567 actionAttributes := raw.(map[string]interface{}) 568 actionName := actionAttributes["name"].(string) 569 actionPath := actionAttributes["path"].(string) 570 actionArgs := actionAttributes["args"].(*schema.Set).List() 571 572 action := &emr.BootstrapActionConfig{ 573 Name: aws.String(actionName), 574 ScriptBootstrapAction: &emr.ScriptBootstrapActionConfig{ 575 Path: aws.String(actionPath), 576 Args: expandStringList(actionArgs), 577 }, 578 } 579 actionsOut = append(actionsOut, action) 580 } 581 582 return actionsOut 583 } 584 585 func expandConfigures(input string) []*emr.Configuration { 586 configsOut := []*emr.Configuration{} 587 if strings.HasPrefix(input, "http") { 588 if err := readHttpJson(input, &configsOut); err != nil { 589 log.Printf("[ERR] Error reading HTTP JSON: %s", err) 590 } 591 } else if strings.HasSuffix(input, ".json") { 592 if err := readLocalJson(input, &configsOut); err != nil { 593 log.Printf("[ERR] Error reading local JSON: %s", err) 594 } 595 } else { 596 if err := readBodyJson(input, &configsOut); err != nil { 597 log.Printf("[ERR] Error reading body JSON: %s", err) 598 } 599 } 600 log.Printf("[DEBUG] Expanded EMR Configurations %s", configsOut) 601 602 return configsOut 603 } 604 605 func readHttpJson(url string, target interface{}) error { 606 r, err := http.Get(url) 607 if err != nil { 608 return err 609 } 610 defer r.Body.Close() 611 612 return json.NewDecoder(r.Body).Decode(target) 613 } 614 615 func readLocalJson(localFile string, target interface{}) error { 616 file, e := ioutil.ReadFile(localFile) 617 if e != nil { 618 log.Printf("[ERROR] %s", e) 619 return e 620 } 621 622 return json.Unmarshal(file, target) 623 } 624 625 func readBodyJson(body string, target interface{}) error { 626 log.Printf("[DEBUG] Raw Body %s\n", body) 627 err := json.Unmarshal([]byte(body), target) 628 if err != nil { 629 log.Printf("[ERROR] parsing JSON %s", err) 630 return err 631 } 632 return nil 633 } 634 635 func resourceAwsEMRClusterStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { 636 return func() (interface{}, string, error) { 637 conn := meta.(*AWSClient).emrconn 638 639 log.Printf("[INFO] Reading EMR Cluster Information: %s", d.Id()) 640 params := &emr.DescribeClusterInput{ 641 ClusterId: aws.String(d.Id()), 642 } 643 644 resp, err := conn.DescribeCluster(params) 645 646 if err != nil { 647 if awsErr, ok := err.(awserr.Error); ok { 648 if "ClusterNotFound" == awsErr.Code() { 649 return 42, "destroyed", nil 650 } 651 } 652 log.Printf("[WARN] Error on retrieving EMR Cluster (%s) when waiting: %s", d.Id(), err) 653 return nil, "", err 654 } 655 656 emrc := resp.Cluster 657 658 if emrc == nil { 659 return 42, "destroyed", nil 660 } 661 662 if resp.Cluster.Status != nil { 663 log.Printf("[DEBUG] EMR Cluster status (%s): %s", d.Id(), *resp.Cluster.Status) 664 } 665 666 return emrc, *emrc.Status.State, nil 667 } 668 }