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 }