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