github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/aws/resource_aws_launch_configuration.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha1"
     6  	"encoding/hex"
     7  	"fmt"
     8  	"log"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/aws/aws-sdk-go/aws"
    13  	"github.com/aws/aws-sdk-go/aws/awserr"
    14  	"github.com/aws/aws-sdk-go/service/autoscaling"
    15  	"github.com/aws/aws-sdk-go/service/ec2"
    16  	"github.com/hashicorp/terraform/helper/hashcode"
    17  	"github.com/hashicorp/terraform/helper/resource"
    18  	"github.com/hashicorp/terraform/helper/schema"
    19  )
    20  
    21  func resourceAwsLaunchConfiguration() *schema.Resource {
    22  	return &schema.Resource{
    23  		Create: resourceAwsLaunchConfigurationCreate,
    24  		Read:   resourceAwsLaunchConfigurationRead,
    25  		Delete: resourceAwsLaunchConfigurationDelete,
    26  		Importer: &schema.ResourceImporter{
    27  			State: schema.ImportStatePassthrough,
    28  		},
    29  
    30  		Schema: map[string]*schema.Schema{
    31  			"name": &schema.Schema{
    32  				Type:          schema.TypeString,
    33  				Optional:      true,
    34  				Computed:      true,
    35  				ForceNew:      true,
    36  				ConflictsWith: []string{"name_prefix"},
    37  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    38  					// https://github.com/boto/botocore/blob/9f322b1/botocore/data/autoscaling/2011-01-01/service-2.json#L1932-L1939
    39  					value := v.(string)
    40  					if len(value) > 255 {
    41  						errors = append(errors, fmt.Errorf(
    42  							"%q cannot be longer than 255 characters", k))
    43  					}
    44  					return
    45  				},
    46  			},
    47  
    48  			"name_prefix": &schema.Schema{
    49  				Type:     schema.TypeString,
    50  				Optional: true,
    51  				ForceNew: true,
    52  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    53  					// https://github.com/boto/botocore/blob/9f322b1/botocore/data/autoscaling/2011-01-01/service-2.json#L1932-L1939
    54  					// uuid is 26 characters, limit the prefix to 229.
    55  					value := v.(string)
    56  					if len(value) > 229 {
    57  						errors = append(errors, fmt.Errorf(
    58  							"%q cannot be longer than 229 characters, name is limited to 255", k))
    59  					}
    60  					return
    61  				},
    62  			},
    63  
    64  			"image_id": &schema.Schema{
    65  				Type:     schema.TypeString,
    66  				Required: true,
    67  				ForceNew: true,
    68  			},
    69  
    70  			"instance_type": &schema.Schema{
    71  				Type:     schema.TypeString,
    72  				Required: true,
    73  				ForceNew: true,
    74  			},
    75  
    76  			"iam_instance_profile": &schema.Schema{
    77  				Type:     schema.TypeString,
    78  				Optional: true,
    79  				ForceNew: true,
    80  			},
    81  
    82  			"key_name": &schema.Schema{
    83  				Type:     schema.TypeString,
    84  				Optional: true,
    85  				Computed: true,
    86  				ForceNew: true,
    87  			},
    88  
    89  			"user_data": &schema.Schema{
    90  				Type:     schema.TypeString,
    91  				Optional: true,
    92  				ForceNew: true,
    93  				StateFunc: func(v interface{}) string {
    94  					switch v.(type) {
    95  					case string:
    96  						hash := sha1.Sum([]byte(v.(string)))
    97  						return hex.EncodeToString(hash[:])
    98  					default:
    99  						return ""
   100  					}
   101  				},
   102  			},
   103  
   104  			"security_groups": &schema.Schema{
   105  				Type:     schema.TypeSet,
   106  				Optional: true,
   107  				ForceNew: true,
   108  				Elem:     &schema.Schema{Type: schema.TypeString},
   109  				Set:      schema.HashString,
   110  			},
   111  
   112  			"vpc_classic_link_id": &schema.Schema{
   113  				Type:     schema.TypeString,
   114  				Optional: true,
   115  				ForceNew: true,
   116  			},
   117  
   118  			"vpc_classic_link_security_groups": &schema.Schema{
   119  				Type:     schema.TypeSet,
   120  				Optional: true,
   121  				ForceNew: true,
   122  				Elem:     &schema.Schema{Type: schema.TypeString},
   123  				Set:      schema.HashString,
   124  			},
   125  
   126  			"associate_public_ip_address": &schema.Schema{
   127  				Type:     schema.TypeBool,
   128  				Optional: true,
   129  				ForceNew: true,
   130  				Default:  false,
   131  			},
   132  
   133  			"spot_price": &schema.Schema{
   134  				Type:     schema.TypeString,
   135  				Optional: true,
   136  				ForceNew: true,
   137  			},
   138  
   139  			"ebs_optimized": &schema.Schema{
   140  				Type:     schema.TypeBool,
   141  				Optional: true,
   142  				ForceNew: true,
   143  				Computed: true,
   144  			},
   145  
   146  			"placement_tenancy": &schema.Schema{
   147  				Type:     schema.TypeString,
   148  				Optional: true,
   149  				ForceNew: true,
   150  			},
   151  
   152  			"enable_monitoring": &schema.Schema{
   153  				Type:     schema.TypeBool,
   154  				Optional: true,
   155  				ForceNew: true,
   156  				Default:  true,
   157  			},
   158  
   159  			"ebs_block_device": &schema.Schema{
   160  				Type:     schema.TypeSet,
   161  				Optional: true,
   162  				Computed: true,
   163  				Elem: &schema.Resource{
   164  					Schema: map[string]*schema.Schema{
   165  						"delete_on_termination": &schema.Schema{
   166  							Type:     schema.TypeBool,
   167  							Optional: true,
   168  							Default:  true,
   169  							ForceNew: true,
   170  						},
   171  
   172  						"device_name": &schema.Schema{
   173  							Type:     schema.TypeString,
   174  							Required: true,
   175  							ForceNew: true,
   176  						},
   177  
   178  						"iops": &schema.Schema{
   179  							Type:     schema.TypeInt,
   180  							Optional: true,
   181  							Computed: true,
   182  							ForceNew: true,
   183  						},
   184  
   185  						"snapshot_id": &schema.Schema{
   186  							Type:     schema.TypeString,
   187  							Optional: true,
   188  							Computed: true,
   189  							ForceNew: true,
   190  						},
   191  
   192  						"volume_size": &schema.Schema{
   193  							Type:     schema.TypeInt,
   194  							Optional: true,
   195  							Computed: true,
   196  							ForceNew: true,
   197  						},
   198  
   199  						"volume_type": &schema.Schema{
   200  							Type:     schema.TypeString,
   201  							Optional: true,
   202  							Computed: true,
   203  							ForceNew: true,
   204  						},
   205  
   206  						"encrypted": &schema.Schema{
   207  							Type:     schema.TypeBool,
   208  							Optional: true,
   209  							Computed: true,
   210  							ForceNew: true,
   211  						},
   212  					},
   213  				},
   214  				Set: func(v interface{}) int {
   215  					var buf bytes.Buffer
   216  					m := v.(map[string]interface{})
   217  					buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
   218  					buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string)))
   219  					return hashcode.String(buf.String())
   220  				},
   221  			},
   222  
   223  			"ephemeral_block_device": &schema.Schema{
   224  				Type:     schema.TypeSet,
   225  				Optional: true,
   226  				ForceNew: true,
   227  				Elem: &schema.Resource{
   228  					Schema: map[string]*schema.Schema{
   229  						"device_name": &schema.Schema{
   230  							Type:     schema.TypeString,
   231  							Required: true,
   232  						},
   233  
   234  						"virtual_name": &schema.Schema{
   235  							Type:     schema.TypeString,
   236  							Required: true,
   237  						},
   238  					},
   239  				},
   240  				Set: func(v interface{}) int {
   241  					var buf bytes.Buffer
   242  					m := v.(map[string]interface{})
   243  					buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
   244  					buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string)))
   245  					return hashcode.String(buf.String())
   246  				},
   247  			},
   248  
   249  			"root_block_device": &schema.Schema{
   250  				// TODO: This is a set because we don't support singleton
   251  				//       sub-resources today. We'll enforce that the set only ever has
   252  				//       length zero or one below. When TF gains support for
   253  				//       sub-resources this can be converted.
   254  				Type:     schema.TypeSet,
   255  				Optional: true,
   256  				Computed: true,
   257  				Elem: &schema.Resource{
   258  					// "You can only modify the volume size, volume type, and Delete on
   259  					// Termination flag on the block device mapping entry for the root
   260  					// device volume." - bit.ly/ec2bdmap
   261  					Schema: map[string]*schema.Schema{
   262  						"delete_on_termination": &schema.Schema{
   263  							Type:     schema.TypeBool,
   264  							Optional: true,
   265  							Default:  true,
   266  							ForceNew: true,
   267  						},
   268  
   269  						"iops": &schema.Schema{
   270  							Type:     schema.TypeInt,
   271  							Optional: true,
   272  							Computed: true,
   273  							ForceNew: true,
   274  						},
   275  
   276  						"volume_size": &schema.Schema{
   277  							Type:     schema.TypeInt,
   278  							Optional: true,
   279  							Computed: true,
   280  							ForceNew: true,
   281  						},
   282  
   283  						"volume_type": &schema.Schema{
   284  							Type:     schema.TypeString,
   285  							Optional: true,
   286  							Computed: true,
   287  							ForceNew: true,
   288  						},
   289  					},
   290  				},
   291  				Set: func(v interface{}) int {
   292  					// there can be only one root device; no need to hash anything
   293  					return 0
   294  				},
   295  			},
   296  		},
   297  	}
   298  }
   299  
   300  func resourceAwsLaunchConfigurationCreate(d *schema.ResourceData, meta interface{}) error {
   301  	autoscalingconn := meta.(*AWSClient).autoscalingconn
   302  	ec2conn := meta.(*AWSClient).ec2conn
   303  
   304  	createLaunchConfigurationOpts := autoscaling.CreateLaunchConfigurationInput{
   305  		LaunchConfigurationName: aws.String(d.Get("name").(string)),
   306  		ImageId:                 aws.String(d.Get("image_id").(string)),
   307  		InstanceType:            aws.String(d.Get("instance_type").(string)),
   308  		EbsOptimized:            aws.Bool(d.Get("ebs_optimized").(bool)),
   309  	}
   310  
   311  	if v, ok := d.GetOk("user_data"); ok {
   312  		userData := base64Encode([]byte(v.(string)))
   313  		createLaunchConfigurationOpts.UserData = aws.String(userData)
   314  	}
   315  
   316  	createLaunchConfigurationOpts.InstanceMonitoring = &autoscaling.InstanceMonitoring{
   317  		Enabled: aws.Bool(d.Get("enable_monitoring").(bool)),
   318  	}
   319  
   320  	if v, ok := d.GetOk("iam_instance_profile"); ok {
   321  		createLaunchConfigurationOpts.IamInstanceProfile = aws.String(v.(string))
   322  	}
   323  
   324  	if v, ok := d.GetOk("placement_tenancy"); ok {
   325  		createLaunchConfigurationOpts.PlacementTenancy = aws.String(v.(string))
   326  	}
   327  
   328  	if v, ok := d.GetOk("associate_public_ip_address"); ok {
   329  		createLaunchConfigurationOpts.AssociatePublicIpAddress = aws.Bool(v.(bool))
   330  	}
   331  
   332  	if v, ok := d.GetOk("key_name"); ok {
   333  		createLaunchConfigurationOpts.KeyName = aws.String(v.(string))
   334  	}
   335  	if v, ok := d.GetOk("spot_price"); ok {
   336  		createLaunchConfigurationOpts.SpotPrice = aws.String(v.(string))
   337  	}
   338  
   339  	if v, ok := d.GetOk("security_groups"); ok {
   340  		createLaunchConfigurationOpts.SecurityGroups = expandStringList(
   341  			v.(*schema.Set).List(),
   342  		)
   343  	}
   344  
   345  	if v, ok := d.GetOk("vpc_classic_link_id"); ok {
   346  		createLaunchConfigurationOpts.ClassicLinkVPCId = aws.String(v.(string))
   347  	}
   348  
   349  	if v, ok := d.GetOk("vpc_classic_link_security_groups"); ok {
   350  		createLaunchConfigurationOpts.ClassicLinkVPCSecurityGroups = expandStringList(
   351  			v.(*schema.Set).List(),
   352  		)
   353  	}
   354  
   355  	var blockDevices []*autoscaling.BlockDeviceMapping
   356  
   357  	// We'll use this to detect if we're declaring it incorrectly as an ebs_block_device.
   358  	rootDeviceName, err := fetchRootDeviceName(d.Get("image_id").(string), ec2conn)
   359  	if err != nil {
   360  		return err
   361  	}
   362  	if rootDeviceName == nil {
   363  		// We do this so the value is empty so we don't have to do nil checks later
   364  		var blank string
   365  		rootDeviceName = &blank
   366  	}
   367  
   368  	if v, ok := d.GetOk("ebs_block_device"); ok {
   369  		vL := v.(*schema.Set).List()
   370  		for _, v := range vL {
   371  			bd := v.(map[string]interface{})
   372  			ebs := &autoscaling.Ebs{
   373  				DeleteOnTermination: aws.Bool(bd["delete_on_termination"].(bool)),
   374  			}
   375  
   376  			if v, ok := bd["snapshot_id"].(string); ok && v != "" {
   377  				ebs.SnapshotId = aws.String(v)
   378  			}
   379  
   380  			if v, ok := bd["encrypted"].(bool); ok && v {
   381  				ebs.Encrypted = aws.Bool(v)
   382  			}
   383  
   384  			if v, ok := bd["volume_size"].(int); ok && v != 0 {
   385  				ebs.VolumeSize = aws.Int64(int64(v))
   386  			}
   387  
   388  			if v, ok := bd["volume_type"].(string); ok && v != "" {
   389  				ebs.VolumeType = aws.String(v)
   390  			}
   391  
   392  			if v, ok := bd["iops"].(int); ok && v > 0 {
   393  				ebs.Iops = aws.Int64(int64(v))
   394  			}
   395  
   396  			if *aws.String(bd["device_name"].(string)) == *rootDeviceName {
   397  				return fmt.Errorf("Root device (%s) declared as an 'ebs_block_device'.  Use 'root_block_device' keyword.", *rootDeviceName)
   398  			}
   399  
   400  			blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{
   401  				DeviceName: aws.String(bd["device_name"].(string)),
   402  				Ebs:        ebs,
   403  			})
   404  		}
   405  	}
   406  
   407  	if v, ok := d.GetOk("ephemeral_block_device"); ok {
   408  		vL := v.(*schema.Set).List()
   409  		for _, v := range vL {
   410  			bd := v.(map[string]interface{})
   411  			blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{
   412  				DeviceName:  aws.String(bd["device_name"].(string)),
   413  				VirtualName: aws.String(bd["virtual_name"].(string)),
   414  			})
   415  		}
   416  	}
   417  
   418  	if v, ok := d.GetOk("root_block_device"); ok {
   419  		vL := v.(*schema.Set).List()
   420  		if len(vL) > 1 {
   421  			return fmt.Errorf("Cannot specify more than one root_block_device.")
   422  		}
   423  		for _, v := range vL {
   424  			bd := v.(map[string]interface{})
   425  			ebs := &autoscaling.Ebs{
   426  				DeleteOnTermination: aws.Bool(bd["delete_on_termination"].(bool)),
   427  			}
   428  
   429  			if v, ok := bd["volume_size"].(int); ok && v != 0 {
   430  				ebs.VolumeSize = aws.Int64(int64(v))
   431  			}
   432  
   433  			if v, ok := bd["volume_type"].(string); ok && v != "" {
   434  				ebs.VolumeType = aws.String(v)
   435  			}
   436  
   437  			if v, ok := bd["iops"].(int); ok && v > 0 {
   438  				ebs.Iops = aws.Int64(int64(v))
   439  			}
   440  
   441  			if dn, err := fetchRootDeviceName(d.Get("image_id").(string), ec2conn); err == nil {
   442  				if dn == nil {
   443  					return fmt.Errorf(
   444  						"Expected to find a Root Device name for AMI (%s), but got none",
   445  						d.Get("image_id").(string))
   446  				}
   447  				blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{
   448  					DeviceName: dn,
   449  					Ebs:        ebs,
   450  				})
   451  			} else {
   452  				return err
   453  			}
   454  		}
   455  	}
   456  
   457  	if len(blockDevices) > 0 {
   458  		createLaunchConfigurationOpts.BlockDeviceMappings = blockDevices
   459  	}
   460  
   461  	var lcName string
   462  	if v, ok := d.GetOk("name"); ok {
   463  		lcName = v.(string)
   464  	} else if v, ok := d.GetOk("name_prefix"); ok {
   465  		lcName = resource.PrefixedUniqueId(v.(string))
   466  	} else {
   467  		lcName = resource.UniqueId()
   468  	}
   469  	createLaunchConfigurationOpts.LaunchConfigurationName = aws.String(lcName)
   470  
   471  	log.Printf(
   472  		"[DEBUG] autoscaling create launch configuration: %s", createLaunchConfigurationOpts)
   473  
   474  	// IAM profiles can take ~10 seconds to propagate in AWS:
   475  	// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console
   476  	err = resource.Retry(30*time.Second, func() *resource.RetryError {
   477  		_, err := autoscalingconn.CreateLaunchConfiguration(&createLaunchConfigurationOpts)
   478  		if err != nil {
   479  			if awsErr, ok := err.(awserr.Error); ok {
   480  				if strings.Contains(awsErr.Message(), "Invalid IamInstanceProfile") {
   481  					return resource.RetryableError(err)
   482  				}
   483  				if strings.Contains(awsErr.Message(), "You are not authorized to perform this operation") {
   484  					return resource.RetryableError(err)
   485  				}
   486  			}
   487  			return resource.NonRetryableError(err)
   488  		}
   489  		return nil
   490  	})
   491  
   492  	if err != nil {
   493  		return fmt.Errorf("Error creating launch configuration: %s", err)
   494  	}
   495  
   496  	d.SetId(lcName)
   497  	log.Printf("[INFO] launch configuration ID: %s", d.Id())
   498  
   499  	// We put a Retry here since sometimes eventual consistency bites
   500  	// us and we need to retry a few times to get the LC to load properly
   501  	return resource.Retry(30*time.Second, func() *resource.RetryError {
   502  		err := resourceAwsLaunchConfigurationRead(d, meta)
   503  		if err != nil {
   504  			return resource.RetryableError(err)
   505  		}
   506  		return nil
   507  	})
   508  }
   509  
   510  func resourceAwsLaunchConfigurationRead(d *schema.ResourceData, meta interface{}) error {
   511  	autoscalingconn := meta.(*AWSClient).autoscalingconn
   512  	ec2conn := meta.(*AWSClient).ec2conn
   513  
   514  	describeOpts := autoscaling.DescribeLaunchConfigurationsInput{
   515  		LaunchConfigurationNames: []*string{aws.String(d.Id())},
   516  	}
   517  
   518  	log.Printf("[DEBUG] launch configuration describe configuration: %s", describeOpts)
   519  	describConfs, err := autoscalingconn.DescribeLaunchConfigurations(&describeOpts)
   520  	if err != nil {
   521  		return fmt.Errorf("Error retrieving launch configuration: %s", err)
   522  	}
   523  	if len(describConfs.LaunchConfigurations) == 0 {
   524  		d.SetId("")
   525  		return nil
   526  	}
   527  
   528  	// Verify AWS returned our launch configuration
   529  	if *describConfs.LaunchConfigurations[0].LaunchConfigurationName != d.Id() {
   530  		return fmt.Errorf(
   531  			"Unable to find launch configuration: %#v",
   532  			describConfs.LaunchConfigurations)
   533  	}
   534  
   535  	lc := describConfs.LaunchConfigurations[0]
   536  
   537  	d.Set("key_name", lc.KeyName)
   538  	d.Set("image_id", lc.ImageId)
   539  	d.Set("instance_type", lc.InstanceType)
   540  	d.Set("name", lc.LaunchConfigurationName)
   541  
   542  	d.Set("iam_instance_profile", lc.IamInstanceProfile)
   543  	d.Set("ebs_optimized", lc.EbsOptimized)
   544  	d.Set("spot_price", lc.SpotPrice)
   545  	d.Set("enable_monitoring", lc.InstanceMonitoring.Enabled)
   546  	d.Set("security_groups", lc.SecurityGroups)
   547  	d.Set("associate_public_ip_address", lc.AssociatePublicIpAddress)
   548  
   549  	d.Set("vpc_classic_link_id", lc.ClassicLinkVPCId)
   550  	d.Set("vpc_classic_link_security_groups", lc.ClassicLinkVPCSecurityGroups)
   551  
   552  	if err := readLCBlockDevices(d, lc, ec2conn); err != nil {
   553  		return err
   554  	}
   555  
   556  	return nil
   557  }
   558  
   559  func resourceAwsLaunchConfigurationDelete(d *schema.ResourceData, meta interface{}) error {
   560  	autoscalingconn := meta.(*AWSClient).autoscalingconn
   561  
   562  	log.Printf("[DEBUG] Launch Configuration destroy: %v", d.Id())
   563  	_, err := autoscalingconn.DeleteLaunchConfiguration(
   564  		&autoscaling.DeleteLaunchConfigurationInput{
   565  			LaunchConfigurationName: aws.String(d.Id()),
   566  		})
   567  	if err != nil {
   568  		autoscalingerr, ok := err.(awserr.Error)
   569  		if ok && (autoscalingerr.Code() == "InvalidConfiguration.NotFound" || autoscalingerr.Code() == "ValidationError") {
   570  			log.Printf("[DEBUG] Launch configuration (%s) not found", d.Id())
   571  			return nil
   572  		}
   573  
   574  		return err
   575  	}
   576  
   577  	return nil
   578  }
   579  
   580  func readLCBlockDevices(d *schema.ResourceData, lc *autoscaling.LaunchConfiguration, ec2conn *ec2.EC2) error {
   581  	ibds, err := readBlockDevicesFromLaunchConfiguration(d, lc, ec2conn)
   582  	if err != nil {
   583  		return err
   584  	}
   585  
   586  	if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil {
   587  		return err
   588  	}
   589  	if err := d.Set("ephemeral_block_device", ibds["ephemeral"]); err != nil {
   590  		return err
   591  	}
   592  	if ibds["root"] != nil {
   593  		if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil {
   594  			return err
   595  		}
   596  	} else {
   597  		d.Set("root_block_device", []interface{}{})
   598  	}
   599  
   600  	return nil
   601  }
   602  
   603  func readBlockDevicesFromLaunchConfiguration(d *schema.ResourceData, lc *autoscaling.LaunchConfiguration, ec2conn *ec2.EC2) (
   604  	map[string]interface{}, error) {
   605  	blockDevices := make(map[string]interface{})
   606  	blockDevices["ebs"] = make([]map[string]interface{}, 0)
   607  	blockDevices["ephemeral"] = make([]map[string]interface{}, 0)
   608  	blockDevices["root"] = nil
   609  	if len(lc.BlockDeviceMappings) == 0 {
   610  		return nil, nil
   611  	}
   612  	rootDeviceName, err := fetchRootDeviceName(d.Get("image_id").(string), ec2conn)
   613  	if err != nil {
   614  		return nil, err
   615  	}
   616  	if rootDeviceName == nil {
   617  		// We do this so the value is empty so we don't have to do nil checks later
   618  		var blank string
   619  		rootDeviceName = &blank
   620  	}
   621  	for _, bdm := range lc.BlockDeviceMappings {
   622  		bd := make(map[string]interface{})
   623  		if bdm.Ebs != nil && bdm.Ebs.DeleteOnTermination != nil {
   624  			bd["delete_on_termination"] = *bdm.Ebs.DeleteOnTermination
   625  		}
   626  		if bdm.Ebs != nil && bdm.Ebs.VolumeSize != nil {
   627  			bd["volume_size"] = *bdm.Ebs.VolumeSize
   628  		}
   629  		if bdm.Ebs != nil && bdm.Ebs.VolumeType != nil {
   630  			bd["volume_type"] = *bdm.Ebs.VolumeType
   631  		}
   632  		if bdm.Ebs != nil && bdm.Ebs.Iops != nil {
   633  			bd["iops"] = *bdm.Ebs.Iops
   634  		}
   635  
   636  		if bdm.DeviceName != nil && *bdm.DeviceName == *rootDeviceName {
   637  			blockDevices["root"] = bd
   638  		} else {
   639  			if bdm.Ebs != nil && bdm.Ebs.Encrypted != nil {
   640  				bd["encrypted"] = *bdm.Ebs.Encrypted
   641  			}
   642  			if bdm.DeviceName != nil {
   643  				bd["device_name"] = *bdm.DeviceName
   644  			}
   645  			if bdm.VirtualName != nil {
   646  				bd["virtual_name"] = *bdm.VirtualName
   647  				blockDevices["ephemeral"] = append(blockDevices["ephemeral"].([]map[string]interface{}), bd)
   648  			} else {
   649  				if bdm.Ebs != nil && bdm.Ebs.SnapshotId != nil {
   650  					bd["snapshot_id"] = *bdm.Ebs.SnapshotId
   651  				}
   652  				blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd)
   653  			}
   654  		}
   655  	}
   656  	return blockDevices, nil
   657  }