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