github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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  		o := expandStringList(owners.([]interface{}))
   197  
   198  		if len(o) > 0 {
   199  			params.Owners = o
   200  		}
   201  	}
   202  
   203  	resp, err := conn.DescribeImages(params)
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	var filteredImages []*ec2.Image
   209  	if nameRegexOk {
   210  		r := regexp.MustCompile(nameRegex.(string))
   211  		for _, image := range resp.Images {
   212  			// Check for a very rare case where the response would include no
   213  			// image name. No name means nothing to attempt a match against,
   214  			// therefore we are skipping such image.
   215  			if image.Name == nil || *image.Name == "" {
   216  				log.Printf("[WARN] Unable to find AMI name to match against "+
   217  					"for image ID %q owned by %q, nothing to do.",
   218  					*image.ImageId, *image.OwnerId)
   219  				continue
   220  			}
   221  			if r.MatchString(*image.Name) {
   222  				filteredImages = append(filteredImages, image)
   223  			}
   224  		}
   225  	} else {
   226  		filteredImages = resp.Images[:]
   227  	}
   228  
   229  	var image *ec2.Image
   230  	if len(filteredImages) < 1 {
   231  		return fmt.Errorf("Your query returned no results. Please change your search criteria and try again.")
   232  	}
   233  
   234  	if len(filteredImages) > 1 {
   235  		recent := d.Get("most_recent").(bool)
   236  		log.Printf("[DEBUG] aws_ami - multiple results found and `most_recent` is set to: %t", recent)
   237  		if recent {
   238  			image = mostRecentAmi(filteredImages)
   239  		} else {
   240  			return fmt.Errorf("Your query returned more than one result. Please try a more " +
   241  				"specific search criteria, or set `most_recent` attribute to true.")
   242  		}
   243  	} else {
   244  		// Query returned single result.
   245  		image = filteredImages[0]
   246  	}
   247  
   248  	log.Printf("[DEBUG] aws_ami - Single AMI found: %s", *image.ImageId)
   249  	return amiDescriptionAttributes(d, image)
   250  }
   251  
   252  type imageSort []*ec2.Image
   253  
   254  func (a imageSort) Len() int      { return len(a) }
   255  func (a imageSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
   256  func (a imageSort) Less(i, j int) bool {
   257  	itime, _ := time.Parse(time.RFC3339, *a[i].CreationDate)
   258  	jtime, _ := time.Parse(time.RFC3339, *a[j].CreationDate)
   259  	return itime.Unix() < jtime.Unix()
   260  }
   261  
   262  // Returns the most recent AMI out of a slice of images.
   263  func mostRecentAmi(images []*ec2.Image) *ec2.Image {
   264  	sortedImages := images
   265  	sort.Sort(imageSort(sortedImages))
   266  	return sortedImages[len(sortedImages)-1]
   267  }
   268  
   269  // populate the numerous fields that the image description returns.
   270  func amiDescriptionAttributes(d *schema.ResourceData, image *ec2.Image) error {
   271  	// Simple attributes first
   272  	d.SetId(*image.ImageId)
   273  	d.Set("architecture", image.Architecture)
   274  	d.Set("creation_date", image.CreationDate)
   275  	if image.Description != nil {
   276  		d.Set("description", image.Description)
   277  	}
   278  	d.Set("hypervisor", image.Hypervisor)
   279  	d.Set("image_id", image.ImageId)
   280  	d.Set("image_location", image.ImageLocation)
   281  	if image.ImageOwnerAlias != nil {
   282  		d.Set("image_owner_alias", image.ImageOwnerAlias)
   283  	}
   284  	d.Set("image_type", image.ImageType)
   285  	if image.KernelId != nil {
   286  		d.Set("kernel_id", image.KernelId)
   287  	}
   288  	d.Set("name", image.Name)
   289  	d.Set("owner_id", image.OwnerId)
   290  	if image.Platform != nil {
   291  		d.Set("platform", image.Platform)
   292  	}
   293  	d.Set("public", image.Public)
   294  	if image.RamdiskId != nil {
   295  		d.Set("ramdisk_id", image.RamdiskId)
   296  	}
   297  	if image.RootDeviceName != nil {
   298  		d.Set("root_device_name", image.RootDeviceName)
   299  	}
   300  	d.Set("root_device_type", image.RootDeviceType)
   301  	if image.SriovNetSupport != nil {
   302  		d.Set("sriov_net_support", image.SriovNetSupport)
   303  	}
   304  	d.Set("state", image.State)
   305  	d.Set("virtualization_type", image.VirtualizationType)
   306  	// Complex types get their own functions
   307  	if err := d.Set("block_device_mappings", amiBlockDeviceMappings(image.BlockDeviceMappings)); err != nil {
   308  		return err
   309  	}
   310  	if err := d.Set("product_codes", amiProductCodes(image.ProductCodes)); err != nil {
   311  		return err
   312  	}
   313  	if err := d.Set("state_reason", amiStateReason(image.StateReason)); err != nil {
   314  		return err
   315  	}
   316  	if err := d.Set("tags", dataSourceTags(image.Tags)); err != nil {
   317  		return err
   318  	}
   319  	return nil
   320  }
   321  
   322  // Returns a set of block device mappings.
   323  func amiBlockDeviceMappings(m []*ec2.BlockDeviceMapping) *schema.Set {
   324  	s := &schema.Set{
   325  		F: amiBlockDeviceMappingHash,
   326  	}
   327  	for _, v := range m {
   328  		mapping := map[string]interface{}{
   329  			"device_name": *v.DeviceName,
   330  		}
   331  		if v.Ebs != nil {
   332  			ebs := map[string]interface{}{
   333  				"delete_on_termination": fmt.Sprintf("%t", *v.Ebs.DeleteOnTermination),
   334  				"encrypted":             fmt.Sprintf("%t", *v.Ebs.Encrypted),
   335  				"volume_size":           fmt.Sprintf("%d", *v.Ebs.VolumeSize),
   336  				"volume_type":           *v.Ebs.VolumeType,
   337  			}
   338  			// Iops is not always set
   339  			if v.Ebs.Iops != nil {
   340  				ebs["iops"] = fmt.Sprintf("%d", *v.Ebs.Iops)
   341  			} else {
   342  				ebs["iops"] = "0"
   343  			}
   344  			// snapshot id may not be set
   345  			if v.Ebs.SnapshotId != nil {
   346  				ebs["snapshot_id"] = *v.Ebs.SnapshotId
   347  			}
   348  
   349  			mapping["ebs"] = ebs
   350  		}
   351  		if v.VirtualName != nil {
   352  			mapping["virtual_name"] = *v.VirtualName
   353  		}
   354  		log.Printf("[DEBUG] aws_ami - adding block device mapping: %v", mapping)
   355  		s.Add(mapping)
   356  	}
   357  	return s
   358  }
   359  
   360  // Returns a set of product codes.
   361  func amiProductCodes(m []*ec2.ProductCode) *schema.Set {
   362  	s := &schema.Set{
   363  		F: amiProductCodesHash,
   364  	}
   365  	for _, v := range m {
   366  		code := map[string]interface{}{
   367  			"product_code_id":   *v.ProductCodeId,
   368  			"product_code_type": *v.ProductCodeType,
   369  		}
   370  		s.Add(code)
   371  	}
   372  	return s
   373  }
   374  
   375  // Returns the state reason.
   376  func amiStateReason(m *ec2.StateReason) map[string]interface{} {
   377  	s := make(map[string]interface{})
   378  	if m != nil {
   379  		s["code"] = *m.Code
   380  		s["message"] = *m.Message
   381  	} else {
   382  		s["code"] = "UNSET"
   383  		s["message"] = "UNSET"
   384  	}
   385  	return s
   386  }
   387  
   388  // Generates a hash for the set hash function used by the block_device_mappings
   389  // attribute.
   390  func amiBlockDeviceMappingHash(v interface{}) int {
   391  	var buf bytes.Buffer
   392  	// All keys added in alphabetical order.
   393  	m := v.(map[string]interface{})
   394  	buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
   395  	if d, ok := m["ebs"]; ok {
   396  		if len(d.(map[string]interface{})) > 0 {
   397  			e := d.(map[string]interface{})
   398  			buf.WriteString(fmt.Sprintf("%s-", e["delete_on_termination"].(string)))
   399  			buf.WriteString(fmt.Sprintf("%s-", e["encrypted"].(string)))
   400  			buf.WriteString(fmt.Sprintf("%s-", e["iops"].(string)))
   401  			buf.WriteString(fmt.Sprintf("%s-", e["volume_size"].(string)))
   402  			buf.WriteString(fmt.Sprintf("%s-", e["volume_type"].(string)))
   403  		}
   404  	}
   405  	if d, ok := m["no_device"]; ok {
   406  		buf.WriteString(fmt.Sprintf("%s-", d.(string)))
   407  	}
   408  	if d, ok := m["virtual_name"]; ok {
   409  		buf.WriteString(fmt.Sprintf("%s-", d.(string)))
   410  	}
   411  	if d, ok := m["snapshot_id"]; ok {
   412  		buf.WriteString(fmt.Sprintf("%s-", d.(string)))
   413  	}
   414  	return hashcode.String(buf.String())
   415  }
   416  
   417  // Generates a hash for the set hash function used by the product_codes
   418  // attribute.
   419  func amiProductCodesHash(v interface{}) int {
   420  	var buf bytes.Buffer
   421  	m := v.(map[string]interface{})
   422  	// All keys added in alphabetical order.
   423  	buf.WriteString(fmt.Sprintf("%s-", m["product_code_id"].(string)))
   424  	buf.WriteString(fmt.Sprintf("%s-", m["product_code_type"].(string)))
   425  	return hashcode.String(buf.String())
   426  }
   427  
   428  func validateNameRegex(v interface{}, k string) (ws []string, errors []error) {
   429  	value := v.(string)
   430  
   431  	if _, err := regexp.Compile(value); err != nil {
   432  		errors = append(errors, fmt.Errorf(
   433  			"%q contains an invalid regular expression: %s",
   434  			k, err))
   435  	}
   436  	return
   437  }