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