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