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