github.com/i0n/terraform@v0.4.3-0.20150506151324-010a39a58ec1/builtin/providers/aws/resource_aws_launch_configuration.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha1"
     6  	"encoding/base64"
     7  	"encoding/hex"
     8  	"fmt"
     9  	"log"
    10  	"time"
    11  
    12  	"github.com/awslabs/aws-sdk-go/aws"
    13  	"github.com/awslabs/aws-sdk-go/service/autoscaling"
    14  	"github.com/awslabs/aws-sdk-go/service/ec2"
    15  	"github.com/hashicorp/terraform/helper/hashcode"
    16  	"github.com/hashicorp/terraform/helper/resource"
    17  	"github.com/hashicorp/terraform/helper/schema"
    18  )
    19  
    20  func resourceAwsLaunchConfiguration() *schema.Resource {
    21  	return &schema.Resource{
    22  		Create: resourceAwsLaunchConfigurationCreate,
    23  		Read:   resourceAwsLaunchConfigurationRead,
    24  		Delete: resourceAwsLaunchConfigurationDelete,
    25  
    26  		Schema: map[string]*schema.Schema{
    27  			"name": &schema.Schema{
    28  				Type:     schema.TypeString,
    29  				Optional: true,
    30  				Computed: true,
    31  				ForceNew: true,
    32  			},
    33  
    34  			"image_id": &schema.Schema{
    35  				Type:     schema.TypeString,
    36  				Required: true,
    37  				ForceNew: true,
    38  			},
    39  
    40  			"instance_type": &schema.Schema{
    41  				Type:     schema.TypeString,
    42  				Required: true,
    43  				ForceNew: true,
    44  			},
    45  
    46  			"iam_instance_profile": &schema.Schema{
    47  				Type:     schema.TypeString,
    48  				Optional: true,
    49  				ForceNew: true,
    50  			},
    51  
    52  			"key_name": &schema.Schema{
    53  				Type:     schema.TypeString,
    54  				Optional: true,
    55  				Computed: true,
    56  				ForceNew: true,
    57  			},
    58  
    59  			"user_data": &schema.Schema{
    60  				Type:     schema.TypeString,
    61  				Optional: true,
    62  				ForceNew: true,
    63  				StateFunc: func(v interface{}) string {
    64  					switch v.(type) {
    65  					case string:
    66  						hash := sha1.Sum([]byte(v.(string)))
    67  						return hex.EncodeToString(hash[:])
    68  					default:
    69  						return ""
    70  					}
    71  				},
    72  			},
    73  
    74  			"security_groups": &schema.Schema{
    75  				Type:     schema.TypeSet,
    76  				Optional: true,
    77  				ForceNew: true,
    78  				Elem:     &schema.Schema{Type: schema.TypeString},
    79  				Set:      schema.HashString,
    80  			},
    81  
    82  			"associate_public_ip_address": &schema.Schema{
    83  				Type:     schema.TypeBool,
    84  				Optional: true,
    85  				ForceNew: true,
    86  				Default:  false,
    87  			},
    88  
    89  			"spot_price": &schema.Schema{
    90  				Type:     schema.TypeString,
    91  				Optional: true,
    92  				ForceNew: true,
    93  			},
    94  
    95  			"ebs_optimized": &schema.Schema{
    96  				Type:     schema.TypeBool,
    97  				Optional: true,
    98  				ForceNew: true,
    99  				Computed: true,
   100  			},
   101  
   102  			"placement_tenancy": &schema.Schema{
   103  				Type:     schema.TypeString,
   104  				Optional: true,
   105  				ForceNew: true,
   106  			},
   107  
   108  			"ebs_block_device": &schema.Schema{
   109  				Type:     schema.TypeSet,
   110  				Optional: true,
   111  				Computed: true,
   112  				Elem: &schema.Resource{
   113  					Schema: map[string]*schema.Schema{
   114  						"delete_on_termination": &schema.Schema{
   115  							Type:     schema.TypeBool,
   116  							Optional: true,
   117  							Default:  true,
   118  							ForceNew: true,
   119  						},
   120  
   121  						"device_name": &schema.Schema{
   122  							Type:     schema.TypeString,
   123  							Required: true,
   124  							ForceNew: true,
   125  						},
   126  
   127  						"iops": &schema.Schema{
   128  							Type:     schema.TypeInt,
   129  							Optional: true,
   130  							Computed: true,
   131  							ForceNew: true,
   132  						},
   133  
   134  						"snapshot_id": &schema.Schema{
   135  							Type:     schema.TypeString,
   136  							Optional: true,
   137  							Computed: true,
   138  							ForceNew: true,
   139  						},
   140  
   141  						"volume_size": &schema.Schema{
   142  							Type:     schema.TypeInt,
   143  							Optional: true,
   144  							Computed: true,
   145  							ForceNew: true,
   146  						},
   147  
   148  						"volume_type": &schema.Schema{
   149  							Type:     schema.TypeString,
   150  							Optional: true,
   151  							Computed: true,
   152  							ForceNew: true,
   153  						},
   154  					},
   155  				},
   156  				Set: func(v interface{}) int {
   157  					var buf bytes.Buffer
   158  					m := v.(map[string]interface{})
   159  					buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
   160  					buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string)))
   161  					return hashcode.String(buf.String())
   162  				},
   163  			},
   164  
   165  			"ephemeral_block_device": &schema.Schema{
   166  				Type:     schema.TypeSet,
   167  				Optional: true,
   168  				ForceNew: true,
   169  				Elem: &schema.Resource{
   170  					Schema: map[string]*schema.Schema{
   171  						"device_name": &schema.Schema{
   172  							Type:     schema.TypeString,
   173  							Required: true,
   174  						},
   175  
   176  						"virtual_name": &schema.Schema{
   177  							Type:     schema.TypeString,
   178  							Required: true,
   179  						},
   180  					},
   181  				},
   182  				Set: func(v interface{}) int {
   183  					var buf bytes.Buffer
   184  					m := v.(map[string]interface{})
   185  					buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
   186  					buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string)))
   187  					return hashcode.String(buf.String())
   188  				},
   189  			},
   190  
   191  			"root_block_device": &schema.Schema{
   192  				// TODO: This is a set because we don't support singleton
   193  				//       sub-resources today. We'll enforce that the set only ever has
   194  				//       length zero or one below. When TF gains support for
   195  				//       sub-resources this can be converted.
   196  				Type:     schema.TypeSet,
   197  				Optional: true,
   198  				Computed: true,
   199  				Elem: &schema.Resource{
   200  					// "You can only modify the volume size, volume type, and Delete on
   201  					// Termination flag on the block device mapping entry for the root
   202  					// device volume." - bit.ly/ec2bdmap
   203  					Schema: map[string]*schema.Schema{
   204  						"delete_on_termination": &schema.Schema{
   205  							Type:     schema.TypeBool,
   206  							Optional: true,
   207  							Default:  true,
   208  							ForceNew: true,
   209  						},
   210  
   211  						"iops": &schema.Schema{
   212  							Type:     schema.TypeInt,
   213  							Optional: true,
   214  							Computed: true,
   215  							ForceNew: true,
   216  						},
   217  
   218  						"volume_size": &schema.Schema{
   219  							Type:     schema.TypeInt,
   220  							Optional: true,
   221  							Computed: true,
   222  							ForceNew: true,
   223  						},
   224  
   225  						"volume_type": &schema.Schema{
   226  							Type:     schema.TypeString,
   227  							Optional: true,
   228  							Computed: true,
   229  							ForceNew: true,
   230  						},
   231  					},
   232  				},
   233  				Set: func(v interface{}) int {
   234  					// there can be only one root device; no need to hash anything
   235  					return 0
   236  				},
   237  			},
   238  		},
   239  	}
   240  }
   241  
   242  func resourceAwsLaunchConfigurationCreate(d *schema.ResourceData, meta interface{}) error {
   243  	autoscalingconn := meta.(*AWSClient).autoscalingconn
   244  	ec2conn := meta.(*AWSClient).ec2conn
   245  
   246  	createLaunchConfigurationOpts := autoscaling.CreateLaunchConfigurationInput{
   247  		LaunchConfigurationName: aws.String(d.Get("name").(string)),
   248  		ImageID:                 aws.String(d.Get("image_id").(string)),
   249  		InstanceType:            aws.String(d.Get("instance_type").(string)),
   250  		EBSOptimized:            aws.Boolean(d.Get("ebs_optimized").(bool)),
   251  	}
   252  
   253  	if v, ok := d.GetOk("user_data"); ok {
   254  		userData := base64.StdEncoding.EncodeToString([]byte(v.(string)))
   255  		createLaunchConfigurationOpts.UserData = aws.String(userData)
   256  	}
   257  
   258  	if v, ok := d.GetOk("iam_instance_profile"); ok {
   259  		createLaunchConfigurationOpts.IAMInstanceProfile = aws.String(v.(string))
   260  	}
   261  
   262  	if v, ok := d.GetOk("placement_tenancy"); ok {
   263  		createLaunchConfigurationOpts.PlacementTenancy = aws.String(v.(string))
   264  	}
   265  
   266  	if v, ok := d.GetOk("associate_public_ip_address"); ok {
   267  		createLaunchConfigurationOpts.AssociatePublicIPAddress = aws.Boolean(v.(bool))
   268  	}
   269  
   270  	if v, ok := d.GetOk("key_name"); ok {
   271  		createLaunchConfigurationOpts.KeyName = aws.String(v.(string))
   272  	}
   273  	if v, ok := d.GetOk("spot_price"); ok {
   274  		createLaunchConfigurationOpts.SpotPrice = aws.String(v.(string))
   275  	}
   276  
   277  	if v, ok := d.GetOk("security_groups"); ok {
   278  		createLaunchConfigurationOpts.SecurityGroups = expandStringList(
   279  			v.(*schema.Set).List(),
   280  		)
   281  	}
   282  
   283  	var blockDevices []*autoscaling.BlockDeviceMapping
   284  
   285  	if v, ok := d.GetOk("ebs_block_device"); ok {
   286  		vL := v.(*schema.Set).List()
   287  		for _, v := range vL {
   288  			bd := v.(map[string]interface{})
   289  			ebs := &autoscaling.EBS{
   290  				DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
   291  			}
   292  
   293  			if v, ok := bd["snapshot_id"].(string); ok && v != "" {
   294  				ebs.SnapshotID = aws.String(v)
   295  			}
   296  
   297  			if v, ok := bd["volume_size"].(int); ok && v != 0 {
   298  				ebs.VolumeSize = aws.Long(int64(v))
   299  			}
   300  
   301  			if v, ok := bd["volume_type"].(string); ok && v != "" {
   302  				ebs.VolumeType = aws.String(v)
   303  			}
   304  
   305  			if v, ok := bd["iops"].(int); ok && v > 0 {
   306  				ebs.IOPS = aws.Long(int64(v))
   307  			}
   308  
   309  			blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{
   310  				DeviceName: aws.String(bd["device_name"].(string)),
   311  				EBS:        ebs,
   312  			})
   313  		}
   314  	}
   315  
   316  	if v, ok := d.GetOk("ephemeral_block_device"); ok {
   317  		vL := v.(*schema.Set).List()
   318  		for _, v := range vL {
   319  			bd := v.(map[string]interface{})
   320  			blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{
   321  				DeviceName:  aws.String(bd["device_name"].(string)),
   322  				VirtualName: aws.String(bd["virtual_name"].(string)),
   323  			})
   324  		}
   325  	}
   326  
   327  	if v, ok := d.GetOk("root_block_device"); ok {
   328  		vL := v.(*schema.Set).List()
   329  		if len(vL) > 1 {
   330  			return fmt.Errorf("Cannot specify more than one root_block_device.")
   331  		}
   332  		for _, v := range vL {
   333  			bd := v.(map[string]interface{})
   334  			ebs := &autoscaling.EBS{
   335  				DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
   336  			}
   337  
   338  			if v, ok := bd["volume_size"].(int); ok && v != 0 {
   339  				ebs.VolumeSize = aws.Long(int64(v))
   340  			}
   341  
   342  			if v, ok := bd["volume_type"].(string); ok && v != "" {
   343  				ebs.VolumeType = aws.String(v)
   344  			}
   345  
   346  			if v, ok := bd["iops"].(int); ok && v > 0 {
   347  				ebs.IOPS = aws.Long(int64(v))
   348  			}
   349  
   350  			if dn, err := fetchRootDeviceName(d.Get("image_id").(string), ec2conn); err == nil {
   351  				blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{
   352  					DeviceName: dn,
   353  					EBS:        ebs,
   354  				})
   355  			} else {
   356  				return err
   357  			}
   358  		}
   359  	}
   360  
   361  	if len(blockDevices) > 0 {
   362  		createLaunchConfigurationOpts.BlockDeviceMappings = blockDevices
   363  	}
   364  
   365  	var lcName string
   366  	if v, ok := d.GetOk("name"); ok {
   367  		lcName = v.(string)
   368  	} else {
   369  		lcName = resource.UniqueId()
   370  	}
   371  	createLaunchConfigurationOpts.LaunchConfigurationName = aws.String(lcName)
   372  
   373  	log.Printf(
   374  		"[DEBUG] autoscaling create launch configuration: %#v", createLaunchConfigurationOpts)
   375  	_, err := autoscalingconn.CreateLaunchConfiguration(&createLaunchConfigurationOpts)
   376  	if err != nil {
   377  		return fmt.Errorf("Error creating launch configuration: %s", err)
   378  	}
   379  
   380  	d.SetId(lcName)
   381  	log.Printf("[INFO] launch configuration ID: %s", d.Id())
   382  
   383  	// We put a Retry here since sometimes eventual consistency bites
   384  	// us and we need to retry a few times to get the LC to load properly
   385  	return resource.Retry(30*time.Second, func() error {
   386  		return resourceAwsLaunchConfigurationRead(d, meta)
   387  	})
   388  }
   389  
   390  func resourceAwsLaunchConfigurationRead(d *schema.ResourceData, meta interface{}) error {
   391  	autoscalingconn := meta.(*AWSClient).autoscalingconn
   392  	ec2conn := meta.(*AWSClient).ec2conn
   393  
   394  	describeOpts := autoscaling.DescribeLaunchConfigurationsInput{
   395  		LaunchConfigurationNames: []*string{aws.String(d.Id())},
   396  	}
   397  
   398  	log.Printf("[DEBUG] launch configuration describe configuration: %#v", describeOpts)
   399  	describConfs, err := autoscalingconn.DescribeLaunchConfigurations(&describeOpts)
   400  	if err != nil {
   401  		return fmt.Errorf("Error retrieving launch configuration: %s", err)
   402  	}
   403  	if len(describConfs.LaunchConfigurations) == 0 {
   404  		d.SetId("")
   405  		return nil
   406  	}
   407  
   408  	// Verify AWS returned our launch configuration
   409  	if *describConfs.LaunchConfigurations[0].LaunchConfigurationName != d.Id() {
   410  		return fmt.Errorf(
   411  			"Unable to find launch configuration: %#v",
   412  			describConfs.LaunchConfigurations)
   413  	}
   414  
   415  	lc := describConfs.LaunchConfigurations[0]
   416  
   417  	d.Set("key_name", lc.KeyName)
   418  	d.Set("image_id", lc.ImageID)
   419  	d.Set("instance_type", lc.InstanceType)
   420  	d.Set("name", lc.LaunchConfigurationName)
   421  
   422  	d.Set("iam_instance_profile", lc.IAMInstanceProfile)
   423  	d.Set("ebs_optimized", lc.EBSOptimized)
   424  	d.Set("spot_price", lc.SpotPrice)
   425  	d.Set("security_groups", lc.SecurityGroups)
   426  
   427  	if err := readLCBlockDevices(d, lc, ec2conn); err != nil {
   428  		return err
   429  	}
   430  
   431  	return nil
   432  }
   433  
   434  func resourceAwsLaunchConfigurationDelete(d *schema.ResourceData, meta interface{}) error {
   435  	autoscalingconn := meta.(*AWSClient).autoscalingconn
   436  
   437  	log.Printf("[DEBUG] Launch Configuration destroy: %v", d.Id())
   438  	_, err := autoscalingconn.DeleteLaunchConfiguration(
   439  		&autoscaling.DeleteLaunchConfigurationInput{
   440  			LaunchConfigurationName: aws.String(d.Id()),
   441  		})
   442  	if err != nil {
   443  		autoscalingerr, ok := err.(aws.APIError)
   444  		if ok && autoscalingerr.Code == "InvalidConfiguration.NotFound" {
   445  			return nil
   446  		}
   447  
   448  		return err
   449  	}
   450  
   451  	return nil
   452  }
   453  
   454  func readLCBlockDevices(d *schema.ResourceData, lc *autoscaling.LaunchConfiguration, ec2conn *ec2.EC2) error {
   455  	ibds, err := readBlockDevicesFromLaunchConfiguration(d, lc, ec2conn)
   456  	if err != nil {
   457  		return err
   458  	}
   459  
   460  	if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil {
   461  		return err
   462  	}
   463  	if err := d.Set("ephemeral_block_device", ibds["ephemeral"]); err != nil {
   464  		return err
   465  	}
   466  	if ibds["root"] != nil {
   467  		if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil {
   468  			return err
   469  		}
   470  	} else {
   471  		d.Set("root_block_device", []interface{}{})
   472  	}
   473  
   474  	return nil
   475  }
   476  
   477  func readBlockDevicesFromLaunchConfiguration(d *schema.ResourceData, lc *autoscaling.LaunchConfiguration, ec2conn *ec2.EC2) (
   478  	map[string]interface{}, error) {
   479  	blockDevices := make(map[string]interface{})
   480  	blockDevices["ebs"] = make([]map[string]interface{}, 0)
   481  	blockDevices["ephemeral"] = make([]map[string]interface{}, 0)
   482  	blockDevices["root"] = nil
   483  	if len(lc.BlockDeviceMappings) == 0 {
   484  		return nil, nil
   485  	}
   486  	rootDeviceName, err := fetchRootDeviceName(d.Get("image_id").(string), ec2conn)
   487  	if err != nil {
   488  		return nil, err
   489  	}
   490  	for _, bdm := range lc.BlockDeviceMappings {
   491  		bd := make(map[string]interface{})
   492  		if bdm.EBS != nil && bdm.EBS.DeleteOnTermination != nil {
   493  			bd["delete_on_termination"] = *bdm.EBS.DeleteOnTermination
   494  		}
   495  		if bdm.EBS != nil && bdm.EBS.VolumeSize != nil {
   496  			bd["volume_size"] = *bdm.EBS.VolumeSize
   497  		}
   498  		if bdm.EBS != nil && bdm.EBS.VolumeType != nil {
   499  			bd["volume_type"] = *bdm.EBS.VolumeType
   500  		}
   501  		if bdm.EBS != nil && bdm.EBS.IOPS != nil {
   502  			bd["iops"] = *bdm.EBS.IOPS
   503  		}
   504  		if bdm.DeviceName != nil && *bdm.DeviceName == *rootDeviceName {
   505  			blockDevices["root"] = bd
   506  		} else {
   507  			if bdm.DeviceName != nil {
   508  				bd["device_name"] = *bdm.DeviceName
   509  			}
   510  			if bdm.VirtualName != nil {
   511  				bd["virtual_name"] = *bdm.VirtualName
   512  				blockDevices["ephemeral"] = append(blockDevices["ephemeral"].([]map[string]interface{}), bd)
   513  			} else {
   514  				if bdm.EBS != nil && bdm.EBS.SnapshotID != nil {
   515  					bd["snapshot_id"] = *bdm.EBS.SnapshotID
   516  				}
   517  				blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd)
   518  			}
   519  		}
   520  	}
   521  	return blockDevices, nil
   522  }