github.com/tkak/terraform@v0.5.4-0.20150712180941-7f738dc27225/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  				Computed: true,
   122  				ForceNew: 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.Boolean(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  	if v, ok := d.GetOk("enable_monitoring"); ok {
   276  		createLaunchConfigurationOpts.InstanceMonitoring = &autoscaling.InstanceMonitoring{
   277  			Enabled: aws.Boolean(v.(bool)),
   278  		}
   279  	}
   280  
   281  	if v, ok := d.GetOk("iam_instance_profile"); ok {
   282  		createLaunchConfigurationOpts.IAMInstanceProfile = aws.String(v.(string))
   283  	}
   284  
   285  	if v, ok := d.GetOk("placement_tenancy"); ok {
   286  		createLaunchConfigurationOpts.PlacementTenancy = aws.String(v.(string))
   287  	}
   288  
   289  	if v, ok := d.GetOk("associate_public_ip_address"); ok {
   290  		createLaunchConfigurationOpts.AssociatePublicIPAddress = aws.Boolean(v.(bool))
   291  	}
   292  
   293  	if v, ok := d.GetOk("key_name"); ok {
   294  		createLaunchConfigurationOpts.KeyName = aws.String(v.(string))
   295  	}
   296  	if v, ok := d.GetOk("spot_price"); ok {
   297  		createLaunchConfigurationOpts.SpotPrice = aws.String(v.(string))
   298  	}
   299  
   300  	if v, ok := d.GetOk("security_groups"); ok {
   301  		createLaunchConfigurationOpts.SecurityGroups = expandStringList(
   302  			v.(*schema.Set).List(),
   303  		)
   304  	}
   305  
   306  	var blockDevices []*autoscaling.BlockDeviceMapping
   307  
   308  	if v, ok := d.GetOk("ebs_block_device"); ok {
   309  		vL := v.(*schema.Set).List()
   310  		for _, v := range vL {
   311  			bd := v.(map[string]interface{})
   312  			ebs := &autoscaling.EBS{
   313  				DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
   314  			}
   315  
   316  			if v, ok := bd["snapshot_id"].(string); ok && v != "" {
   317  				ebs.SnapshotID = aws.String(v)
   318  			}
   319  
   320  			if v, ok := bd["volume_size"].(int); ok && v != 0 {
   321  				ebs.VolumeSize = aws.Long(int64(v))
   322  			}
   323  
   324  			if v, ok := bd["volume_type"].(string); ok && v != "" {
   325  				ebs.VolumeType = aws.String(v)
   326  			}
   327  
   328  			if v, ok := bd["iops"].(int); ok && v > 0 {
   329  				ebs.IOPS = aws.Long(int64(v))
   330  			}
   331  
   332  			blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{
   333  				DeviceName: aws.String(bd["device_name"].(string)),
   334  				EBS:        ebs,
   335  			})
   336  		}
   337  	}
   338  
   339  	if v, ok := d.GetOk("ephemeral_block_device"); ok {
   340  		vL := v.(*schema.Set).List()
   341  		for _, v := range vL {
   342  			bd := v.(map[string]interface{})
   343  			blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{
   344  				DeviceName:  aws.String(bd["device_name"].(string)),
   345  				VirtualName: aws.String(bd["virtual_name"].(string)),
   346  			})
   347  		}
   348  	}
   349  
   350  	if v, ok := d.GetOk("root_block_device"); ok {
   351  		vL := v.(*schema.Set).List()
   352  		if len(vL) > 1 {
   353  			return fmt.Errorf("Cannot specify more than one root_block_device.")
   354  		}
   355  		for _, v := range vL {
   356  			bd := v.(map[string]interface{})
   357  			ebs := &autoscaling.EBS{
   358  				DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
   359  			}
   360  
   361  			if v, ok := bd["volume_size"].(int); ok && v != 0 {
   362  				ebs.VolumeSize = aws.Long(int64(v))
   363  			}
   364  
   365  			if v, ok := bd["volume_type"].(string); ok && v != "" {
   366  				ebs.VolumeType = aws.String(v)
   367  			}
   368  
   369  			if v, ok := bd["iops"].(int); ok && v > 0 {
   370  				ebs.IOPS = aws.Long(int64(v))
   371  			}
   372  
   373  			if dn, err := fetchRootDeviceName(d.Get("image_id").(string), ec2conn); err == nil {
   374  				blockDevices = append(blockDevices, &autoscaling.BlockDeviceMapping{
   375  					DeviceName: dn,
   376  					EBS:        ebs,
   377  				})
   378  			} else {
   379  				return err
   380  			}
   381  		}
   382  	}
   383  
   384  	if len(blockDevices) > 0 {
   385  		createLaunchConfigurationOpts.BlockDeviceMappings = blockDevices
   386  	}
   387  
   388  	var lcName string
   389  	if v, ok := d.GetOk("name"); ok {
   390  		lcName = v.(string)
   391  	} else {
   392  		lcName = resource.UniqueId()
   393  	}
   394  	createLaunchConfigurationOpts.LaunchConfigurationName = aws.String(lcName)
   395  
   396  	log.Printf(
   397  		"[DEBUG] autoscaling create launch configuration: %#v", createLaunchConfigurationOpts)
   398  
   399  	// IAM profiles can take ~10 seconds to propagate in AWS:
   400  	// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console
   401  	err := resource.Retry(30*time.Second, func() error {
   402  		_, err := autoscalingconn.CreateLaunchConfiguration(&createLaunchConfigurationOpts)
   403  		if err != nil {
   404  			if awsErr, ok := err.(awserr.Error); ok {
   405  				if awsErr.Message() == "Invalid IamInstanceProfile" {
   406  					return err
   407  				}
   408  			}
   409  			return &resource.RetryError{
   410  				Err: err,
   411  			}
   412  		}
   413  		return nil
   414  	})
   415  
   416  	if err != nil {
   417  		return fmt.Errorf("Error creating launch configuration: %s", err)
   418  	}
   419  
   420  	d.SetId(lcName)
   421  	log.Printf("[INFO] launch configuration ID: %s", d.Id())
   422  
   423  	// We put a Retry here since sometimes eventual consistency bites
   424  	// us and we need to retry a few times to get the LC to load properly
   425  	return resource.Retry(30*time.Second, func() error {
   426  		return resourceAwsLaunchConfigurationRead(d, meta)
   427  	})
   428  }
   429  
   430  func resourceAwsLaunchConfigurationRead(d *schema.ResourceData, meta interface{}) error {
   431  	autoscalingconn := meta.(*AWSClient).autoscalingconn
   432  	ec2conn := meta.(*AWSClient).ec2conn
   433  
   434  	describeOpts := autoscaling.DescribeLaunchConfigurationsInput{
   435  		LaunchConfigurationNames: []*string{aws.String(d.Id())},
   436  	}
   437  
   438  	log.Printf("[DEBUG] launch configuration describe configuration: %#v", describeOpts)
   439  	describConfs, err := autoscalingconn.DescribeLaunchConfigurations(&describeOpts)
   440  	if err != nil {
   441  		return fmt.Errorf("Error retrieving launch configuration: %s", err)
   442  	}
   443  	if len(describConfs.LaunchConfigurations) == 0 {
   444  		d.SetId("")
   445  		return nil
   446  	}
   447  
   448  	// Verify AWS returned our launch configuration
   449  	if *describConfs.LaunchConfigurations[0].LaunchConfigurationName != d.Id() {
   450  		return fmt.Errorf(
   451  			"Unable to find launch configuration: %#v",
   452  			describConfs.LaunchConfigurations)
   453  	}
   454  
   455  	lc := describConfs.LaunchConfigurations[0]
   456  
   457  	d.Set("key_name", lc.KeyName)
   458  	d.Set("image_id", lc.ImageID)
   459  	d.Set("instance_type", lc.InstanceType)
   460  	d.Set("name", lc.LaunchConfigurationName)
   461  
   462  	d.Set("iam_instance_profile", lc.IAMInstanceProfile)
   463  	d.Set("ebs_optimized", lc.EBSOptimized)
   464  	d.Set("spot_price", lc.SpotPrice)
   465  	d.Set("enable_monitoring", lc.InstanceMonitoring.Enabled)
   466  	d.Set("security_groups", lc.SecurityGroups)
   467  
   468  	if err := readLCBlockDevices(d, lc, ec2conn); err != nil {
   469  		return err
   470  	}
   471  
   472  	return nil
   473  }
   474  
   475  func resourceAwsLaunchConfigurationDelete(d *schema.ResourceData, meta interface{}) error {
   476  	autoscalingconn := meta.(*AWSClient).autoscalingconn
   477  
   478  	log.Printf("[DEBUG] Launch Configuration destroy: %v", d.Id())
   479  	_, err := autoscalingconn.DeleteLaunchConfiguration(
   480  		&autoscaling.DeleteLaunchConfigurationInput{
   481  			LaunchConfigurationName: aws.String(d.Id()),
   482  		})
   483  	if err != nil {
   484  		autoscalingerr, ok := err.(awserr.Error)
   485  		if ok && autoscalingerr.Code() == "InvalidConfiguration.NotFound" {
   486  			return nil
   487  		}
   488  
   489  		return err
   490  	}
   491  
   492  	return nil
   493  }
   494  
   495  func readLCBlockDevices(d *schema.ResourceData, lc *autoscaling.LaunchConfiguration, ec2conn *ec2.EC2) error {
   496  	ibds, err := readBlockDevicesFromLaunchConfiguration(d, lc, ec2conn)
   497  	if err != nil {
   498  		return err
   499  	}
   500  
   501  	if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil {
   502  		return err
   503  	}
   504  	if err := d.Set("ephemeral_block_device", ibds["ephemeral"]); err != nil {
   505  		return err
   506  	}
   507  	if ibds["root"] != nil {
   508  		if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil {
   509  			return err
   510  		}
   511  	} else {
   512  		d.Set("root_block_device", []interface{}{})
   513  	}
   514  
   515  	return nil
   516  }
   517  
   518  func readBlockDevicesFromLaunchConfiguration(d *schema.ResourceData, lc *autoscaling.LaunchConfiguration, ec2conn *ec2.EC2) (
   519  	map[string]interface{}, error) {
   520  	blockDevices := make(map[string]interface{})
   521  	blockDevices["ebs"] = make([]map[string]interface{}, 0)
   522  	blockDevices["ephemeral"] = make([]map[string]interface{}, 0)
   523  	blockDevices["root"] = nil
   524  	if len(lc.BlockDeviceMappings) == 0 {
   525  		return nil, nil
   526  	}
   527  	rootDeviceName, err := fetchRootDeviceName(d.Get("image_id").(string), ec2conn)
   528  	if err != nil {
   529  		return nil, err
   530  	}
   531  	if rootDeviceName == nil {
   532  		// We do this so the value is empty so we don't have to do nil checks later
   533  		var blank string
   534  		rootDeviceName = &blank
   535  	}
   536  	for _, bdm := range lc.BlockDeviceMappings {
   537  		bd := make(map[string]interface{})
   538  		if bdm.EBS != nil && bdm.EBS.DeleteOnTermination != nil {
   539  			bd["delete_on_termination"] = *bdm.EBS.DeleteOnTermination
   540  		}
   541  		if bdm.EBS != nil && bdm.EBS.VolumeSize != nil {
   542  			bd["volume_size"] = *bdm.EBS.VolumeSize
   543  		}
   544  		if bdm.EBS != nil && bdm.EBS.VolumeType != nil {
   545  			bd["volume_type"] = *bdm.EBS.VolumeType
   546  		}
   547  		if bdm.EBS != nil && bdm.EBS.IOPS != nil {
   548  			bd["iops"] = *bdm.EBS.IOPS
   549  		}
   550  		if bdm.DeviceName != nil && *bdm.DeviceName == *rootDeviceName {
   551  			blockDevices["root"] = bd
   552  		} else {
   553  			if bdm.DeviceName != nil {
   554  				bd["device_name"] = *bdm.DeviceName
   555  			}
   556  			if bdm.VirtualName != nil {
   557  				bd["virtual_name"] = *bdm.VirtualName
   558  				blockDevices["ephemeral"] = append(blockDevices["ephemeral"].([]map[string]interface{}), bd)
   559  			} else {
   560  				if bdm.EBS != nil && bdm.EBS.SnapshotID != nil {
   561  					bd["snapshot_id"] = *bdm.EBS.SnapshotID
   562  				}
   563  				blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd)
   564  			}
   565  		}
   566  	}
   567  	return blockDevices, nil
   568  }