terraform/builtin/providers/aws/data_source_aws_ami.go

512 lines
13 KiB
Go

package aws
import (
"bytes"
"fmt"
"log"
"regexp"
"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": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"filter": {
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"values": {
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
},
},
"name_regex": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validateNameRegex,
},
"most_recent": {
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
"owners": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
// Computed values.
"architecture": {
Type: schema.TypeString,
Computed: true,
},
"creation_date": {
Type: schema.TypeString,
Computed: true,
},
"description": {
Type: schema.TypeString,
Computed: true,
},
"hypervisor": {
Type: schema.TypeString,
Computed: true,
},
"image_id": {
Type: schema.TypeString,
Computed: true,
},
"image_location": {
Type: schema.TypeString,
Computed: true,
},
"image_owner_alias": {
Type: schema.TypeString,
Computed: true,
},
"image_type": {
Type: schema.TypeString,
Computed: true,
},
"kernel_id": {
Type: schema.TypeString,
Computed: true,
},
"name": {
Type: schema.TypeString,
Computed: true,
},
"owner_id": {
Type: schema.TypeString,
Computed: true,
},
"platform": {
Type: schema.TypeString,
Computed: true,
},
"public": {
Type: schema.TypeBool,
Computed: true,
},
"ramdisk_id": {
Type: schema.TypeString,
Computed: true,
},
"root_device_name": {
Type: schema.TypeString,
Computed: true,
},
"root_device_type": {
Type: schema.TypeString,
Computed: true,
},
"sriov_net_support": {
Type: schema.TypeString,
Computed: true,
},
"state": {
Type: schema.TypeString,
Computed: true,
},
"virtualization_type": {
Type: schema.TypeString,
Computed: true,
},
// Complex computed values
"block_device_mappings": {
Type: schema.TypeSet,
Computed: true,
Set: amiBlockDeviceMappingHash,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"device_name": {
Type: schema.TypeString,
Computed: true,
},
"no_device": {
Type: schema.TypeString,
Computed: true,
},
"virtual_name": {
Type: schema.TypeString,
Computed: true,
},
"ebs": {
Type: schema.TypeMap,
Computed: true,
},
},
},
},
"product_codes": {
Type: schema.TypeSet,
Computed: true,
Set: amiProductCodesHash,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"product_code_id": {
Type: schema.TypeString,
Computed: true,
},
"product_code_type": {
Type: schema.TypeString,
Computed: true,
},
},
},
},
"state_reason": {
Type: schema.TypeMap,
Computed: true,
},
"tags": {
Type: schema.TypeSet,
Computed: true,
Set: amiTagsHash,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {
Type: schema.TypeString,
Computed: true,
},
"value": {
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")
nameRegex, nameRegexOk := d.GetOk("name_regex")
owners, ownersOk := d.GetOk("owners")
if executableUsersOk == false && filtersOk == false && nameRegexOk == false && ownersOk == false {
return fmt.Errorf("One of executable_users, filters, name_regex, 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 filteredImages []*ec2.Image
if nameRegexOk {
r := regexp.MustCompile(nameRegex.(string))
for _, image := range resp.Images {
// Check for a very rare case where the response would include no
// image name. No name means nothing to attempt a match against,
// therefore we are skipping such image.
if image.Name == nil || *image.Name == "" {
log.Printf("[WARN] Unable to find AMI name to match against "+
"for image ID %q owned by %q, nothing to do.",
*image.ImageId, *image.OwnerId)
continue
}
if r.MatchString(*image.Name) {
filteredImages = append(filteredImages, image)
}
}
} else {
filteredImages = resp.Images[:]
}
var image *ec2.Image
if len(filteredImages) < 1 {
return fmt.Errorf("Your query returned no results. Please change your search criteria and try again.")
}
if len(filteredImages) > 1 {
recent := d.Get("most_recent").(bool)
log.Printf("[DEBUG] aws_ami - multiple results found and `most_recent` is set to: %t", recent)
if recent {
image = mostRecentAmi(filteredImages)
} else {
return fmt.Errorf("Your query returned more than one result. Please try a more " +
"specific search criteria, or set `most_recent` attribute to true.")
}
} else {
// Query returned single result.
image = filteredImages[0]
}
log.Printf("[DEBUG] aws_ami - Single AMI found: %s", *image.ImageId)
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),
"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"
}
// snapshot id may not be set
if v.Ebs.SnapshotId != nil {
ebs["snapshot_id"] = *v.Ebs.SnapshotId
}
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["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)))
}
if d, ok := m["snapshot_id"]; 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())
}
func validateNameRegex(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if _, err := regexp.Compile(value); err != nil {
errors = append(errors, fmt.Errorf(
"%q contains an invalid regular expression: %s",
k, err))
}
return
}