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