github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/data_source_aws_ami.go (about)

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