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