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