From 9ac7fb027602f4ada54b26c988f86b8087a97e13 Mon Sep 17 00:00:00 2001 From: Chris Marchesi Date: Fri, 18 Dec 2015 15:52:42 -0800 Subject: [PATCH] provider/aws: New data source: aws_ami This data source allows one to look up the most recent AMI for a specific set of parameters, much like aws ec2 describe-images in the AWS CLI. Basically a refresh of hashicorp/terraform#4396, in data source form. --- builtin/providers/aws/data_source_aws_ami.go | 459 ++++++++++++++++++ .../providers/aws/data_source_aws_ami_test.go | 247 ++++++++++ builtin/providers/aws/provider.go | 1 + .../docs/providers/aws/d/ami.html.markdown | 111 +++++ website/source/layouts/aws.erb | 3 + 5 files changed, 821 insertions(+) create mode 100644 builtin/providers/aws/data_source_aws_ami.go create mode 100644 builtin/providers/aws/data_source_aws_ami_test.go create mode 100644 website/source/docs/providers/aws/d/ami.html.markdown diff --git a/builtin/providers/aws/data_source_aws_ami.go b/builtin/providers/aws/data_source_aws_ami.go new file mode 100644 index 000000000000..d3f088c8fea1 --- /dev/null +++ b/builtin/providers/aws/data_source_aws_ami.go @@ -0,0 +1,459 @@ +package aws + +import ( + "bytes" + "fmt" + "log" + "sort" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceAwsAmi() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsAmiRead, + + Schema: map[string]*schema.Schema{ + "executable_users": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "filter": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "values": &schema.Schema{ + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + "most_recent": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + }, + "owners": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + // Computed values. + "architecture": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "creation_date": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "hypervisor": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "image_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "image_location": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "image_owner_alias": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "image_type": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "kernel_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "owner_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "platform": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "public": &schema.Schema{ + Type: schema.TypeBool, + Computed: true, + }, + "ramdisk_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "root_device_name": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "root_device_type": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "sriov_net_support": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "state": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "virtualization_type": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + // Complex computed values + "block_device_mappings": &schema.Schema{ + Type: schema.TypeSet, + Computed: true, + Set: amiBlockDeviceMappingHash, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "device_name": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "no_device": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "virtual_name": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "ebs": &schema.Schema{ + Type: schema.TypeMap, + Computed: true, + }, + }, + }, + }, + "product_codes": &schema.Schema{ + Type: schema.TypeSet, + Computed: true, + Set: amiProductCodesHash, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "product_code_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "product_code_type": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "state_reason": &schema.Schema{ + Type: schema.TypeMap, + Computed: true, + }, + "tags": &schema.Schema{ + Type: schema.TypeSet, + Computed: true, + Set: amiTagsHash, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +// dataSourceAwsAmiDescriptionRead performs the AMI lookup. +func dataSourceAwsAmiRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + executableUsers, executableUsersOk := d.GetOk("executable_users") + filters, filtersOk := d.GetOk("filter") + owners, ownersOk := d.GetOk("owners") + + if executableUsersOk == false && filtersOk == false && ownersOk == false { + return fmt.Errorf("One of executable_users, filters, or owners must be assigned") + } + + params := &ec2.DescribeImagesInput{} + if executableUsersOk { + params.ExecutableUsers = expandStringList(executableUsers.([]interface{})) + } + if filtersOk { + params.Filters = buildAmiFilters(filters.(*schema.Set)) + } + if ownersOk { + params.Owners = expandStringList(owners.([]interface{})) + } + + resp, err := conn.DescribeImages(params) + if err != nil { + return err + } + var image *ec2.Image + if len(resp.Images) < 1 { + return fmt.Errorf("Your query returned no results. Please change your filters and try again.") + } else if len(resp.Images) > 1 { + if (d.Get("most_recent").(bool)) == true { + log.Printf("[DEBUG] aws_ami - multiple results found and most_recent is set") + image = mostRecentAmi(resp.Images) + } else { + log.Printf("[DEBUG] aws_ami - multiple results found and most_recent not set") + return fmt.Errorf("Your query returned more than one result. Please try a more specific search, or set most_recent to true.") + } + } else { + log.Printf("[DEBUG] aws_ami - Single AMI found: %s", *resp.Images[0].ImageId) + image = resp.Images[0] + } + return amiDescriptionAttributes(d, image) +} + +// Build a slice of AMI filter options from the filters provided. +func buildAmiFilters(set *schema.Set) []*ec2.Filter { + var filters []*ec2.Filter + for _, v := range set.List() { + m := v.(map[string]interface{}) + var filterValues []*string + for _, e := range m["values"].([]interface{}) { + filterValues = append(filterValues, aws.String(e.(string))) + } + filters = append(filters, &ec2.Filter{ + Name: aws.String(m["name"].(string)), + Values: filterValues, + }) + } + return filters +} + +type imageSort []*ec2.Image + +func (a imageSort) Len() int { return len(a) } +func (a imageSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a imageSort) Less(i, j int) bool { + itime, _ := time.Parse(time.RFC3339, *a[i].CreationDate) + jtime, _ := time.Parse(time.RFC3339, *a[j].CreationDate) + return itime.Unix() < jtime.Unix() +} + +// Returns the most recent AMI out of a slice of images. +func mostRecentAmi(images []*ec2.Image) *ec2.Image { + sortedImages := images + sort.Sort(imageSort(sortedImages)) + return sortedImages[len(sortedImages)-1] +} + +// populate the numerous fields that the image description returns. +func amiDescriptionAttributes(d *schema.ResourceData, image *ec2.Image) error { + // Simple attributes first + d.SetId(*image.ImageId) + d.Set("architecture", *image.Architecture) + d.Set("creation_date", *image.CreationDate) + if image.Description != nil { + d.Set("description", *image.Description) + } + d.Set("hypervisor", *image.Hypervisor) + d.Set("image_id", *image.ImageId) + d.Set("image_location", *image.ImageLocation) + if image.ImageOwnerAlias != nil { + d.Set("image_owner_alias", *image.ImageOwnerAlias) + } + d.Set("image_type", *image.ImageType) + if image.KernelId != nil { + d.Set("kernel_id", *image.KernelId) + } + d.Set("name", *image.Name) + d.Set("owner_id", *image.OwnerId) + if image.Platform != nil { + d.Set("platform", *image.Platform) + } + d.Set("public", *image.Public) + if image.RamdiskId != nil { + d.Set("ramdisk_id", *image.RamdiskId) + } + if image.RootDeviceName != nil { + d.Set("root_device_name", *image.RootDeviceName) + } + d.Set("root_device_type", *image.RootDeviceType) + if image.SriovNetSupport != nil { + d.Set("sriov_net_support", *image.SriovNetSupport) + } + d.Set("state", *image.State) + d.Set("virtualization_type", *image.VirtualizationType) + // Complex types get their own functions + if err := d.Set("block_device_mappings", amiBlockDeviceMappings(image.BlockDeviceMappings)); err != nil { + return err + } + if err := d.Set("product_codes", amiProductCodes(image.ProductCodes)); err != nil { + return err + } + if err := d.Set("state_reason", amiStateReason(image.StateReason)); err != nil { + return err + } + if err := d.Set("tags", amiTags(image.Tags)); err != nil { + return err + } + return nil +} + +// Returns a set of block device mappings. +func amiBlockDeviceMappings(m []*ec2.BlockDeviceMapping) *schema.Set { + s := &schema.Set{ + F: amiBlockDeviceMappingHash, + } + for _, v := range m { + mapping := map[string]interface{}{ + "device_name": *v.DeviceName, + } + if v.Ebs != nil { + ebs := map[string]interface{}{ + "delete_on_termination": fmt.Sprintf("%t", *v.Ebs.DeleteOnTermination), + "encrypted": fmt.Sprintf("%t", *v.Ebs.Encrypted), + "snapshot_id": *v.Ebs.SnapshotId, + "volume_size": fmt.Sprintf("%d", *v.Ebs.VolumeSize), + "volume_type": *v.Ebs.VolumeType, + } + // Iops is not always set + if v.Ebs.Iops != nil { + ebs["iops"] = fmt.Sprintf("%d", *v.Ebs.Iops) + } else { + ebs["iops"] = "0" + } + mapping["ebs"] = ebs + } + if v.VirtualName != nil { + mapping["virtual_name"] = *v.VirtualName + } + log.Printf("[DEBUG] aws_ami - adding block device mapping: %v", mapping) + s.Add(mapping) + } + return s +} + +// Returns a set of product codes. +func amiProductCodes(m []*ec2.ProductCode) *schema.Set { + s := &schema.Set{ + F: amiProductCodesHash, + } + for _, v := range m { + code := map[string]interface{}{ + "product_code_id": *v.ProductCodeId, + "product_code_type": *v.ProductCodeType, + } + s.Add(code) + } + return s +} + +// Returns the state reason. +func amiStateReason(m *ec2.StateReason) map[string]interface{} { + s := make(map[string]interface{}) + if m != nil { + s["code"] = *m.Code + s["message"] = *m.Message + } else { + s["code"] = "UNSET" + s["message"] = "UNSET" + } + return s +} + +// Returns a set of tags. +func amiTags(m []*ec2.Tag) *schema.Set { + s := &schema.Set{ + F: amiTagsHash, + } + for _, v := range m { + tag := map[string]interface{}{ + "key": *v.Key, + "value": *v.Value, + } + s.Add(tag) + } + return s +} + +// Generates a hash for the set hash function used by the block_device_mappings +// attribute. +func amiBlockDeviceMappingHash(v interface{}) int { + var buf bytes.Buffer + // All keys added in alphabetical order. + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) + if d, ok := m["ebs"]; ok { + if len(d.(map[string]interface{})) > 0 { + e := d.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", e["delete_on_termination"].(string))) + buf.WriteString(fmt.Sprintf("%s-", e["encrypted"].(string))) + buf.WriteString(fmt.Sprintf("%s-", e["iops"].(string))) + buf.WriteString(fmt.Sprintf("%s-", e["snapshot_id"].(string))) + buf.WriteString(fmt.Sprintf("%s-", e["volume_size"].(string))) + buf.WriteString(fmt.Sprintf("%s-", e["volume_type"].(string))) + } + } + if d, ok := m["no_device"]; ok { + buf.WriteString(fmt.Sprintf("%s-", d.(string))) + } + if d, ok := m["virtual_name"]; ok { + buf.WriteString(fmt.Sprintf("%s-", d.(string))) + } + return hashcode.String(buf.String()) +} + +// Generates a hash for the set hash function used by the product_codes +// attribute. +func amiProductCodesHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + // All keys added in alphabetical order. + buf.WriteString(fmt.Sprintf("%s-", m["product_code_id"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["product_code_type"].(string))) + return hashcode.String(buf.String()) +} + +// Generates a hash for the set hash function used by the tags +// attribute. +func amiTagsHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + // All keys added in alphabetical order. + buf.WriteString(fmt.Sprintf("%s-", m["key"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["value"].(string))) + return hashcode.String(buf.String()) +} diff --git a/builtin/providers/aws/data_source_aws_ami_test.go b/builtin/providers/aws/data_source_aws_ami_test.go new file mode 100644 index 000000000000..2f8eba3eb448 --- /dev/null +++ b/builtin/providers/aws/data_source_aws_ami_test.go @@ -0,0 +1,247 @@ +package aws + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSAmiDataSource_natInstance(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckAwsAmiDataSourceConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAmiDataSourceID("data.aws_ami.nat_ami"), + // Check attributes. Some attributes are tough to test - any not contained here should not be considered + // stable and should not be used in interpolation. Exception to block_device_mappings which should both + // show up consistently and break if certain references are not available. However modification of the + // snapshot ID which is bound to happen on the NAT AMIs will cause testing to break consistently, so + // deep inspection is not included, simply the count is checked. + // Tags and product codes may need more testing, but I'm having a hard time finding images with + // these attributes set. + resource.TestCheckResourceAttr("data.aws_ami.nat_ami", "architecture", "x86_64"), + resource.TestCheckResourceAttr("data.aws_ami.nat_ami", "block_device_mappings.#", "1"), + resource.TestMatchResourceAttr("data.aws_ami.nat_ami", "creation_date", regexp.MustCompile("^20[0-9]{2}-")), + resource.TestMatchResourceAttr("data.aws_ami.nat_ami", "description", regexp.MustCompile("^Amazon Linux AMI")), + resource.TestCheckResourceAttr("data.aws_ami.nat_ami", "hypervisor", "xen"), + resource.TestMatchResourceAttr("data.aws_ami.nat_ami", "image_id", regexp.MustCompile("^ami-")), + resource.TestMatchResourceAttr("data.aws_ami.nat_ami", "image_location", regexp.MustCompile("^amazon/")), + resource.TestCheckResourceAttr("data.aws_ami.nat_ami", "image_owner_alias", "amazon"), + resource.TestCheckResourceAttr("data.aws_ami.nat_ami", "image_type", "machine"), + resource.TestCheckResourceAttr("data.aws_ami.nat_ami", "most_recent", "true"), + resource.TestMatchResourceAttr("data.aws_ami.nat_ami", "name", regexp.MustCompile("^amzn-ami-vpc-nat")), + resource.TestCheckResourceAttr("data.aws_ami.nat_ami", "owner_id", "137112412989"), + resource.TestCheckResourceAttr("data.aws_ami.nat_ami", "public", "true"), + resource.TestCheckResourceAttr("data.aws_ami.nat_ami", "product_codes.#", "0"), + resource.TestCheckResourceAttr("data.aws_ami.nat_ami", "root_device_name", "/dev/xvda"), + resource.TestCheckResourceAttr("data.aws_ami.nat_ami", "root_device_type", "ebs"), + resource.TestCheckResourceAttr("data.aws_ami.nat_ami", "sriov_net_support", "simple"), + resource.TestCheckResourceAttr("data.aws_ami.nat_ami", "state", "available"), + resource.TestCheckResourceAttr("data.aws_ami.nat_ami", "state_reason.code", "UNSET"), + resource.TestCheckResourceAttr("data.aws_ami.nat_ami", "state_reason.message", "UNSET"), + resource.TestCheckResourceAttr("data.aws_ami.nat_ami", "tags.#", "0"), + resource.TestCheckResourceAttr("data.aws_ami.nat_ami", "virtualization_type", "hvm"), + ), + }, + }, + }) +} +func TestAccAWSAmiDataSource_windowsInstance(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckAwsAmiDataSourceWindowsConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAmiDataSourceID("data.aws_ami.windows_ami"), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "architecture", "x86_64"), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "block_device_mappings.#", "27"), + resource.TestMatchResourceAttr("data.aws_ami.windows_ami", "creation_date", regexp.MustCompile("^20[0-9]{2}-")), + resource.TestMatchResourceAttr("data.aws_ami.windows_ami", "description", regexp.MustCompile("^Microsoft Windows Server 2012")), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "hypervisor", "xen"), + resource.TestMatchResourceAttr("data.aws_ami.windows_ami", "image_id", regexp.MustCompile("^ami-")), + resource.TestMatchResourceAttr("data.aws_ami.windows_ami", "image_location", regexp.MustCompile("^amazon/")), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "image_owner_alias", "amazon"), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "image_type", "machine"), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "most_recent", "true"), + resource.TestMatchResourceAttr("data.aws_ami.windows_ami", "name", regexp.MustCompile("^Windows_Server-2012-R2")), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "owner_id", "801119661308"), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "platform", "windows"), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "public", "true"), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "product_codes.#", "0"), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "root_device_name", "/dev/sda1"), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "root_device_type", "ebs"), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "sriov_net_support", "simple"), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "state", "available"), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "state_reason.code", "UNSET"), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "state_reason.message", "UNSET"), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "tags.#", "0"), + resource.TestCheckResourceAttr("data.aws_ami.windows_ami", "virtualization_type", "hvm"), + ), + }, + }, + }) +} + +func TestAccAWSAmiDataSource_instanceStore(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckAwsAmiDataSourceInstanceStoreConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAmiDataSourceID("data.aws_ami.instance_store_ami"), + resource.TestCheckResourceAttr("data.aws_ami.instance_store_ami", "architecture", "x86_64"), + resource.TestCheckResourceAttr("data.aws_ami.instance_store_ami", "block_device_mappings.#", "0"), + resource.TestMatchResourceAttr("data.aws_ami.instance_store_ami", "creation_date", regexp.MustCompile("^20[0-9]{2}-")), + resource.TestCheckResourceAttr("data.aws_ami.instance_store_ami", "hypervisor", "xen"), + resource.TestMatchResourceAttr("data.aws_ami.instance_store_ami", "image_id", regexp.MustCompile("^ami-")), + resource.TestMatchResourceAttr("data.aws_ami.instance_store_ami", "image_location", regexp.MustCompile("images/hvm-instance/ubuntu-trusty-14.04-amd64-server")), + resource.TestCheckResourceAttr("data.aws_ami.instance_store_ami", "image_type", "machine"), + resource.TestCheckResourceAttr("data.aws_ami.instance_store_ami", "most_recent", "true"), + resource.TestMatchResourceAttr("data.aws_ami.instance_store_ami", "name", regexp.MustCompile("^ubuntu/images/hvm-instance/ubuntu-trusty-14.04-amd64-server")), + resource.TestCheckResourceAttr("data.aws_ami.instance_store_ami", "owner_id", "099720109477"), + resource.TestCheckResourceAttr("data.aws_ami.instance_store_ami", "public", "true"), + resource.TestCheckResourceAttr("data.aws_ami.instance_store_ami", "product_codes.#", "0"), + resource.TestCheckResourceAttr("data.aws_ami.instance_store_ami", "root_device_type", "instance-store"), + resource.TestCheckResourceAttr("data.aws_ami.instance_store_ami", "sriov_net_support", "simple"), + resource.TestCheckResourceAttr("data.aws_ami.instance_store_ami", "state", "available"), + resource.TestCheckResourceAttr("data.aws_ami.instance_store_ami", "state_reason.code", "UNSET"), + resource.TestCheckResourceAttr("data.aws_ami.instance_store_ami", "state_reason.message", "UNSET"), + resource.TestCheckResourceAttr("data.aws_ami.instance_store_ami", "tags.#", "0"), + resource.TestCheckResourceAttr("data.aws_ami.instance_store_ami", "virtualization_type", "hvm"), + ), + }, + }, + }) +} + +func TestAccAWSAmiDataSource_owners(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckAwsAmiDataSourceOwnersConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAmiDataSourceID("data.aws_ami.amazon_ami"), + ), + }, + }, + }) +} + +func testAccCheckAwsAmiDataSourceDestroy(s *terraform.State) error { + return nil +} + +func testAccCheckAwsAmiDataSourceID(n string) resource.TestCheckFunc { + // Wait for IAM role + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Can't find AMI data source: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("AMI data source ID not set") + } + return nil + } +} + +// Using NAT AMIs for testing - I would expect with NAT gateways now a thing, +// that this will possibly be deprecated at some point in time. Other candidates +// for testing this after that may be Ubuntu's AMI's, or Amazon's regular +// Amazon Linux AMIs. +const testAccCheckAwsAmiDataSourceConfig = ` +data "aws_ami" "nat_ami" { + most_recent = true + filter { + name = "owner-alias" + values = ["amazon"] + } + filter { + name = "name" + values = ["amzn-ami-vpc-nat*"] + } + filter { + name = "virtualization-type" + values = ["hvm"] + } + filter { + name = "root-device-type" + values = ["ebs"] + } + filter { + name = "block-device-mapping.volume-type" + values = ["standard"] + } +} +` + +// Windows image test. +const testAccCheckAwsAmiDataSourceWindowsConfig = ` +data "aws_ami" "windows_ami" { + most_recent = true + filter { + name = "owner-alias" + values = ["amazon"] + } + filter { + name = "name" + values = ["Windows_Server-2012-R2*"] + } + filter { + name = "virtualization-type" + values = ["hvm"] + } + filter { + name = "root-device-type" + values = ["ebs"] + } + filter { + name = "block-device-mapping.volume-type" + values = ["gp2"] + } +} +` + +// Instance store test - using Ubuntu images +const testAccCheckAwsAmiDataSourceInstanceStoreConfig = ` +data "aws_ami" "instance_store_ami" { + most_recent = true + filter { + name = "owner-id" + values = ["099720109477"] + } + filter { + name = "name" + values = ["ubuntu/images/hvm-instance/ubuntu-trusty-14.04-amd64-server*"] + } + filter { + name = "virtualization-type" + values = ["hvm"] + } + filter { + name = "root-device-type" + values = ["instance-store"] + } +} +` + +// Testing owner parameter +const testAccCheckAwsAmiDataSourceOwnersConfig = ` +data "aws_ami" "amazon_ami" { + most_recent = true + owners = ["amazon"] +} +` diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index d80f18c5a86b..9f7f557973ad 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -111,6 +111,7 @@ func Provider() terraform.ResourceProvider { }, DataSourcesMap: map[string]*schema.Resource{ + "aws_ami": dataSourceAwsAmi(), "aws_availability_zones": dataSourceAwsAvailabilityZones(), }, diff --git a/website/source/docs/providers/aws/d/ami.html.markdown b/website/source/docs/providers/aws/d/ami.html.markdown new file mode 100644 index 000000000000..b405f6b783b6 --- /dev/null +++ b/website/source/docs/providers/aws/d/ami.html.markdown @@ -0,0 +1,111 @@ +--- +layout: "aws" +page_title: "AWS: aws_ami" +sidebar_current: "docs-aws-datasource-ami" +description: |- + Get information on a Amazon Machine Image (AMI). +--- + +# aws\_ami + +Use this data source to get the ID of a registered AMI for use in other +resources. + +## Example Usage + +``` +data "aws_ami" "nat_ami" { + most_recent = true + executable_users = ["self"] + filter { + name = "owner-alias" + values = ["amazon"] + } + filter { + name = "name" + values = ["amzn-ami-vpc-nat*"] + } + owners = ["self"] +} +``` + +## Argument Reference + + * `most_recent` (optional): If more than one result is returned, use the most +recent AMI. + + * `executable_users`: Limit search to users with *explicit* launch permission on + the image. Valid items are the numeric account ID or `self`. + +* `filter`: One or more name/value pairs to filter off of. There are +several valid keys, for a full reference, check out +[describe-images in the AWS CLI reference][1]. + +* `owners`: Limit search to specific AMI owners. Valid items are the numeric +account ID, `amazon`, or `self`. + +~> **NOTE:** one of `executable_users`, `filter`, or `owners` must be specified. + +~> **NOTE:** if more or less than a single match is returned by the search, +Terraform will fail. Ensure that your search is specific enough to return +a single AMI ID only, or use `most_recent` to choose the most recent one. + +## Attributes Reference + +`id` is set to the ID of the found AMI. In addition, the following attributes +are exported: + +~> **NOTE:** some values are not always set and may not be available for +interpolation. + +* `architecture` - The OS architecture of the AMI (ie: `i368` or `x86_64`). +* `block_device_mappings` - The block device mappings of the AMI. + * `block_device_mappings.#.device_name` - The physical name of the device. + * `block_device_mappings.#.ebs.delete_on_termination` - `true` if the EBS volume + will be deleted on termination. + * `block_device_mappings.#.ebs.encrypted` - `true` if the EBS volume + is encrypted. + * `block_device_mappings.#.ebs.encrypted` - `0` if the EBS volume + not a provisioned IOPS image, otherwise the supported IOPS count. + * `block_device_mappings.#.ebs.snapshot_id` - The ID of the snapshot. + * `block_device_mappings.#.ebs.volume_size` - The size of the volume, in GiB. + * `block_device_mappings.#.ebs.volume_type` - The volume type. + * `block_device_mappings.#.no_device` - Suppresses the specified device + included in the block device mapping of the AMI. + * `block_device_mappings.#.virtual_name` - The virtual device name (for + instance stores). +* `creation_date` - The date and time the image was created. +* `description` - The description of the AMI that was provided during image + creation. +* `hypervisor` - The hypervisor type of the image. +* `image_id` - The ID of the AMI. Should be the same as the resource `id`. +* `image_location` - The location of the AMI. +* `image_owner_alias` - The AWS account alias (for example, `amazon`, `self`) or + the AWS account ID of the AMI owner. +* `image_type` - The type of image. +* `kernel_id` - The kernel associated with the image, if any. Only applicable + for machine images. +* `name` - The name of the AMI that was provided during image creation. +* `owner_id` - The AWS account ID of the image owner. +* `platform` - The value is Windows for `Windows` AMIs; otherwise blank. +* `product_codes` - Any product codes associated with the AMI. + * `product_codes.#.product_code_id` - The product code. + * `product_codes.#.product_code_type` - The type of product code. +* `public` - `true` if the image has public launch permissions. +* `ramdisk_id` - The RAM disk associated with the image, if any. Only applicable + for machine images. +* `root_device_name` - The device name of the root device. +* `root_device_type` - The type of root device (ie: `ebs` or `instance-store`). +* `sriov_net_support` - Specifies whether enhanced networking is enabled. +* `state` - The current state of the AMI. If the state is `available`, the image + is successfully registered and can be used to launch an instance. +* `state_reason` - Describes a state change. Fields are `UNSET` if not available. + * `state_reason.code` - The reason code for the state change. + * `state_reason.message` - The message for the state change. +* `tags` - Any tags assigned to the image. + * `tags.#.key` - The key name of the tag. + * `tags.#.value` - The value of the tag. +* `virtualization_type` - The type of virtualization of the AMI (ie: `hvm` or + `paravirtual`). + +[1]: http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-images.html diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 66a73a0bcb00..a4c24890758c 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -13,6 +13,9 @@ > Data Sources