github.com/mohanarpit/terraform@v0.6.16-0.20160909104007-291f29853544/builtin/providers/aws/resource_aws_ami.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"log"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/aws/aws-sdk-go/aws"
    12  	"github.com/aws/aws-sdk-go/aws/awserr"
    13  	"github.com/aws/aws-sdk-go/service/ec2"
    14  
    15  	"github.com/hashicorp/terraform/helper/hashcode"
    16  	"github.com/hashicorp/terraform/helper/schema"
    17  )
    18  
    19  func resourceAwsAmi() *schema.Resource {
    20  	// Our schema is shared also with aws_ami_copy and aws_ami_from_instance
    21  	resourceSchema := resourceAwsAmiCommonSchema(false)
    22  
    23  	return &schema.Resource{
    24  		Create: resourceAwsAmiCreate,
    25  
    26  		Schema: resourceSchema,
    27  
    28  		// The Read, Update and Delete operations are shared with aws_ami_copy
    29  		// and aws_ami_from_instance, since they differ only in how the image
    30  		// is created.
    31  		Read:   resourceAwsAmiRead,
    32  		Update: resourceAwsAmiUpdate,
    33  		Delete: resourceAwsAmiDelete,
    34  	}
    35  }
    36  
    37  func resourceAwsAmiCreate(d *schema.ResourceData, meta interface{}) error {
    38  	client := meta.(*AWSClient).ec2conn
    39  
    40  	req := &ec2.RegisterImageInput{
    41  		Name:               aws.String(d.Get("name").(string)),
    42  		Description:        aws.String(d.Get("description").(string)),
    43  		Architecture:       aws.String(d.Get("architecture").(string)),
    44  		ImageLocation:      aws.String(d.Get("image_location").(string)),
    45  		RootDeviceName:     aws.String(d.Get("root_device_name").(string)),
    46  		SriovNetSupport:    aws.String(d.Get("sriov_net_support").(string)),
    47  		VirtualizationType: aws.String(d.Get("virtualization_type").(string)),
    48  	}
    49  
    50  	if kernelId := d.Get("kernel_id").(string); kernelId != "" {
    51  		req.KernelId = aws.String(kernelId)
    52  	}
    53  	if ramdiskId := d.Get("ramdisk_id").(string); ramdiskId != "" {
    54  		req.RamdiskId = aws.String(ramdiskId)
    55  	}
    56  
    57  	ebsBlockDevsSet := d.Get("ebs_block_device").(*schema.Set)
    58  	ephemeralBlockDevsSet := d.Get("ephemeral_block_device").(*schema.Set)
    59  	for _, ebsBlockDevI := range ebsBlockDevsSet.List() {
    60  		ebsBlockDev := ebsBlockDevI.(map[string]interface{})
    61  		blockDev := &ec2.BlockDeviceMapping{
    62  			DeviceName: aws.String(ebsBlockDev["device_name"].(string)),
    63  			Ebs: &ec2.EbsBlockDevice{
    64  				DeleteOnTermination: aws.Bool(ebsBlockDev["delete_on_termination"].(bool)),
    65  				VolumeSize:          aws.Int64(int64(ebsBlockDev["volume_size"].(int))),
    66  				VolumeType:          aws.String(ebsBlockDev["volume_type"].(string)),
    67  			},
    68  		}
    69  		if iops := ebsBlockDev["iops"].(int); iops != 0 {
    70  			blockDev.Ebs.Iops = aws.Int64(int64(iops))
    71  		}
    72  		encrypted := ebsBlockDev["encrypted"].(bool)
    73  		if snapshotId := ebsBlockDev["snapshot_id"].(string); snapshotId != "" {
    74  			blockDev.Ebs.SnapshotId = aws.String(snapshotId)
    75  			if encrypted {
    76  				return errors.New("can't set both 'snapshot_id' and 'encrypted'")
    77  			}
    78  		} else if encrypted {
    79  			blockDev.Ebs.Encrypted = aws.Bool(true)
    80  		}
    81  		req.BlockDeviceMappings = append(req.BlockDeviceMappings, blockDev)
    82  	}
    83  	for _, ephemeralBlockDevI := range ephemeralBlockDevsSet.List() {
    84  		ephemeralBlockDev := ephemeralBlockDevI.(map[string]interface{})
    85  		blockDev := &ec2.BlockDeviceMapping{
    86  			DeviceName:  aws.String(ephemeralBlockDev["device_name"].(string)),
    87  			VirtualName: aws.String(ephemeralBlockDev["virtual_name"].(string)),
    88  		}
    89  		req.BlockDeviceMappings = append(req.BlockDeviceMappings, blockDev)
    90  	}
    91  
    92  	res, err := client.RegisterImage(req)
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	id := *res.ImageId
    98  	d.SetId(id)
    99  	d.Partial(true) // make sure we record the id even if the rest of this gets interrupted
   100  	d.Set("id", id)
   101  	d.Set("manage_ebs_block_devices", false)
   102  	d.SetPartial("id")
   103  	d.SetPartial("manage_ebs_block_devices")
   104  	d.Partial(false)
   105  
   106  	_, err = resourceAwsAmiWaitForAvailable(id, client)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	return resourceAwsAmiUpdate(d, meta)
   112  }
   113  
   114  func resourceAwsAmiRead(d *schema.ResourceData, meta interface{}) error {
   115  	client := meta.(*AWSClient).ec2conn
   116  	id := d.Id()
   117  
   118  	req := &ec2.DescribeImagesInput{
   119  		ImageIds: []*string{aws.String(id)},
   120  	}
   121  
   122  	res, err := client.DescribeImages(req)
   123  	if err != nil {
   124  		return err
   125  	}
   126  
   127  	if len(res.Images) != 1 {
   128  		d.SetId("")
   129  		return nil
   130  	}
   131  
   132  	image := res.Images[0]
   133  	state := *image.State
   134  
   135  	if state == "pending" {
   136  		// This could happen if a user manually adds an image we didn't create
   137  		// to the state. We'll wait for the image to become available
   138  		// before we continue. We should never take this branch in normal
   139  		// circumstances since we would've waited for availability during
   140  		// the "Create" step.
   141  		image, err = resourceAwsAmiWaitForAvailable(id, client)
   142  		if err != nil {
   143  			return err
   144  		}
   145  		state = *image.State
   146  	}
   147  
   148  	if state == "deregistered" {
   149  		d.SetId("")
   150  		return nil
   151  	}
   152  
   153  	if state != "available" {
   154  		return fmt.Errorf("AMI has become %s", state)
   155  	}
   156  
   157  	d.Set("name", image.Name)
   158  	d.Set("description", image.Description)
   159  	d.Set("image_location", image.ImageLocation)
   160  	d.Set("architecture", image.Architecture)
   161  	d.Set("kernel_id", image.KernelId)
   162  	d.Set("ramdisk_id", image.RamdiskId)
   163  	d.Set("root_device_name", image.RootDeviceName)
   164  	d.Set("sriov_net_support", image.SriovNetSupport)
   165  	d.Set("virtualization_type", image.VirtualizationType)
   166  
   167  	var ebsBlockDevs []map[string]interface{}
   168  	var ephemeralBlockDevs []map[string]interface{}
   169  
   170  	for _, blockDev := range image.BlockDeviceMappings {
   171  		if blockDev.Ebs != nil {
   172  			ebsBlockDev := map[string]interface{}{
   173  				"device_name":           *blockDev.DeviceName,
   174  				"delete_on_termination": *blockDev.Ebs.DeleteOnTermination,
   175  				"encrypted":             *blockDev.Ebs.Encrypted,
   176  				"iops":                  0,
   177  				"volume_size":           int(*blockDev.Ebs.VolumeSize),
   178  				"volume_type":           *blockDev.Ebs.VolumeType,
   179  			}
   180  			if blockDev.Ebs.Iops != nil {
   181  				ebsBlockDev["iops"] = int(*blockDev.Ebs.Iops)
   182  			}
   183  			// The snapshot ID might not be set.
   184  			if blockDev.Ebs.SnapshotId != nil {
   185  				ebsBlockDev["snapshot_id"] = *blockDev.Ebs.SnapshotId
   186  			}
   187  			ebsBlockDevs = append(ebsBlockDevs, ebsBlockDev)
   188  		} else {
   189  			ephemeralBlockDevs = append(ephemeralBlockDevs, map[string]interface{}{
   190  				"device_name":  *blockDev.DeviceName,
   191  				"virtual_name": *blockDev.VirtualName,
   192  			})
   193  		}
   194  	}
   195  
   196  	d.Set("ebs_block_device", ebsBlockDevs)
   197  	d.Set("ephemeral_block_device", ephemeralBlockDevs)
   198  
   199  	d.Set("tags", tagsToMap(image.Tags))
   200  
   201  	return nil
   202  }
   203  
   204  func resourceAwsAmiUpdate(d *schema.ResourceData, meta interface{}) error {
   205  	client := meta.(*AWSClient).ec2conn
   206  
   207  	d.Partial(true)
   208  
   209  	if err := setTags(client, d); err != nil {
   210  		return err
   211  	} else {
   212  		d.SetPartial("tags")
   213  	}
   214  
   215  	if d.Get("description").(string) != "" {
   216  		_, err := client.ModifyImageAttribute(&ec2.ModifyImageAttributeInput{
   217  			ImageId: aws.String(d.Id()),
   218  			Description: &ec2.AttributeValue{
   219  				Value: aws.String(d.Get("description").(string)),
   220  			},
   221  		})
   222  		if err != nil {
   223  			return err
   224  		}
   225  		d.SetPartial("description")
   226  	}
   227  
   228  	d.Partial(false)
   229  
   230  	return resourceAwsAmiRead(d, meta)
   231  }
   232  
   233  func resourceAwsAmiDelete(d *schema.ResourceData, meta interface{}) error {
   234  	client := meta.(*AWSClient).ec2conn
   235  
   236  	req := &ec2.DeregisterImageInput{
   237  		ImageId: aws.String(d.Id()),
   238  	}
   239  
   240  	_, err := client.DeregisterImage(req)
   241  	if err != nil {
   242  		return err
   243  	}
   244  
   245  	// If we're managing the EBS snapshots then we need to delete those too.
   246  	if d.Get("manage_ebs_snapshots").(bool) {
   247  		errs := map[string]error{}
   248  		ebsBlockDevsSet := d.Get("ebs_block_device").(*schema.Set)
   249  		req := &ec2.DeleteSnapshotInput{}
   250  		for _, ebsBlockDevI := range ebsBlockDevsSet.List() {
   251  			ebsBlockDev := ebsBlockDevI.(map[string]interface{})
   252  			snapshotId := ebsBlockDev["snapshot_id"].(string)
   253  			if snapshotId != "" {
   254  				req.SnapshotId = aws.String(snapshotId)
   255  				_, err := client.DeleteSnapshot(req)
   256  				if err != nil {
   257  					errs[snapshotId] = err
   258  				}
   259  			}
   260  		}
   261  
   262  		if len(errs) > 0 {
   263  			errParts := []string{"Errors while deleting associated EBS snapshots:"}
   264  			for snapshotId, err := range errs {
   265  				errParts = append(errParts, fmt.Sprintf("%s: %s", snapshotId, err))
   266  			}
   267  			errParts = append(errParts, "These are no longer managed by Terraform and must be deleted manually.")
   268  			return errors.New(strings.Join(errParts, "\n"))
   269  		}
   270  	}
   271  
   272  	d.SetId("")
   273  
   274  	return nil
   275  }
   276  
   277  func resourceAwsAmiWaitForAvailable(id string, client *ec2.EC2) (*ec2.Image, error) {
   278  	log.Printf("Waiting for AMI %s to become available...", id)
   279  
   280  	req := &ec2.DescribeImagesInput{
   281  		ImageIds: []*string{aws.String(id)},
   282  	}
   283  	pollsWhereNotFound := 0
   284  	for {
   285  		res, err := client.DescribeImages(req)
   286  		if err != nil {
   287  			// When using RegisterImage (for aws_ami) the AMI sometimes isn't available at all
   288  			// right after the API responds, so we need to tolerate a couple Not Found errors
   289  			// before an available AMI shows up.
   290  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidAMIID.NotFound" {
   291  				pollsWhereNotFound++
   292  				// We arbitrarily stop polling after getting a "not found" error five times,
   293  				// assuming that the AMI has been deleted by something other than Terraform.
   294  				if pollsWhereNotFound > 5 {
   295  					return nil, fmt.Errorf("gave up waiting for AMI to be created: %s", err)
   296  				}
   297  				time.Sleep(4 * time.Second)
   298  				continue
   299  			}
   300  			return nil, fmt.Errorf("error reading AMI: %s", err)
   301  		}
   302  
   303  		if len(res.Images) != 1 {
   304  			return nil, fmt.Errorf("new AMI vanished while pending")
   305  		}
   306  
   307  		state := *res.Images[0].State
   308  
   309  		if state == "pending" {
   310  			// Give it a few seconds before we poll again.
   311  			time.Sleep(4 * time.Second)
   312  			continue
   313  		}
   314  
   315  		if state == "available" {
   316  			// We're done!
   317  			return res.Images[0], nil
   318  		}
   319  
   320  		// If we're not pending or available then we're in one of the invalid/error
   321  		// states, so stop polling and bail out.
   322  		stateReason := *res.Images[0].StateReason
   323  		return nil, fmt.Errorf("new AMI became %s while pending: %s", state, stateReason)
   324  	}
   325  }
   326  
   327  func resourceAwsAmiCommonSchema(computed bool) map[string]*schema.Schema {
   328  	// The "computed" parameter controls whether we're making
   329  	// a schema for an AMI that's been implicitly registered (aws_ami_copy, aws_ami_from_instance)
   330  	// or whether we're making a schema for an explicit registration (aws_ami).
   331  	// When set, almost every attribute is marked as "computed".
   332  	// When not set, only the "id" attribute is computed.
   333  	// "name" and "description" are never computed, since they must always
   334  	// be provided by the user.
   335  
   336  	var virtualizationTypeDefault interface{}
   337  	var deleteEbsOnTerminationDefault interface{}
   338  	var sriovNetSupportDefault interface{}
   339  	var architectureDefault interface{}
   340  	var volumeTypeDefault interface{}
   341  	if !computed {
   342  		virtualizationTypeDefault = "paravirtual"
   343  		deleteEbsOnTerminationDefault = true
   344  		sriovNetSupportDefault = "simple"
   345  		architectureDefault = "x86_64"
   346  		volumeTypeDefault = "standard"
   347  	}
   348  
   349  	return map[string]*schema.Schema{
   350  		"id": &schema.Schema{
   351  			Type:     schema.TypeString,
   352  			Computed: true,
   353  		},
   354  		"image_location": &schema.Schema{
   355  			Type:     schema.TypeString,
   356  			Optional: !computed,
   357  			Computed: true,
   358  			ForceNew: !computed,
   359  		},
   360  		"architecture": &schema.Schema{
   361  			Type:     schema.TypeString,
   362  			Optional: !computed,
   363  			Computed: computed,
   364  			ForceNew: !computed,
   365  			Default:  architectureDefault,
   366  		},
   367  		"description": &schema.Schema{
   368  			Type:     schema.TypeString,
   369  			Optional: true,
   370  		},
   371  		"kernel_id": &schema.Schema{
   372  			Type:     schema.TypeString,
   373  			Optional: !computed,
   374  			Computed: computed,
   375  			ForceNew: !computed,
   376  		},
   377  		"name": &schema.Schema{
   378  			Type:     schema.TypeString,
   379  			Required: true,
   380  			ForceNew: true,
   381  		},
   382  		"ramdisk_id": &schema.Schema{
   383  			Type:     schema.TypeString,
   384  			Optional: !computed,
   385  			Computed: computed,
   386  			ForceNew: !computed,
   387  		},
   388  		"root_device_name": &schema.Schema{
   389  			Type:     schema.TypeString,
   390  			Optional: !computed,
   391  			Computed: computed,
   392  			ForceNew: !computed,
   393  		},
   394  		"sriov_net_support": &schema.Schema{
   395  			Type:     schema.TypeString,
   396  			Optional: !computed,
   397  			Computed: computed,
   398  			ForceNew: !computed,
   399  			Default:  sriovNetSupportDefault,
   400  		},
   401  		"virtualization_type": &schema.Schema{
   402  			Type:     schema.TypeString,
   403  			Optional: !computed,
   404  			Computed: computed,
   405  			ForceNew: !computed,
   406  			Default:  virtualizationTypeDefault,
   407  		},
   408  
   409  		// The following block device attributes intentionally mimick the
   410  		// corresponding attributes on aws_instance, since they have the
   411  		// same meaning.
   412  		// However, we don't use root_block_device here because the constraint
   413  		// on which root device attributes can be overridden for an instance to
   414  		// not apply when registering an AMI.
   415  
   416  		"ebs_block_device": &schema.Schema{
   417  			Type:     schema.TypeSet,
   418  			Optional: true,
   419  			Computed: true,
   420  			Elem: &schema.Resource{
   421  				Schema: map[string]*schema.Schema{
   422  					"delete_on_termination": &schema.Schema{
   423  						Type:     schema.TypeBool,
   424  						Optional: !computed,
   425  						Default:  deleteEbsOnTerminationDefault,
   426  						ForceNew: !computed,
   427  						Computed: computed,
   428  					},
   429  
   430  					"device_name": &schema.Schema{
   431  						Type:     schema.TypeString,
   432  						Required: !computed,
   433  						ForceNew: !computed,
   434  						Computed: computed,
   435  					},
   436  
   437  					"encrypted": &schema.Schema{
   438  						Type:     schema.TypeBool,
   439  						Optional: !computed,
   440  						Computed: computed,
   441  						ForceNew: !computed,
   442  					},
   443  
   444  					"iops": &schema.Schema{
   445  						Type:     schema.TypeInt,
   446  						Optional: !computed,
   447  						Computed: computed,
   448  						ForceNew: !computed,
   449  					},
   450  
   451  					"snapshot_id": &schema.Schema{
   452  						Type:     schema.TypeString,
   453  						Optional: !computed,
   454  						Computed: computed,
   455  						ForceNew: !computed,
   456  					},
   457  
   458  					"volume_size": &schema.Schema{
   459  						Type:     schema.TypeInt,
   460  						Optional: !computed,
   461  						Computed: true,
   462  						ForceNew: !computed,
   463  					},
   464  
   465  					"volume_type": &schema.Schema{
   466  						Type:     schema.TypeString,
   467  						Optional: !computed,
   468  						Computed: computed,
   469  						ForceNew: !computed,
   470  						Default:  volumeTypeDefault,
   471  					},
   472  				},
   473  			},
   474  			Set: func(v interface{}) int {
   475  				var buf bytes.Buffer
   476  				m := v.(map[string]interface{})
   477  				buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
   478  				buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string)))
   479  				return hashcode.String(buf.String())
   480  			},
   481  		},
   482  
   483  		"ephemeral_block_device": &schema.Schema{
   484  			Type:     schema.TypeSet,
   485  			Optional: true,
   486  			Computed: true,
   487  			ForceNew: true,
   488  			Elem: &schema.Resource{
   489  				Schema: map[string]*schema.Schema{
   490  					"device_name": &schema.Schema{
   491  						Type:     schema.TypeString,
   492  						Required: !computed,
   493  						Computed: computed,
   494  					},
   495  
   496  					"virtual_name": &schema.Schema{
   497  						Type:     schema.TypeString,
   498  						Required: !computed,
   499  						Computed: computed,
   500  					},
   501  				},
   502  			},
   503  			Set: func(v interface{}) int {
   504  				var buf bytes.Buffer
   505  				m := v.(map[string]interface{})
   506  				buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
   507  				buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string)))
   508  				return hashcode.String(buf.String())
   509  			},
   510  		},
   511  
   512  		"tags": tagsSchema(),
   513  
   514  		// Not a public attribute; used to let the aws_ami_copy and aws_ami_from_instance
   515  		// resources record that they implicitly created new EBS snapshots that we should
   516  		// now manage. Not set by aws_ami, since the snapshots used there are presumed to
   517  		// be independently managed.
   518  		"manage_ebs_snapshots": &schema.Schema{
   519  			Type:     schema.TypeBool,
   520  			Computed: true,
   521  			ForceNew: true,
   522  		},
   523  	}
   524  }