Merge pull request #2784 from apparentlymart/aws-images
AWS AMI resources
This commit is contained in:
commit
3abf4796d4
|
@ -156,6 +156,9 @@ func Provider() terraform.ResourceProvider {
|
|||
},
|
||||
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"aws_ami": resourceAwsAmi(),
|
||||
"aws_ami_copy": resourceAwsAmiCopy(),
|
||||
"aws_ami_from_instance": resourceAwsAmiFromInstance(),
|
||||
"aws_app_cookie_stickiness_policy": resourceAwsAppCookieStickinessPolicy(),
|
||||
"aws_autoscaling_group": resourceAwsAutoscalingGroup(),
|
||||
"aws_autoscaling_notification": resourceAwsAutoscalingNotification(),
|
||||
|
|
|
@ -0,0 +1,521 @@
|
|||
package aws
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/hashcode"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func resourceAwsAmi() *schema.Resource {
|
||||
// Our schema is shared also with aws_ami_copy and aws_ami_from_instance
|
||||
resourceSchema := resourceAwsAmiCommonSchema(false)
|
||||
|
||||
return &schema.Resource{
|
||||
Create: resourceAwsAmiCreate,
|
||||
|
||||
Schema: resourceSchema,
|
||||
|
||||
// The Read, Update and Delete operations are shared with aws_ami_copy
|
||||
// and aws_ami_from_instance, since they differ only in how the image
|
||||
// is created.
|
||||
Read: resourceAwsAmiRead,
|
||||
Update: resourceAwsAmiUpdate,
|
||||
Delete: resourceAwsAmiDelete,
|
||||
}
|
||||
}
|
||||
|
||||
func resourceAwsAmiCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*AWSClient).ec2conn
|
||||
|
||||
req := &ec2.RegisterImageInput{
|
||||
Name: aws.String(d.Get("name").(string)),
|
||||
Description: aws.String(d.Get("description").(string)),
|
||||
Architecture: aws.String(d.Get("architecture").(string)),
|
||||
ImageLocation: aws.String(d.Get("image_location").(string)),
|
||||
RootDeviceName: aws.String(d.Get("root_device_name").(string)),
|
||||
SriovNetSupport: aws.String(d.Get("sriov_net_support").(string)),
|
||||
VirtualizationType: aws.String(d.Get("virtualization_type").(string)),
|
||||
}
|
||||
|
||||
if kernelId := d.Get("kernel_id").(string); kernelId != "" {
|
||||
req.KernelId = aws.String(kernelId)
|
||||
}
|
||||
if ramdiskId := d.Get("ramdisk_id").(string); ramdiskId != "" {
|
||||
req.RamdiskId = aws.String(ramdiskId)
|
||||
}
|
||||
|
||||
ebsBlockDevsSet := d.Get("ebs_block_device").(*schema.Set)
|
||||
ephemeralBlockDevsSet := d.Get("ephemeral_block_device").(*schema.Set)
|
||||
for _, ebsBlockDevI := range ebsBlockDevsSet.List() {
|
||||
ebsBlockDev := ebsBlockDevI.(map[string]interface{})
|
||||
blockDev := &ec2.BlockDeviceMapping{
|
||||
DeviceName: aws.String(ebsBlockDev["device_name"].(string)),
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
DeleteOnTermination: aws.Bool(ebsBlockDev["delete_on_termination"].(bool)),
|
||||
VolumeSize: aws.Int64(int64(ebsBlockDev["volume_size"].(int))),
|
||||
VolumeType: aws.String(ebsBlockDev["volume_type"].(string)),
|
||||
},
|
||||
}
|
||||
if iops := ebsBlockDev["iops"].(int); iops != 0 {
|
||||
blockDev.Ebs.Iops = aws.Int64(int64(iops))
|
||||
}
|
||||
encrypted := ebsBlockDev["encrypted"].(bool)
|
||||
if snapshotId := ebsBlockDev["snapshot_id"].(string); snapshotId != "" {
|
||||
blockDev.Ebs.SnapshotId = aws.String(snapshotId)
|
||||
if encrypted {
|
||||
return errors.New("can't set both 'snapshot_id' and 'encrypted'")
|
||||
}
|
||||
} else if encrypted {
|
||||
blockDev.Ebs.Encrypted = aws.Bool(true)
|
||||
}
|
||||
req.BlockDeviceMappings = append(req.BlockDeviceMappings, blockDev)
|
||||
}
|
||||
for _, ephemeralBlockDevI := range ephemeralBlockDevsSet.List() {
|
||||
ephemeralBlockDev := ephemeralBlockDevI.(map[string]interface{})
|
||||
blockDev := &ec2.BlockDeviceMapping{
|
||||
DeviceName: aws.String(ephemeralBlockDev["device_name"].(string)),
|
||||
VirtualName: aws.String(ephemeralBlockDev["virtual_name"].(string)),
|
||||
}
|
||||
req.BlockDeviceMappings = append(req.BlockDeviceMappings, blockDev)
|
||||
}
|
||||
|
||||
res, err := client.RegisterImage(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id := *res.ImageId
|
||||
d.SetId(id)
|
||||
d.Partial(true) // make sure we record the id even if the rest of this gets interrupted
|
||||
d.Set("id", id)
|
||||
d.Set("manage_ebs_block_devices", false)
|
||||
d.SetPartial("id")
|
||||
d.SetPartial("manage_ebs_block_devices")
|
||||
d.Partial(false)
|
||||
|
||||
_, err = resourceAwsAmiWaitForAvailable(id, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return resourceAwsAmiUpdate(d, meta)
|
||||
}
|
||||
|
||||
func resourceAwsAmiRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*AWSClient).ec2conn
|
||||
id := d.Id()
|
||||
|
||||
req := &ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{aws.String(id)},
|
||||
}
|
||||
|
||||
res, err := client.DescribeImages(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(res.Images) != 1 {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
image := res.Images[0]
|
||||
state := *(image.State)
|
||||
|
||||
if state == "pending" {
|
||||
// This could happen if a user manually adds an image we didn't create
|
||||
// to the state. We'll wait for the image to become available
|
||||
// before we continue. We should never take this branch in normal
|
||||
// circumstances since we would've waited for availability during
|
||||
// the "Create" step.
|
||||
image, err = resourceAwsAmiWaitForAvailable(id, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state = *(image.State)
|
||||
}
|
||||
|
||||
if state == "deregistered" {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
if state != "available" {
|
||||
return fmt.Errorf("AMI has become %s", state)
|
||||
}
|
||||
|
||||
d.Set("name", image.Name)
|
||||
d.Set("description", image.Description)
|
||||
d.Set("image_location", image.ImageLocation)
|
||||
d.Set("architecture", image.Architecture)
|
||||
d.Set("kernel_id", image.KernelId)
|
||||
d.Set("ramdisk_id", image.RamdiskId)
|
||||
d.Set("root_device_name", image.RootDeviceName)
|
||||
d.Set("sriov_net_support", image.SriovNetSupport)
|
||||
d.Set("virtualization_type", image.VirtualizationType)
|
||||
|
||||
var ebsBlockDevs []map[string]interface{}
|
||||
var ephemeralBlockDevs []map[string]interface{}
|
||||
|
||||
for _, blockDev := range image.BlockDeviceMappings {
|
||||
if blockDev.Ebs != nil {
|
||||
ebsBlockDev := map[string]interface{}{
|
||||
"device_name": *(blockDev.DeviceName),
|
||||
"delete_on_termination": *(blockDev.Ebs.DeleteOnTermination),
|
||||
"encrypted": *(blockDev.Ebs.Encrypted),
|
||||
"iops": 0,
|
||||
"snapshot_id": *(blockDev.Ebs.SnapshotId),
|
||||
"volume_size": int(*(blockDev.Ebs.VolumeSize)),
|
||||
"volume_type": *(blockDev.Ebs.VolumeType),
|
||||
}
|
||||
if blockDev.Ebs.Iops != nil {
|
||||
ebsBlockDev["iops"] = int(*(blockDev.Ebs.Iops))
|
||||
}
|
||||
ebsBlockDevs = append(ebsBlockDevs, ebsBlockDev)
|
||||
} else {
|
||||
ephemeralBlockDevs = append(ephemeralBlockDevs, map[string]interface{}{
|
||||
"device_name": *(blockDev.DeviceName),
|
||||
"virtual_name": *(blockDev.VirtualName),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
d.Set("ebs_block_device", ebsBlockDevs)
|
||||
d.Set("ephemeral_block_device", ephemeralBlockDevs)
|
||||
|
||||
d.Set("tags", tagsToMap(image.Tags))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceAwsAmiUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*AWSClient).ec2conn
|
||||
|
||||
d.Partial(true)
|
||||
|
||||
if err := setTags(client, d); err != nil {
|
||||
return err
|
||||
} else {
|
||||
d.SetPartial("tags")
|
||||
}
|
||||
|
||||
if d.Get("description").(string) != "" {
|
||||
_, err := client.ModifyImageAttribute(&ec2.ModifyImageAttributeInput{
|
||||
ImageId: aws.String(d.Id()),
|
||||
Description: &ec2.AttributeValue{
|
||||
Value: aws.String(d.Get("description").(string)),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.SetPartial("description")
|
||||
}
|
||||
|
||||
d.Partial(false)
|
||||
|
||||
return resourceAwsAmiRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceAwsAmiDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*AWSClient).ec2conn
|
||||
|
||||
req := &ec2.DeregisterImageInput{
|
||||
ImageId: aws.String(d.Id()),
|
||||
}
|
||||
|
||||
_, err := client.DeregisterImage(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If we're managing the EBS snapshots then we need to delete those too.
|
||||
if d.Get("manage_ebs_snapshots").(bool) {
|
||||
errs := map[string]error{}
|
||||
ebsBlockDevsSet := d.Get("ebs_block_device").(*schema.Set)
|
||||
req := &ec2.DeleteSnapshotInput{}
|
||||
for _, ebsBlockDevI := range ebsBlockDevsSet.List() {
|
||||
ebsBlockDev := ebsBlockDevI.(map[string]interface{})
|
||||
snapshotId := ebsBlockDev["snapshot_id"].(string)
|
||||
if snapshotId != "" {
|
||||
req.SnapshotId = aws.String(snapshotId)
|
||||
_, err := client.DeleteSnapshot(req)
|
||||
if err != nil {
|
||||
errs[snapshotId] = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
errParts := []string{"Errors while deleting associated EBS snapshots:"}
|
||||
for snapshotId, err := range errs {
|
||||
errParts = append(errParts, fmt.Sprintf("%s: %s", snapshotId, err))
|
||||
}
|
||||
errParts = append(errParts, "These are no longer managed by Terraform and must be deleted manually.")
|
||||
return errors.New(strings.Join(errParts, "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId("")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceAwsAmiWaitForAvailable(id string, client *ec2.EC2) (*ec2.Image, error) {
|
||||
log.Printf("Waiting for AMI %s to become available...", id)
|
||||
|
||||
req := &ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{aws.String(id)},
|
||||
}
|
||||
pollsWhereNotFound := 0
|
||||
for {
|
||||
res, err := client.DescribeImages(req)
|
||||
if err != nil {
|
||||
// When using RegisterImage (for aws_ami) the AMI sometimes isn't available at all
|
||||
// right after the API responds, so we need to tolerate a couple Not Found errors
|
||||
// before an available AMI shows up.
|
||||
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidAMIID.NotFound" {
|
||||
pollsWhereNotFound++
|
||||
// We arbitrarily stop polling after getting a "not found" error five times,
|
||||
// assuming that the AMI has been deleted by something other than Terraform.
|
||||
if pollsWhereNotFound > 5 {
|
||||
return nil, fmt.Errorf("gave up waiting for AMI to be created: %s", err)
|
||||
}
|
||||
time.Sleep(4 * time.Second)
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("error reading AMI: %s", err)
|
||||
}
|
||||
|
||||
if len(res.Images) != 1 {
|
||||
return nil, fmt.Errorf("new AMI vanished while pending")
|
||||
}
|
||||
|
||||
state := *(res.Images[0].State)
|
||||
|
||||
if state == "pending" {
|
||||
// Give it a few seconds before we poll again.
|
||||
time.Sleep(4 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
if state == "available" {
|
||||
// We're done!
|
||||
return res.Images[0], nil
|
||||
}
|
||||
|
||||
// If we're not pending or available then we're in one of the invalid/error
|
||||
// states, so stop polling and bail out.
|
||||
stateReason := *(res.Images[0].StateReason)
|
||||
return nil, fmt.Errorf("new AMI became %s while pending: %s", state, stateReason)
|
||||
}
|
||||
}
|
||||
|
||||
func resourceAwsAmiCommonSchema(computed bool) map[string]*schema.Schema {
|
||||
// The "computed" parameter controls whether we're making
|
||||
// a schema for an AMI that's been implicitly registered (aws_ami_copy, aws_ami_from_instance)
|
||||
// or whether we're making a schema for an explicit registration (aws_ami).
|
||||
// When set, almost every attribute is marked as "computed".
|
||||
// When not set, only the "id" attribute is computed.
|
||||
// "name" and "description" are never computed, since they must always
|
||||
// be provided by the user.
|
||||
|
||||
var virtualizationTypeDefault interface{}
|
||||
var deleteEbsOnTerminationDefault interface{}
|
||||
var sriovNetSupportDefault interface{}
|
||||
var architectureDefault interface{}
|
||||
var volumeTypeDefault interface{}
|
||||
if !computed {
|
||||
virtualizationTypeDefault = "paravirtual"
|
||||
deleteEbsOnTerminationDefault = true
|
||||
sriovNetSupportDefault = "simple"
|
||||
architectureDefault = "x86_64"
|
||||
volumeTypeDefault = "standard"
|
||||
}
|
||||
|
||||
return map[string]*schema.Schema{
|
||||
"id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
"image_location": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: true,
|
||||
ForceNew: !computed,
|
||||
},
|
||||
"architecture": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
Default: architectureDefault,
|
||||
},
|
||||
"description": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"kernel_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
},
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"ramdisk_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
},
|
||||
"root_device_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
},
|
||||
"sriov_net_support": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
Default: sriovNetSupportDefault,
|
||||
},
|
||||
"virtualization_type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
Default: virtualizationTypeDefault,
|
||||
},
|
||||
|
||||
// The following block device attributes intentionally mimick the
|
||||
// corresponding attributes on aws_instance, since they have the
|
||||
// same meaning.
|
||||
// However, we don't use root_block_device here because the constraint
|
||||
// on which root device attributes can be overridden for an instance to
|
||||
// not apply when registering an AMI.
|
||||
|
||||
"ebs_block_device": &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"delete_on_termination": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: !computed,
|
||||
Default: deleteEbsOnTerminationDefault,
|
||||
ForceNew: !computed,
|
||||
Computed: computed,
|
||||
},
|
||||
|
||||
"device_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: !computed,
|
||||
ForceNew: !computed,
|
||||
Computed: computed,
|
||||
},
|
||||
|
||||
"encrypted": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
},
|
||||
|
||||
"iops": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
},
|
||||
|
||||
"snapshot_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
},
|
||||
|
||||
"volume_size": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: !computed,
|
||||
Computed: true,
|
||||
ForceNew: !computed,
|
||||
},
|
||||
|
||||
"volume_type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
Default: volumeTypeDefault,
|
||||
},
|
||||
},
|
||||
},
|
||||
Set: func(v interface{}) int {
|
||||
var buf bytes.Buffer
|
||||
m := v.(map[string]interface{})
|
||||
buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
|
||||
buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string)))
|
||||
return hashcode.String(buf.String())
|
||||
},
|
||||
},
|
||||
|
||||
"ephemeral_block_device": &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ForceNew: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"device_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: !computed,
|
||||
Computed: computed,
|
||||
},
|
||||
|
||||
"virtual_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: !computed,
|
||||
Computed: computed,
|
||||
},
|
||||
},
|
||||
},
|
||||
Set: func(v interface{}) int {
|
||||
var buf bytes.Buffer
|
||||
m := v.(map[string]interface{})
|
||||
buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
|
||||
buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string)))
|
||||
return hashcode.String(buf.String())
|
||||
},
|
||||
},
|
||||
|
||||
"tags": tagsSchema(),
|
||||
|
||||
// Not a public attribute; used to let the aws_ami_copy and aws_ami_from_instance
|
||||
// resources record that they implicitly created new EBS snapshots that we should
|
||||
// now manage. Not set by aws_ami, since the snapshots used there are presumed to
|
||||
// be independently managed.
|
||||
"manage_ebs_snapshots": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Computed: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package aws
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func resourceAwsAmiCopy() *schema.Resource {
|
||||
// Inherit all of the common AMI attributes from aws_ami, since we're
|
||||
// implicitly creating an aws_ami resource.
|
||||
resourceSchema := resourceAwsAmiCommonSchema(true)
|
||||
|
||||
// Additional attributes unique to the copy operation.
|
||||
resourceSchema["source_ami_id"] = &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
}
|
||||
resourceSchema["source_ami_region"] = &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
}
|
||||
|
||||
return &schema.Resource{
|
||||
Create: resourceAwsAmiCopyCreate,
|
||||
|
||||
Schema: resourceSchema,
|
||||
|
||||
// The remaining operations are shared with the generic aws_ami resource,
|
||||
// since the aws_ami_copy resource only differs in how it's created.
|
||||
Read: resourceAwsAmiRead,
|
||||
Update: resourceAwsAmiUpdate,
|
||||
Delete: resourceAwsAmiDelete,
|
||||
}
|
||||
}
|
||||
|
||||
func resourceAwsAmiCopyCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*AWSClient).ec2conn
|
||||
|
||||
req := &ec2.CopyImageInput{
|
||||
Name: aws.String(d.Get("name").(string)),
|
||||
Description: aws.String(d.Get("description").(string)),
|
||||
SourceImageId: aws.String(d.Get("source_ami_id").(string)),
|
||||
SourceRegion: aws.String(d.Get("source_ami_region").(string)),
|
||||
}
|
||||
|
||||
res, err := client.CopyImage(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id := *res.ImageId
|
||||
d.SetId(id)
|
||||
d.Partial(true) // make sure we record the id even if the rest of this gets interrupted
|
||||
d.Set("id", id)
|
||||
d.Set("manage_ebs_snapshots", true)
|
||||
d.SetPartial("id")
|
||||
d.SetPartial("manage_ebs_snapshots")
|
||||
d.Partial(false)
|
||||
|
||||
_, err = resourceAwsAmiWaitForAvailable(id, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return resourceAwsAmiUpdate(d, meta)
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
package aws
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccAWSAMICopy(t *testing.T) {
|
||||
var amiId string
|
||||
snapshots := []string{}
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccAWSAMICopyConfig,
|
||||
Check: func(state *terraform.State) error {
|
||||
rs, ok := state.RootModule().Resources["aws_ami_copy.test"]
|
||||
if !ok {
|
||||
return fmt.Errorf("AMI resource not found")
|
||||
}
|
||||
|
||||
amiId = rs.Primary.ID
|
||||
|
||||
if amiId == "" {
|
||||
return fmt.Errorf("AMI id is not set")
|
||||
}
|
||||
|
||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
||||
req := &ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{aws.String(amiId)},
|
||||
}
|
||||
describe, err := conn.DescribeImages(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(describe.Images) != 1 ||
|
||||
*describe.Images[0].ImageId != rs.Primary.ID {
|
||||
return fmt.Errorf("AMI not found")
|
||||
}
|
||||
|
||||
image := describe.Images[0]
|
||||
if expected := "available"; *image.State != expected {
|
||||
return fmt.Errorf("invalid image state; expected %v, got %v", expected, image.State)
|
||||
}
|
||||
if expected := "machine"; *image.ImageType != expected {
|
||||
return fmt.Errorf("wrong image type; expected %v, got %v", expected, image.ImageType)
|
||||
}
|
||||
if expected := "terraform-acc-ami-copy"; *image.Name != expected {
|
||||
return fmt.Errorf("wrong name; expected %v, got %v", expected, image.Name)
|
||||
}
|
||||
|
||||
for _, bdm := range image.BlockDeviceMappings {
|
||||
if bdm.Ebs != nil && bdm.Ebs.SnapshotId != nil {
|
||||
snapshots = append(snapshots, *bdm.Ebs.SnapshotId)
|
||||
}
|
||||
}
|
||||
|
||||
if expected := 1; len(snapshots) != expected {
|
||||
return fmt.Errorf("wrong number of snapshots; expected %v, got %v", expected, len(snapshots))
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
CheckDestroy: func(state *terraform.State) error {
|
||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
||||
diReq := &ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{aws.String(amiId)},
|
||||
}
|
||||
diRes, err := conn.DescribeImages(diReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(diRes.Images) > 0 {
|
||||
state := diRes.Images[0].State
|
||||
return fmt.Errorf("AMI %v remains in state %v", amiId, state)
|
||||
}
|
||||
|
||||
stillExist := make([]string, 0, len(snapshots))
|
||||
checkErrors := make(map[string]error)
|
||||
for _, snapshotId := range snapshots {
|
||||
dsReq := &ec2.DescribeSnapshotsInput{
|
||||
SnapshotIds: []*string{aws.String(snapshotId)},
|
||||
}
|
||||
_, err := conn.DescribeSnapshots(dsReq)
|
||||
if err == nil {
|
||||
stillExist = append(stillExist, snapshotId)
|
||||
continue
|
||||
}
|
||||
|
||||
awsErr, ok := err.(awserr.Error)
|
||||
if !ok {
|
||||
checkErrors[snapshotId] = err
|
||||
continue
|
||||
}
|
||||
|
||||
if awsErr.Code() != "InvalidSnapshot.NotFound" {
|
||||
checkErrors[snapshotId] = err
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(stillExist) > 0 || len(checkErrors) > 0 {
|
||||
errParts := []string{
|
||||
"Expected all snapshots to be gone, but:",
|
||||
}
|
||||
for _, snapshotId := range stillExist {
|
||||
errParts = append(
|
||||
errParts,
|
||||
fmt.Sprintf("- %v still exists", snapshotId),
|
||||
)
|
||||
}
|
||||
for snapshotId, err := range checkErrors {
|
||||
errParts = append(
|
||||
errParts,
|
||||
fmt.Sprintf("- checking %v gave error: %v", snapshotId, err),
|
||||
)
|
||||
}
|
||||
return errors.New(strings.Join(errParts, "\n"))
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
var testAccAWSAMICopyConfig = `
|
||||
provider "aws" {
|
||||
region = "us-east-1"
|
||||
}
|
||||
|
||||
// An AMI can't be directly copied from one account to another, and
|
||||
// we can't rely on any particular AMI being available since anyone
|
||||
// can run this test in whatever account they like.
|
||||
// Therefore we jump through some hoops here:
|
||||
// - Spin up an EC2 instance based on a public AMI
|
||||
// - Create an AMI by snapshotting that EC2 instance, using
|
||||
// aws_ami_from_instance .
|
||||
// - Copy the new AMI using aws_ami_copy .
|
||||
//
|
||||
// Thus this test can only succeed if the aws_ami_from_instance resource
|
||||
// is working. If it's misbehaving it will likely cause this test to fail too.
|
||||
|
||||
// Since we're booting a t2.micro HVM instance we need a VPC for it to boot
|
||||
// up into.
|
||||
|
||||
resource "aws_vpc" "foo" {
|
||||
cidr_block = "10.1.0.0/16"
|
||||
}
|
||||
|
||||
resource "aws_subnet" "foo" {
|
||||
cidr_block = "10.1.1.0/24"
|
||||
vpc_id = "${aws_vpc.foo.id}"
|
||||
}
|
||||
|
||||
resource "aws_instance" "test" {
|
||||
// This AMI has one block device mapping, so we expect to have
|
||||
// one snapshot in our created AMI.
|
||||
// This is an Amazon Linux HVM AMI. A public HVM AMI is required
|
||||
// because paravirtual images cannot be copied between accounts.
|
||||
ami = "ami-8fff43e4"
|
||||
instance_type = "t2.micro"
|
||||
tags {
|
||||
Name = "terraform-acc-ami-copy-victim"
|
||||
}
|
||||
|
||||
subnet_id = "${aws_subnet.foo.id}"
|
||||
}
|
||||
|
||||
resource "aws_ami_from_instance" "test" {
|
||||
name = "terraform-acc-ami-copy-victim"
|
||||
description = "Testing Terraform aws_ami_from_instance resource"
|
||||
source_instance_id = "${aws_instance.test.id}"
|
||||
}
|
||||
|
||||
resource "aws_ami_copy" "test" {
|
||||
name = "terraform-acc-ami-copy"
|
||||
description = "Testing Terraform aws_ami_copy resource"
|
||||
source_ami_id = "${aws_ami_from_instance.test.id}"
|
||||
source_ami_region = "us-east-1"
|
||||
}
|
||||
`
|
|
@ -0,0 +1,70 @@
|
|||
package aws
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func resourceAwsAmiFromInstance() *schema.Resource {
|
||||
// Inherit all of the common AMI attributes from aws_ami, since we're
|
||||
// implicitly creating an aws_ami resource.
|
||||
resourceSchema := resourceAwsAmiCommonSchema(true)
|
||||
|
||||
// Additional attributes unique to the copy operation.
|
||||
resourceSchema["source_instance_id"] = &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
}
|
||||
resourceSchema["snapshot_without_reboot"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
}
|
||||
|
||||
return &schema.Resource{
|
||||
Create: resourceAwsAmiFromInstanceCreate,
|
||||
|
||||
Schema: resourceSchema,
|
||||
|
||||
// The remaining operations are shared with the generic aws_ami resource,
|
||||
// since the aws_ami_copy resource only differs in how it's created.
|
||||
Read: resourceAwsAmiRead,
|
||||
Update: resourceAwsAmiUpdate,
|
||||
Delete: resourceAwsAmiDelete,
|
||||
}
|
||||
}
|
||||
|
||||
func resourceAwsAmiFromInstanceCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*AWSClient).ec2conn
|
||||
|
||||
req := &ec2.CreateImageInput{
|
||||
Name: aws.String(d.Get("name").(string)),
|
||||
Description: aws.String(d.Get("description").(string)),
|
||||
InstanceId: aws.String(d.Get("source_instance_id").(string)),
|
||||
NoReboot: aws.Bool(d.Get("snapshot_without_reboot").(bool)),
|
||||
}
|
||||
|
||||
res, err := client.CreateImage(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id := *res.ImageId
|
||||
d.SetId(id)
|
||||
d.Partial(true) // make sure we record the id even if the rest of this gets interrupted
|
||||
d.Set("id", id)
|
||||
d.Set("manage_ebs_snapshots", true)
|
||||
d.SetPartial("id")
|
||||
d.SetPartial("manage_ebs_snapshots")
|
||||
d.Partial(false)
|
||||
|
||||
_, err = resourceAwsAmiWaitForAvailable(id, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return resourceAwsAmiUpdate(d, meta)
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
package aws
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccAWSAMIFromInstance(t *testing.T) {
|
||||
var amiId string
|
||||
snapshots := []string{}
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccAWSAMIFromInstanceConfig,
|
||||
Check: func(state *terraform.State) error {
|
||||
rs, ok := state.RootModule().Resources["aws_ami_from_instance.test"]
|
||||
if !ok {
|
||||
return fmt.Errorf("AMI resource not found")
|
||||
}
|
||||
|
||||
amiId = rs.Primary.ID
|
||||
|
||||
if amiId == "" {
|
||||
return fmt.Errorf("AMI id is not set")
|
||||
}
|
||||
|
||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
||||
req := &ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{aws.String(amiId)},
|
||||
}
|
||||
describe, err := conn.DescribeImages(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(describe.Images) != 1 ||
|
||||
*describe.Images[0].ImageId != rs.Primary.ID {
|
||||
return fmt.Errorf("AMI not found")
|
||||
}
|
||||
|
||||
image := describe.Images[0]
|
||||
if expected := "available"; *image.State != expected {
|
||||
return fmt.Errorf("invalid image state; expected %v, got %v", expected, image.State)
|
||||
}
|
||||
if expected := "machine"; *image.ImageType != expected {
|
||||
return fmt.Errorf("wrong image type; expected %v, got %v", expected, image.ImageType)
|
||||
}
|
||||
if expected := "terraform-acc-ami-from-instance"; *image.Name != expected {
|
||||
return fmt.Errorf("wrong name; expected %v, got %v", expected, image.Name)
|
||||
}
|
||||
|
||||
for _, bdm := range image.BlockDeviceMappings {
|
||||
if bdm.Ebs != nil && bdm.Ebs.SnapshotId != nil {
|
||||
snapshots = append(snapshots, *bdm.Ebs.SnapshotId)
|
||||
}
|
||||
}
|
||||
|
||||
if expected := 1; len(snapshots) != expected {
|
||||
return fmt.Errorf("wrong number of snapshots; expected %v, got %v", expected, len(snapshots))
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
CheckDestroy: func(state *terraform.State) error {
|
||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
||||
diReq := &ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{aws.String(amiId)},
|
||||
}
|
||||
diRes, err := conn.DescribeImages(diReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(diRes.Images) > 0 {
|
||||
state := diRes.Images[0].State
|
||||
return fmt.Errorf("AMI %v remains in state %v", amiId, state)
|
||||
}
|
||||
|
||||
stillExist := make([]string, 0, len(snapshots))
|
||||
checkErrors := make(map[string]error)
|
||||
for _, snapshotId := range snapshots {
|
||||
dsReq := &ec2.DescribeSnapshotsInput{
|
||||
SnapshotIds: []*string{aws.String(snapshotId)},
|
||||
}
|
||||
_, err := conn.DescribeSnapshots(dsReq)
|
||||
if err == nil {
|
||||
stillExist = append(stillExist, snapshotId)
|
||||
continue
|
||||
}
|
||||
|
||||
awsErr, ok := err.(awserr.Error)
|
||||
if !ok {
|
||||
checkErrors[snapshotId] = err
|
||||
continue
|
||||
}
|
||||
|
||||
if awsErr.Code() != "InvalidSnapshot.NotFound" {
|
||||
checkErrors[snapshotId] = err
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(stillExist) > 0 || len(checkErrors) > 0 {
|
||||
errParts := []string{
|
||||
"Expected all snapshots to be gone, but:",
|
||||
}
|
||||
for _, snapshotId := range stillExist {
|
||||
errParts = append(
|
||||
errParts,
|
||||
fmt.Sprintf("- %v still exists", snapshotId),
|
||||
)
|
||||
}
|
||||
for snapshotId, err := range checkErrors {
|
||||
errParts = append(
|
||||
errParts,
|
||||
fmt.Sprintf("- checking %v gave error: %v", snapshotId, err),
|
||||
)
|
||||
}
|
||||
return errors.New(strings.Join(errParts, "\n"))
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
var testAccAWSAMIFromInstanceConfig = `
|
||||
provider "aws" {
|
||||
region = "us-east-1"
|
||||
}
|
||||
|
||||
resource "aws_instance" "test" {
|
||||
// This AMI has one block device mapping, so we expect to have
|
||||
// one snapshot in our created AMI.
|
||||
ami = "ami-408c7f28"
|
||||
instance_type = "t1.micro"
|
||||
}
|
||||
|
||||
resource "aws_ami_from_instance" "test" {
|
||||
name = "terraform-acc-ami-from-instance"
|
||||
description = "Testing Terraform aws_ami_from_instance resource"
|
||||
source_instance_id = "${aws_instance.test.id}"
|
||||
}
|
||||
`
|
|
@ -0,0 +1,8 @@
|
|||
package aws
|
||||
|
||||
// FIXME: The aws_ami resource doesn't currently have any acceptance tests,
|
||||
// since creating an AMI requires having an EBS snapshot and we don't yet
|
||||
// have a resource type for creating those.
|
||||
// Once there is an aws_ebs_snapshot resource we can use it to implement
|
||||
// a reasonable acceptance test for aws_ami. Until then it's necessary to
|
||||
// test manually using a pre-existing EBS snapshot.
|
|
@ -0,0 +1,92 @@
|
|||
---
|
||||
layout: "aws"
|
||||
page_title: "AWS: aws_ami"
|
||||
sidebar_current: "docs-aws-resource-ami"
|
||||
description: |-
|
||||
Creates and manages a custom Amazon Machine Image (AMI).
|
||||
---
|
||||
|
||||
# aws\_ami
|
||||
|
||||
The AMI resource allows the creation and management of a completely-custom
|
||||
*Amazon Machine Image* (AMI).
|
||||
|
||||
If you just want to duplicate an existing AMI, possibly copying it to another
|
||||
region, it's better to use `aws_ami_copy` instead.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
# Create an AMI that will start a machine whose root device is backed by
|
||||
# an EBS volume populated from a snapshot. It is assumed that such a snapshot
|
||||
# already exists with the id "snap-xxxxxxxx".
|
||||
resource "aws_ami" "example" {
|
||||
name = "terraform-example"
|
||||
virtualization_type = "hvm"
|
||||
root_device_name = "/dev/xvda"
|
||||
|
||||
ebs_block_device {
|
||||
device_name = "/dev/xvda"
|
||||
snapshot_id = "snap-xxxxxxxx"
|
||||
volume_size = 8
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `name` - (Required) A region-unique name for the AMI.
|
||||
* `description` - (Optional) A longer, human-readable description for the AMI.
|
||||
* `virtualization_type` - (Optional) Keyword to choose what virtualization mode created instances
|
||||
will use. Can be either "paravirtual" (the default) or "hvm". The choice of virtualization type
|
||||
changes the set of further arguments that are required, as described below.
|
||||
* `architecture` - (Optional) Machine architecture for created instances. Defaults to "x86_64".
|
||||
* `ebs_block_device` - (Optional) Nested block describing an EBS block device that should be
|
||||
attached to created instances. The structure of this block is described below.
|
||||
* `ephemeral_block_device` - (Optional) Nested block describing an ephemeral block device that
|
||||
should be attached to created instances. The structure of this block is described below.
|
||||
|
||||
When `virtualization_type` is "paravirtual" the following additional arguments apply:
|
||||
|
||||
* `image_location` - (Required) Path to an S3 object containing an image manifest, e.g. created
|
||||
by the `ec2-upload-bundle` command in the EC2 command line tools.
|
||||
* `kernel_id` - (Required) The id of the kernel image (AKI) that will be used as the paravirtual
|
||||
kernel in created instances.
|
||||
* `ramdisk_id` - (Optional) The id of an initrd image (ARI) that will be used when booting the
|
||||
created instances.
|
||||
|
||||
When `virtualization_type` is "hvm" the following additional arguments apply:
|
||||
|
||||
* `sriov_net_support` - (Optional) When set to "simple" (the default), enables enhanced networking
|
||||
for created instances. No other value is supported at this time.
|
||||
|
||||
Nested `ebs_block_device` blocks have the following structure:
|
||||
|
||||
* `device_name` - (Required) The path at which the device is exposed to created instances.
|
||||
* `delete_on_termination` - (Optional) Boolean controlling whether the EBS volumes created to
|
||||
support each created instance will be deleted once that instance is terminated.
|
||||
* `encrypted` - (Optional) Boolean controlling whether the created EBS volumes will be encrypted.
|
||||
* `iops` - (Required only when `volume_type` is "io1") Number of I/O operations per second the
|
||||
created volumes will support.
|
||||
* `snapshot_id` - (Optional) The id of an EBS snapshot that will be used to initialize the created
|
||||
EBS volumes. If set, the `volume_size` attribute must be at least as large as the referenced
|
||||
snapshot.
|
||||
* `volume_size` - (Required unless `snapshot_id` is set) The size of created volumes in GiB.
|
||||
If `snapshot_id` is set and `volume_size` is omitted then the volume will have the same size
|
||||
as the selected snapshot.
|
||||
* `volume_type` - (Optional) The type of EBS volume to create. Can be one of "standard" (the
|
||||
default), "io1" or "gp2".
|
||||
|
||||
Nested `ephemeral_block_device` blocks have the following structure:
|
||||
|
||||
* `device_name` - (Required) The path at which the device is exposed to created instances.
|
||||
* `virtual_name` - (Required) A name for the ephemeral device, of the form "ephemeralN" where
|
||||
*N* is a volume number starting from zero.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - The ID of the created AMI.
|
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
layout: "aws"
|
||||
page_title: "AWS: aws_ami_copy"
|
||||
sidebar_current: "docs-aws-resource-ami-copy"
|
||||
description: |-
|
||||
Duplicates an existing Amazon Machine Image (AMI)
|
||||
---
|
||||
|
||||
# aws\_ami\_copy
|
||||
|
||||
The "AMI copy" resource allows duplication of an Amazon Machine Image (AMI),
|
||||
including cross-region copies.
|
||||
|
||||
If the source AMI has associated EBS snapshots, those will also be duplicated
|
||||
along with the AMI.
|
||||
|
||||
This is useful for taking a single AMI provisioned in one region and making
|
||||
it available in another for a multi-region deployment.
|
||||
|
||||
Copying an AMI can take several minutes. The creation of this resource will
|
||||
block until the new AMI is available for use on new instances.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "aws_ami_copy" "example" {
|
||||
name = "terraform-example"
|
||||
source_ami_id = "ami-xxxxxxxx"
|
||||
source_ami_region = "us-west-1"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `name` - (Required) A region-unique name for the AMI.
|
||||
* `source_ami_id` - (Required) The id of the AMI to copy. This id must be valid in the region
|
||||
given by `source_ami_region`.
|
||||
* `source_region` - (Required) The region from which the AMI will be copied. This may be the
|
||||
same as the AWS provider region in order to create a copy within the same region.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - The ID of the created AMI.
|
||||
|
||||
This resource also exports a full set of attributes corresponding to the arguments of the
|
||||
`aws_ami` resource, allowing the properties of the created AMI to be used elsewhere in the
|
||||
configuration.
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
layout: "aws"
|
||||
page_title: "AWS: aws_ami_from_instance"
|
||||
sidebar_current: "docs-aws-resource-ami-from-instance"
|
||||
description: |-
|
||||
Creates an Amazon Machine Image (AMI) from an EBS-backed EC2 instance
|
||||
---
|
||||
|
||||
# aws\_ami\_from\_instance
|
||||
|
||||
The "AMI from instance" resource allows the creation of an Amazon Machine
|
||||
Image (AMI) modelled after an existing EBS-backed EC2 instance.
|
||||
|
||||
The created AMI will refer to implicitly-created snapshots of the instance's
|
||||
EBS volumes and mimick its assigned block device configuration at the time
|
||||
the resource is created.
|
||||
|
||||
This resource is best applied to an instance that is stopped when this instance
|
||||
is created, so that the contents of the created image are predictable. When
|
||||
applied to an instance that is running, *the instance will be stopped before taking
|
||||
the snapshots and then started back up again*, resulting in a period of
|
||||
downtime.
|
||||
|
||||
Note that the source instance is inspected only at the initial creation of this
|
||||
resource. Ongoing updates to the referenced instance will not be propagated into
|
||||
the generated AMI. Users may taint or otherwise recreate the resource in order
|
||||
to produce a fresh snapshot.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "aws_ami_from_instance" "example" {
|
||||
name = "terraform-example"
|
||||
source_instance_id = "i-xxxxxxxx"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `name` - (Required) A region-unique name for the AMI.
|
||||
* `source_instance_id` - (Required) The id of the instance to use as the basis of the AMI.
|
||||
* `snapshot_without_reboot` - (Optional) Boolean that overrides the behavior of stopping
|
||||
the instance before snapshotting. This is risky since it may cause a snapshot of an
|
||||
inconsistent filesystem state, but can be used to avoid downtime if the user otherwise
|
||||
guarantees that no filesystem writes will be underway at the time of snapshot.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - The ID of the created AMI.
|
||||
|
||||
This resource also exports a full set of attributes corresponding to the arguments of the
|
||||
`aws_ami` resource, allowing the properties of the created AMI to be used elsewhere in the
|
||||
configuration.
|
|
@ -39,6 +39,18 @@
|
|||
<a href="#">EC2 Resources</a>
|
||||
<ul class="nav nav-visible">
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource-ami") %>>
|
||||
<a href="/docs/providers/aws/r/ami.html">aws_ami</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource-ami-copy") %>>
|
||||
<a href="/docs/providers/aws/r/ami_copy.html">aws_ami_copy</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource-ami-from-instance") %>>
|
||||
<a href="/docs/providers/aws/r/ami_from_instance.html">aws_ami_from_instance</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource-app-cookie-stickiness-policy") %>>
|
||||
<a href="/docs/providers/aws/r/app_cookie_stickiness_policy.html">aws_app_cookie_stickiness_policy</a>
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue