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