github.com/inge4pres/terraform@v0.7.5-0.20160930053151-bd083f84f376/builtin/providers/aws/data_source_aws_ami.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"regexp"
     8  	"sort"
     9  	"time"
    10  
    11  	"github.com/aws/aws-sdk-go/aws"
    12  	"github.com/aws/aws-sdk-go/service/ec2"
    13  	"github.com/hashicorp/terraform/helper/hashcode"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  )
    16  
    17  func dataSourceAwsAmi() *schema.Resource {
    18  	return &schema.Resource{
    19  		Read: dataSourceAwsAmiRead,
    20  
    21  		Schema: map[string]*schema.Schema{
    22  			"executable_users": &schema.Schema{
    23  				Type:     schema.TypeList,
    24  				Optional: true,
    25  				ForceNew: true,
    26  				Elem:     &schema.Schema{Type: schema.TypeString},
    27  			},
    28  			"filter": &schema.Schema{
    29  				Type:     schema.TypeSet,
    30  				Optional: true,
    31  				ForceNew: true,
    32  				Elem: &schema.Resource{
    33  					Schema: map[string]*schema.Schema{
    34  						"name": &schema.Schema{
    35  							Type:     schema.TypeString,
    36  							Required: true,
    37  						},
    38  
    39  						"values": &schema.Schema{
    40  							Type:     schema.TypeList,
    41  							Required: true,
    42  							Elem:     &schema.Schema{Type: schema.TypeString},
    43  						},
    44  					},
    45  				},
    46  			},
    47  			"name_regex": &schema.Schema{
    48  				Type:     schema.TypeString,
    49  				Optional: true,
    50  				ForceNew: true,
    51  			},
    52  			"most_recent": &schema.Schema{
    53  				Type:     schema.TypeBool,
    54  				Optional: true,
    55  				Default:  false,
    56  				ForceNew: true,
    57  			},
    58  			"owners": &schema.Schema{
    59  				Type:     schema.TypeList,
    60  				Optional: true,
    61  				ForceNew: true,
    62  				Elem:     &schema.Schema{Type: schema.TypeString},
    63  			},
    64  			// Computed values.
    65  			"architecture": &schema.Schema{
    66  				Type:     schema.TypeString,
    67  				Computed: true,
    68  			},
    69  			"creation_date": &schema.Schema{
    70  				Type:     schema.TypeString,
    71  				Computed: true,
    72  			},
    73  			"description": &schema.Schema{
    74  				Type:     schema.TypeString,
    75  				Computed: true,
    76  			},
    77  			"hypervisor": &schema.Schema{
    78  				Type:     schema.TypeString,
    79  				Computed: true,
    80  			},
    81  			"image_id": &schema.Schema{
    82  				Type:     schema.TypeString,
    83  				Computed: true,
    84  			},
    85  			"image_location": &schema.Schema{
    86  				Type:     schema.TypeString,
    87  				Computed: true,
    88  			},
    89  			"image_owner_alias": &schema.Schema{
    90  				Type:     schema.TypeString,
    91  				Computed: true,
    92  			},
    93  			"image_type": &schema.Schema{
    94  				Type:     schema.TypeString,
    95  				Computed: true,
    96  			},
    97  			"kernel_id": &schema.Schema{
    98  				Type:     schema.TypeString,
    99  				Computed: true,
   100  			},
   101  			"name": &schema.Schema{
   102  				Type:     schema.TypeString,
   103  				Computed: true,
   104  			},
   105  			"owner_id": &schema.Schema{
   106  				Type:     schema.TypeString,
   107  				Computed: true,
   108  			},
   109  			"platform": &schema.Schema{
   110  				Type:     schema.TypeString,
   111  				Computed: true,
   112  			},
   113  			"public": &schema.Schema{
   114  				Type:     schema.TypeBool,
   115  				Computed: true,
   116  			},
   117  			"ramdisk_id": &schema.Schema{
   118  				Type:     schema.TypeString,
   119  				Computed: true,
   120  			},
   121  			"root_device_name": &schema.Schema{
   122  				Type:     schema.TypeString,
   123  				Computed: true,
   124  			},
   125  			"root_device_type": &schema.Schema{
   126  				Type:     schema.TypeString,
   127  				Computed: true,
   128  			},
   129  			"sriov_net_support": &schema.Schema{
   130  				Type:     schema.TypeString,
   131  				Computed: true,
   132  			},
   133  			"state": &schema.Schema{
   134  				Type:     schema.TypeString,
   135  				Computed: true,
   136  			},
   137  			"virtualization_type": &schema.Schema{
   138  				Type:     schema.TypeString,
   139  				Computed: true,
   140  			},
   141  			// Complex computed values
   142  			"block_device_mappings": &schema.Schema{
   143  				Type:     schema.TypeSet,
   144  				Computed: true,
   145  				Set:      amiBlockDeviceMappingHash,
   146  				Elem: &schema.Resource{
   147  					Schema: map[string]*schema.Schema{
   148  						"device_name": &schema.Schema{
   149  							Type:     schema.TypeString,
   150  							Computed: true,
   151  						},
   152  						"no_device": &schema.Schema{
   153  							Type:     schema.TypeString,
   154  							Computed: true,
   155  						},
   156  						"virtual_name": &schema.Schema{
   157  							Type:     schema.TypeString,
   158  							Computed: true,
   159  						},
   160  						"ebs": &schema.Schema{
   161  							Type:     schema.TypeMap,
   162  							Computed: true,
   163  						},
   164  					},
   165  				},
   166  			},
   167  			"product_codes": &schema.Schema{
   168  				Type:     schema.TypeSet,
   169  				Computed: true,
   170  				Set:      amiProductCodesHash,
   171  				Elem: &schema.Resource{
   172  					Schema: map[string]*schema.Schema{
   173  						"product_code_id": &schema.Schema{
   174  							Type:     schema.TypeString,
   175  							Computed: true,
   176  						},
   177  						"product_code_type": &schema.Schema{
   178  							Type:     schema.TypeString,
   179  							Computed: true,
   180  						},
   181  					},
   182  				},
   183  			},
   184  			"state_reason": &schema.Schema{
   185  				Type:     schema.TypeMap,
   186  				Computed: true,
   187  			},
   188  			"tags": &schema.Schema{
   189  				Type:     schema.TypeSet,
   190  				Computed: true,
   191  				Set:      amiTagsHash,
   192  				Elem: &schema.Resource{
   193  					Schema: map[string]*schema.Schema{
   194  						"key": &schema.Schema{
   195  							Type:     schema.TypeString,
   196  							Computed: true,
   197  						},
   198  						"value": &schema.Schema{
   199  							Type:     schema.TypeString,
   200  							Computed: true,
   201  						},
   202  					},
   203  				},
   204  			},
   205  		},
   206  	}
   207  }
   208  
   209  // dataSourceAwsAmiDescriptionRead performs the AMI lookup.
   210  func dataSourceAwsAmiRead(d *schema.ResourceData, meta interface{}) error {
   211  	conn := meta.(*AWSClient).ec2conn
   212  
   213  	executableUsers, executableUsersOk := d.GetOk("executable_users")
   214  	filters, filtersOk := d.GetOk("filter")
   215  	nameRegex, nameRegexOk := d.GetOk("name_regex")
   216  	owners, ownersOk := d.GetOk("owners")
   217  
   218  	if executableUsersOk == false && filtersOk == false && nameRegexOk == false && ownersOk == false {
   219  		return fmt.Errorf("One of executable_users, filters, name_regex, or owners must be assigned")
   220  	}
   221  
   222  	params := &ec2.DescribeImagesInput{}
   223  	if executableUsersOk {
   224  		params.ExecutableUsers = expandStringList(executableUsers.([]interface{}))
   225  	}
   226  	if filtersOk {
   227  		params.Filters = buildAmiFilters(filters.(*schema.Set))
   228  	}
   229  	if ownersOk {
   230  		params.Owners = expandStringList(owners.([]interface{}))
   231  	}
   232  
   233  	resp, err := conn.DescribeImages(params)
   234  	if err != nil {
   235  		return err
   236  	}
   237  
   238  	var filteredImages []*ec2.Image
   239  	if nameRegexOk {
   240  		r := regexp.MustCompile(nameRegex.(string))
   241  		for _, image := range resp.Images {
   242  			// Check for a very rare case where the response would include no
   243  			// image name. No name means nothing to attempt a match against,
   244  			// therefore we are skipping such image.
   245  			if image.Name == nil || *image.Name == "" {
   246  				log.Printf("[WARN] Unable to find AMI name to match against "+
   247  					"for image ID %q owned by %q, nothing to do.",
   248  					*image.ImageId, *image.OwnerId)
   249  				continue
   250  			}
   251  			if r.MatchString(*image.Name) {
   252  				filteredImages = append(filteredImages, image)
   253  			}
   254  		}
   255  	} else {
   256  		filteredImages = resp.Images[:]
   257  	}
   258  
   259  	var image *ec2.Image
   260  	if len(filteredImages) < 1 {
   261  		return fmt.Errorf("Your query returned no results. Please change your search criteria and try again.")
   262  	}
   263  
   264  	if len(filteredImages) > 1 {
   265  		recent := d.Get("most_recent").(bool)
   266  		log.Printf("[DEBUG] aws_ami - multiple results found and `most_recent` is set to: %t", recent)
   267  		if recent {
   268  			image = mostRecentAmi(filteredImages)
   269  		} else {
   270  			return fmt.Errorf("Your query returned more than one result. Please try a more " +
   271  				"specific search criteria, or set `most_recent` attribute to true.")
   272  		}
   273  	}
   274  
   275  	image = filteredImages[0]
   276  	log.Printf("[DEBUG] aws_ami - Single AMI found: %s", *image.ImageId)
   277  
   278  	return amiDescriptionAttributes(d, image)
   279  }
   280  
   281  // Build a slice of AMI filter options from the filters provided.
   282  func buildAmiFilters(set *schema.Set) []*ec2.Filter {
   283  	var filters []*ec2.Filter
   284  	for _, v := range set.List() {
   285  		m := v.(map[string]interface{})
   286  		var filterValues []*string
   287  		for _, e := range m["values"].([]interface{}) {
   288  			filterValues = append(filterValues, aws.String(e.(string)))
   289  		}
   290  		filters = append(filters, &ec2.Filter{
   291  			Name:   aws.String(m["name"].(string)),
   292  			Values: filterValues,
   293  		})
   294  	}
   295  	return filters
   296  }
   297  
   298  type imageSort []*ec2.Image
   299  
   300  func (a imageSort) Len() int      { return len(a) }
   301  func (a imageSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
   302  func (a imageSort) Less(i, j int) bool {
   303  	itime, _ := time.Parse(time.RFC3339, *a[i].CreationDate)
   304  	jtime, _ := time.Parse(time.RFC3339, *a[j].CreationDate)
   305  	return itime.Unix() < jtime.Unix()
   306  }
   307  
   308  // Returns the most recent AMI out of a slice of images.
   309  func mostRecentAmi(images []*ec2.Image) *ec2.Image {
   310  	sortedImages := images
   311  	sort.Sort(imageSort(sortedImages))
   312  	return sortedImages[len(sortedImages)-1]
   313  }
   314  
   315  // populate the numerous fields that the image description returns.
   316  func amiDescriptionAttributes(d *schema.ResourceData, image *ec2.Image) error {
   317  	// Simple attributes first
   318  	d.SetId(*image.ImageId)
   319  	d.Set("architecture", image.Architecture)
   320  	d.Set("creation_date", image.CreationDate)
   321  	if image.Description != nil {
   322  		d.Set("description", image.Description)
   323  	}
   324  	d.Set("hypervisor", image.Hypervisor)
   325  	d.Set("image_id", image.ImageId)
   326  	d.Set("image_location", image.ImageLocation)
   327  	if image.ImageOwnerAlias != nil {
   328  		d.Set("image_owner_alias", image.ImageOwnerAlias)
   329  	}
   330  	d.Set("image_type", image.ImageType)
   331  	if image.KernelId != nil {
   332  		d.Set("kernel_id", image.KernelId)
   333  	}
   334  	d.Set("name", image.Name)
   335  	d.Set("owner_id", image.OwnerId)
   336  	if image.Platform != nil {
   337  		d.Set("platform", image.Platform)
   338  	}
   339  	d.Set("public", image.Public)
   340  	if image.RamdiskId != nil {
   341  		d.Set("ramdisk_id", image.RamdiskId)
   342  	}
   343  	if image.RootDeviceName != nil {
   344  		d.Set("root_device_name", image.RootDeviceName)
   345  	}
   346  	d.Set("root_device_type", image.RootDeviceType)
   347  	if image.SriovNetSupport != nil {
   348  		d.Set("sriov_net_support", image.SriovNetSupport)
   349  	}
   350  	d.Set("state", image.State)
   351  	d.Set("virtualization_type", image.VirtualizationType)
   352  	// Complex types get their own functions
   353  	if err := d.Set("block_device_mappings", amiBlockDeviceMappings(image.BlockDeviceMappings)); err != nil {
   354  		return err
   355  	}
   356  	if err := d.Set("product_codes", amiProductCodes(image.ProductCodes)); err != nil {
   357  		return err
   358  	}
   359  	if err := d.Set("state_reason", amiStateReason(image.StateReason)); err != nil {
   360  		return err
   361  	}
   362  	if err := d.Set("tags", amiTags(image.Tags)); err != nil {
   363  		return err
   364  	}
   365  	return nil
   366  }
   367  
   368  // Returns a set of block device mappings.
   369  func amiBlockDeviceMappings(m []*ec2.BlockDeviceMapping) *schema.Set {
   370  	s := &schema.Set{
   371  		F: amiBlockDeviceMappingHash,
   372  	}
   373  	for _, v := range m {
   374  		mapping := map[string]interface{}{
   375  			"device_name": *v.DeviceName,
   376  		}
   377  		if v.Ebs != nil {
   378  			ebs := map[string]interface{}{
   379  				"delete_on_termination": fmt.Sprintf("%t", *v.Ebs.DeleteOnTermination),
   380  				"encrypted":             fmt.Sprintf("%t", *v.Ebs.Encrypted),
   381  				"volume_size":           fmt.Sprintf("%d", *v.Ebs.VolumeSize),
   382  				"volume_type":           *v.Ebs.VolumeType,
   383  			}
   384  			// Iops is not always set
   385  			if v.Ebs.Iops != nil {
   386  				ebs["iops"] = fmt.Sprintf("%d", *v.Ebs.Iops)
   387  			} else {
   388  				ebs["iops"] = "0"
   389  			}
   390  			// snapshot id may not be set
   391  			if v.Ebs.SnapshotId != nil {
   392  				ebs["snapshot_id"] = *v.Ebs.SnapshotId
   393  			}
   394  
   395  			mapping["ebs"] = ebs
   396  		}
   397  		if v.VirtualName != nil {
   398  			mapping["virtual_name"] = *v.VirtualName
   399  		}
   400  		log.Printf("[DEBUG] aws_ami - adding block device mapping: %v", mapping)
   401  		s.Add(mapping)
   402  	}
   403  	return s
   404  }
   405  
   406  // Returns a set of product codes.
   407  func amiProductCodes(m []*ec2.ProductCode) *schema.Set {
   408  	s := &schema.Set{
   409  		F: amiProductCodesHash,
   410  	}
   411  	for _, v := range m {
   412  		code := map[string]interface{}{
   413  			"product_code_id":   *v.ProductCodeId,
   414  			"product_code_type": *v.ProductCodeType,
   415  		}
   416  		s.Add(code)
   417  	}
   418  	return s
   419  }
   420  
   421  // Returns the state reason.
   422  func amiStateReason(m *ec2.StateReason) map[string]interface{} {
   423  	s := make(map[string]interface{})
   424  	if m != nil {
   425  		s["code"] = *m.Code
   426  		s["message"] = *m.Message
   427  	} else {
   428  		s["code"] = "UNSET"
   429  		s["message"] = "UNSET"
   430  	}
   431  	return s
   432  }
   433  
   434  // Returns a set of tags.
   435  func amiTags(m []*ec2.Tag) *schema.Set {
   436  	s := &schema.Set{
   437  		F: amiTagsHash,
   438  	}
   439  	for _, v := range m {
   440  		tag := map[string]interface{}{
   441  			"key":   *v.Key,
   442  			"value": *v.Value,
   443  		}
   444  		s.Add(tag)
   445  	}
   446  	return s
   447  }
   448  
   449  // Generates a hash for the set hash function used by the block_device_mappings
   450  // attribute.
   451  func amiBlockDeviceMappingHash(v interface{}) int {
   452  	var buf bytes.Buffer
   453  	// All keys added in alphabetical order.
   454  	m := v.(map[string]interface{})
   455  	buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
   456  	if d, ok := m["ebs"]; ok {
   457  		if len(d.(map[string]interface{})) > 0 {
   458  			e := d.(map[string]interface{})
   459  			buf.WriteString(fmt.Sprintf("%s-", e["delete_on_termination"].(string)))
   460  			buf.WriteString(fmt.Sprintf("%s-", e["encrypted"].(string)))
   461  			buf.WriteString(fmt.Sprintf("%s-", e["iops"].(string)))
   462  			buf.WriteString(fmt.Sprintf("%s-", e["volume_size"].(string)))
   463  			buf.WriteString(fmt.Sprintf("%s-", e["volume_type"].(string)))
   464  		}
   465  	}
   466  	if d, ok := m["no_device"]; ok {
   467  		buf.WriteString(fmt.Sprintf("%s-", d.(string)))
   468  	}
   469  	if d, ok := m["virtual_name"]; ok {
   470  		buf.WriteString(fmt.Sprintf("%s-", d.(string)))
   471  	}
   472  	if d, ok := m["snapshot_id"]; ok {
   473  		buf.WriteString(fmt.Sprintf("%s-", d.(string)))
   474  	}
   475  	return hashcode.String(buf.String())
   476  }
   477  
   478  // Generates a hash for the set hash function used by the product_codes
   479  // attribute.
   480  func amiProductCodesHash(v interface{}) int {
   481  	var buf bytes.Buffer
   482  	m := v.(map[string]interface{})
   483  	// All keys added in alphabetical order.
   484  	buf.WriteString(fmt.Sprintf("%s-", m["product_code_id"].(string)))
   485  	buf.WriteString(fmt.Sprintf("%s-", m["product_code_type"].(string)))
   486  	return hashcode.String(buf.String())
   487  }
   488  
   489  // Generates a hash for the set hash function used by the tags
   490  // attribute.
   491  func amiTagsHash(v interface{}) int {
   492  	var buf bytes.Buffer
   493  	m := v.(map[string]interface{})
   494  	// All keys added in alphabetical order.
   495  	buf.WriteString(fmt.Sprintf("%s-", m["key"].(string)))
   496  	buf.WriteString(fmt.Sprintf("%s-", m["value"].(string)))
   497  	return hashcode.String(buf.String())
   498  }