github.com/andresvia/terraform@v0.6.15-0.20160412045437-d51c75946785/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  				Encrypted:           aws.Bool(bd["encrypted"].(bool)),
   338  			}
   339  
   340  			if v, ok := bd["snapshot_id"].(string); ok && v != "" {
   341  				ebs.SnapshotId = aws.String(v)
   342  			}
   343  
   344  			if v, ok := bd["volume_size"].(int); ok && v != 0 {
   345  				ebs.VolumeSize = aws.Int64(int64(v))
   346  			}
   347  
   348  			if v, ok := bd["volume_type"].(string); ok && v != "" {
   349  				ebs.VolumeType = aws.String(v)
   350  			}
   351  
   352  			if v, ok := bd["iops"].(int); ok && v > 0 {
   353  				ebs.Iops = aws.Int64(int64(v))
   354  			}
   355  
   356  			blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{
   357  				DeviceName: aws.String(bd["device_name"].(string)),
   358  				Ebs:        ebs,
   359  			})
   360  		}
   361  	}
   362  
   363  	if v, ok := d.GetOk("ephemeral_block_device"); ok {
   364  		vL := v.(*schema.Set).List()
   365  		for _, v := range vL {
   366  			bd := v.(map[string]interface{})
   367  			blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{
   368  				DeviceName:  aws.String(bd["device_name"].(string)),
   369  				VirtualName: aws.String(bd["virtual_name"].(string)),
   370  			})
   371  		}
   372  	}
   373  
   374  	if v, ok := d.GetOk("root_block_device"); ok {
   375  		vL := v.(*schema.Set).List()
   376  		if len(vL) > 1 {
   377  			return fmt.Errorf("Cannot specify more than one root_block_device.")
   378  		}
   379  		for _, v := range vL {
   380  			bd := v.(map[string]interface{})
   381  			ebs := &autoscaling.Ebs{
   382  				DeleteOnTermination: aws.Bool(bd["delete_on_termination"].(bool)),
   383  			}
   384  
   385  			if v, ok := bd["volume_size"].(int); ok && v != 0 {
   386  				ebs.VolumeSize = aws.Int64(int64(v))
   387  			}
   388  
   389  			if v, ok := bd["volume_type"].(string); ok && v != "" {
   390  				ebs.VolumeType = aws.String(v)
   391  			}
   392  
   393  			if v, ok := bd["iops"].(int); ok && v > 0 {
   394  				ebs.Iops = aws.Int64(int64(v))
   395  			}
   396  
   397  			if dn, err := fetchRootDeviceName(d.Get("image_id").(string), ec2conn); err == nil {
   398  				if dn == nil {
   399  					return fmt.Errorf(
   400  						"Expected to find a Root Device name for AMI (%s), but got none",
   401  						d.Get("image_id").(string))
   402  				}
   403  				blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{
   404  					DeviceName: dn,
   405  					Ebs:        ebs,
   406  				})
   407  			} else {
   408  				return err
   409  			}
   410  		}
   411  	}
   412  
   413  	if len(blockDevices) > 0 {
   414  		createLaunchConfigurationOpts.BlockDeviceMappings = blockDevices
   415  	}
   416  
   417  	var lcName string
   418  	if v, ok := d.GetOk("name"); ok {
   419  		lcName = v.(string)
   420  	} else if v, ok := d.GetOk("name_prefix"); ok {
   421  		lcName = resource.PrefixedUniqueId(v.(string))
   422  	} else {
   423  		lcName = resource.UniqueId()
   424  	}
   425  	createLaunchConfigurationOpts.LaunchConfigurationName = aws.String(lcName)
   426  
   427  	log.Printf(
   428  		"[DEBUG] autoscaling create launch configuration: %s", createLaunchConfigurationOpts)
   429  
   430  	// IAM profiles can take ~10 seconds to propagate in AWS:
   431  	// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console
   432  	err := resource.Retry(30*time.Second, func() *resource.RetryError {
   433  		_, err := autoscalingconn.CreateLaunchConfiguration(&createLaunchConfigurationOpts)
   434  		if err != nil {
   435  			if awsErr, ok := err.(awserr.Error); ok {
   436  				if strings.Contains(awsErr.Message(), "Invalid IamInstanceProfile") {
   437  					return resource.RetryableError(err)
   438  				}
   439  			}
   440  			return resource.NonRetryableError(err)
   441  		}
   442  		return nil
   443  	})
   444  
   445  	if err != nil {
   446  		return fmt.Errorf("Error creating launch configuration: %s", err)
   447  	}
   448  
   449  	d.SetId(lcName)
   450  	log.Printf("[INFO] launch configuration ID: %s", d.Id())
   451  
   452  	// We put a Retry here since sometimes eventual consistency bites
   453  	// us and we need to retry a few times to get the LC to load properly
   454  	return resource.Retry(30*time.Second, func() *resource.RetryError {
   455  		err := resourceAwsLaunchConfigurationRead(d, meta)
   456  		if err != nil {
   457  			return resource.RetryableError(err)
   458  		}
   459  		return nil
   460  	})
   461  }
   462  
   463  func resourceAwsLaunchConfigurationRead(d *schema.ResourceData, meta interface{}) error {
   464  	autoscalingconn := meta.(*AWSClient).autoscalingconn
   465  	ec2conn := meta.(*AWSClient).ec2conn
   466  
   467  	describeOpts := autoscaling.DescribeLaunchConfigurationsInput{
   468  		LaunchConfigurationNames: []*string{aws.String(d.Id())},
   469  	}
   470  
   471  	log.Printf("[DEBUG] launch configuration describe configuration: %s", describeOpts)
   472  	describConfs, err := autoscalingconn.DescribeLaunchConfigurations(&describeOpts)
   473  	if err != nil {
   474  		return fmt.Errorf("Error retrieving launch configuration: %s", err)
   475  	}
   476  	if len(describConfs.LaunchConfigurations) == 0 {
   477  		d.SetId("")
   478  		return nil
   479  	}
   480  
   481  	// Verify AWS returned our launch configuration
   482  	if *describConfs.LaunchConfigurations[0].LaunchConfigurationName != d.Id() {
   483  		return fmt.Errorf(
   484  			"Unable to find launch configuration: %#v",
   485  			describConfs.LaunchConfigurations)
   486  	}
   487  
   488  	lc := describConfs.LaunchConfigurations[0]
   489  
   490  	d.Set("key_name", lc.KeyName)
   491  	d.Set("image_id", lc.ImageId)
   492  	d.Set("instance_type", lc.InstanceType)
   493  	d.Set("name", lc.LaunchConfigurationName)
   494  
   495  	d.Set("iam_instance_profile", lc.IamInstanceProfile)
   496  	d.Set("ebs_optimized", lc.EbsOptimized)
   497  	d.Set("spot_price", lc.SpotPrice)
   498  	d.Set("enable_monitoring", lc.InstanceMonitoring.Enabled)
   499  	d.Set("security_groups", lc.SecurityGroups)
   500  
   501  	if err := readLCBlockDevices(d, lc, ec2conn); err != nil {
   502  		return err
   503  	}
   504  
   505  	return nil
   506  }
   507  
   508  func resourceAwsLaunchConfigurationDelete(d *schema.ResourceData, meta interface{}) error {
   509  	autoscalingconn := meta.(*AWSClient).autoscalingconn
   510  
   511  	log.Printf("[DEBUG] Launch Configuration destroy: %v", d.Id())
   512  	_, err := autoscalingconn.DeleteLaunchConfiguration(
   513  		&autoscaling.DeleteLaunchConfigurationInput{
   514  			LaunchConfigurationName: aws.String(d.Id()),
   515  		})
   516  	if err != nil {
   517  		autoscalingerr, ok := err.(awserr.Error)
   518  		if ok && (autoscalingerr.Code() == "InvalidConfiguration.NotFound" || autoscalingerr.Code() == "ValidationError") {
   519  			log.Printf("[DEBUG] Launch configuration (%s) not found", d.Id())
   520  			return nil
   521  		}
   522  
   523  		return err
   524  	}
   525  
   526  	return nil
   527  }
   528  
   529  func readLCBlockDevices(d *schema.ResourceData, lc *autoscaling.LaunchConfiguration, ec2conn *ec2.EC2) error {
   530  	ibds, err := readBlockDevicesFromLaunchConfiguration(d, lc, ec2conn)
   531  	if err != nil {
   532  		return err
   533  	}
   534  
   535  	if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil {
   536  		return err
   537  	}
   538  	if err := d.Set("ephemeral_block_device", ibds["ephemeral"]); err != nil {
   539  		return err
   540  	}
   541  	if ibds["root"] != nil {
   542  		if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil {
   543  			return err
   544  		}
   545  	} else {
   546  		d.Set("root_block_device", []interface{}{})
   547  	}
   548  
   549  	return nil
   550  }
   551  
   552  func readBlockDevicesFromLaunchConfiguration(d *schema.ResourceData, lc *autoscaling.LaunchConfiguration, ec2conn *ec2.EC2) (
   553  	map[string]interface{}, error) {
   554  	blockDevices := make(map[string]interface{})
   555  	blockDevices["ebs"] = make([]map[string]interface{}, 0)
   556  	blockDevices["ephemeral"] = make([]map[string]interface{}, 0)
   557  	blockDevices["root"] = nil
   558  	if len(lc.BlockDeviceMappings) == 0 {
   559  		return nil, nil
   560  	}
   561  	rootDeviceName, err := fetchRootDeviceName(d.Get("image_id").(string), ec2conn)
   562  	if err != nil {
   563  		return nil, err
   564  	}
   565  	if rootDeviceName == nil {
   566  		// We do this so the value is empty so we don't have to do nil checks later
   567  		var blank string
   568  		rootDeviceName = &blank
   569  	}
   570  	for _, bdm := range lc.BlockDeviceMappings {
   571  		bd := make(map[string]interface{})
   572  		if bdm.Ebs != nil && bdm.Ebs.DeleteOnTermination != nil {
   573  			bd["delete_on_termination"] = *bdm.Ebs.DeleteOnTermination
   574  		}
   575  		if bdm.Ebs != nil && bdm.Ebs.VolumeSize != nil {
   576  			bd["volume_size"] = *bdm.Ebs.VolumeSize
   577  		}
   578  		if bdm.Ebs != nil && bdm.Ebs.VolumeType != nil {
   579  			bd["volume_type"] = *bdm.Ebs.VolumeType
   580  		}
   581  		if bdm.Ebs != nil && bdm.Ebs.Iops != nil {
   582  			bd["iops"] = *bdm.Ebs.Iops
   583  		}
   584  		if bdm.Ebs != nil && bdm.Ebs.Encrypted != nil {
   585  			bd["encrypted"] = *bdm.Ebs.Encrypted
   586  		}
   587  		if bdm.DeviceName != nil && *bdm.DeviceName == *rootDeviceName {
   588  			blockDevices["root"] = bd
   589  		} else {
   590  			if bdm.DeviceName != nil {
   591  				bd["device_name"] = *bdm.DeviceName
   592  			}
   593  			if bdm.VirtualName != nil {
   594  				bd["virtual_name"] = *bdm.VirtualName
   595  				blockDevices["ephemeral"] = append(blockDevices["ephemeral"].([]map[string]interface{}), bd)
   596  			} else {
   597  				if bdm.Ebs != nil && bdm.Ebs.SnapshotId != nil {
   598  					bd["snapshot_id"] = *bdm.Ebs.SnapshotId
   599  				}
   600  				blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd)
   601  			}
   602  		}
   603  	}
   604  	return blockDevices, nil
   605  }