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