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