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