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 }