github.com/inge4pres/terraform@v0.7.5-0.20160930053151-bd083f84f376/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 } 274 275 image = filteredImages[0] 276 log.Printf("[DEBUG] aws_ami - Single AMI found: %s", *image.ImageId) 277 278 return amiDescriptionAttributes(d, image) 279 } 280 281 // Build a slice of AMI filter options from the filters provided. 282 func buildAmiFilters(set *schema.Set) []*ec2.Filter { 283 var filters []*ec2.Filter 284 for _, v := range set.List() { 285 m := v.(map[string]interface{}) 286 var filterValues []*string 287 for _, e := range m["values"].([]interface{}) { 288 filterValues = append(filterValues, aws.String(e.(string))) 289 } 290 filters = append(filters, &ec2.Filter{ 291 Name: aws.String(m["name"].(string)), 292 Values: filterValues, 293 }) 294 } 295 return filters 296 } 297 298 type imageSort []*ec2.Image 299 300 func (a imageSort) Len() int { return len(a) } 301 func (a imageSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 302 func (a imageSort) Less(i, j int) bool { 303 itime, _ := time.Parse(time.RFC3339, *a[i].CreationDate) 304 jtime, _ := time.Parse(time.RFC3339, *a[j].CreationDate) 305 return itime.Unix() < jtime.Unix() 306 } 307 308 // Returns the most recent AMI out of a slice of images. 309 func mostRecentAmi(images []*ec2.Image) *ec2.Image { 310 sortedImages := images 311 sort.Sort(imageSort(sortedImages)) 312 return sortedImages[len(sortedImages)-1] 313 } 314 315 // populate the numerous fields that the image description returns. 316 func amiDescriptionAttributes(d *schema.ResourceData, image *ec2.Image) error { 317 // Simple attributes first 318 d.SetId(*image.ImageId) 319 d.Set("architecture", image.Architecture) 320 d.Set("creation_date", image.CreationDate) 321 if image.Description != nil { 322 d.Set("description", image.Description) 323 } 324 d.Set("hypervisor", image.Hypervisor) 325 d.Set("image_id", image.ImageId) 326 d.Set("image_location", image.ImageLocation) 327 if image.ImageOwnerAlias != nil { 328 d.Set("image_owner_alias", image.ImageOwnerAlias) 329 } 330 d.Set("image_type", image.ImageType) 331 if image.KernelId != nil { 332 d.Set("kernel_id", image.KernelId) 333 } 334 d.Set("name", image.Name) 335 d.Set("owner_id", image.OwnerId) 336 if image.Platform != nil { 337 d.Set("platform", image.Platform) 338 } 339 d.Set("public", image.Public) 340 if image.RamdiskId != nil { 341 d.Set("ramdisk_id", image.RamdiskId) 342 } 343 if image.RootDeviceName != nil { 344 d.Set("root_device_name", image.RootDeviceName) 345 } 346 d.Set("root_device_type", image.RootDeviceType) 347 if image.SriovNetSupport != nil { 348 d.Set("sriov_net_support", image.SriovNetSupport) 349 } 350 d.Set("state", image.State) 351 d.Set("virtualization_type", image.VirtualizationType) 352 // Complex types get their own functions 353 if err := d.Set("block_device_mappings", amiBlockDeviceMappings(image.BlockDeviceMappings)); err != nil { 354 return err 355 } 356 if err := d.Set("product_codes", amiProductCodes(image.ProductCodes)); err != nil { 357 return err 358 } 359 if err := d.Set("state_reason", amiStateReason(image.StateReason)); err != nil { 360 return err 361 } 362 if err := d.Set("tags", amiTags(image.Tags)); err != nil { 363 return err 364 } 365 return nil 366 } 367 368 // Returns a set of block device mappings. 369 func amiBlockDeviceMappings(m []*ec2.BlockDeviceMapping) *schema.Set { 370 s := &schema.Set{ 371 F: amiBlockDeviceMappingHash, 372 } 373 for _, v := range m { 374 mapping := map[string]interface{}{ 375 "device_name": *v.DeviceName, 376 } 377 if v.Ebs != nil { 378 ebs := map[string]interface{}{ 379 "delete_on_termination": fmt.Sprintf("%t", *v.Ebs.DeleteOnTermination), 380 "encrypted": fmt.Sprintf("%t", *v.Ebs.Encrypted), 381 "volume_size": fmt.Sprintf("%d", *v.Ebs.VolumeSize), 382 "volume_type": *v.Ebs.VolumeType, 383 } 384 // Iops is not always set 385 if v.Ebs.Iops != nil { 386 ebs["iops"] = fmt.Sprintf("%d", *v.Ebs.Iops) 387 } else { 388 ebs["iops"] = "0" 389 } 390 // snapshot id may not be set 391 if v.Ebs.SnapshotId != nil { 392 ebs["snapshot_id"] = *v.Ebs.SnapshotId 393 } 394 395 mapping["ebs"] = ebs 396 } 397 if v.VirtualName != nil { 398 mapping["virtual_name"] = *v.VirtualName 399 } 400 log.Printf("[DEBUG] aws_ami - adding block device mapping: %v", mapping) 401 s.Add(mapping) 402 } 403 return s 404 } 405 406 // Returns a set of product codes. 407 func amiProductCodes(m []*ec2.ProductCode) *schema.Set { 408 s := &schema.Set{ 409 F: amiProductCodesHash, 410 } 411 for _, v := range m { 412 code := map[string]interface{}{ 413 "product_code_id": *v.ProductCodeId, 414 "product_code_type": *v.ProductCodeType, 415 } 416 s.Add(code) 417 } 418 return s 419 } 420 421 // Returns the state reason. 422 func amiStateReason(m *ec2.StateReason) map[string]interface{} { 423 s := make(map[string]interface{}) 424 if m != nil { 425 s["code"] = *m.Code 426 s["message"] = *m.Message 427 } else { 428 s["code"] = "UNSET" 429 s["message"] = "UNSET" 430 } 431 return s 432 } 433 434 // Returns a set of tags. 435 func amiTags(m []*ec2.Tag) *schema.Set { 436 s := &schema.Set{ 437 F: amiTagsHash, 438 } 439 for _, v := range m { 440 tag := map[string]interface{}{ 441 "key": *v.Key, 442 "value": *v.Value, 443 } 444 s.Add(tag) 445 } 446 return s 447 } 448 449 // Generates a hash for the set hash function used by the block_device_mappings 450 // attribute. 451 func amiBlockDeviceMappingHash(v interface{}) int { 452 var buf bytes.Buffer 453 // All keys added in alphabetical order. 454 m := v.(map[string]interface{}) 455 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 456 if d, ok := m["ebs"]; ok { 457 if len(d.(map[string]interface{})) > 0 { 458 e := d.(map[string]interface{}) 459 buf.WriteString(fmt.Sprintf("%s-", e["delete_on_termination"].(string))) 460 buf.WriteString(fmt.Sprintf("%s-", e["encrypted"].(string))) 461 buf.WriteString(fmt.Sprintf("%s-", e["iops"].(string))) 462 buf.WriteString(fmt.Sprintf("%s-", e["volume_size"].(string))) 463 buf.WriteString(fmt.Sprintf("%s-", e["volume_type"].(string))) 464 } 465 } 466 if d, ok := m["no_device"]; ok { 467 buf.WriteString(fmt.Sprintf("%s-", d.(string))) 468 } 469 if d, ok := m["virtual_name"]; ok { 470 buf.WriteString(fmt.Sprintf("%s-", d.(string))) 471 } 472 if d, ok := m["snapshot_id"]; ok { 473 buf.WriteString(fmt.Sprintf("%s-", d.(string))) 474 } 475 return hashcode.String(buf.String()) 476 } 477 478 // Generates a hash for the set hash function used by the product_codes 479 // attribute. 480 func amiProductCodesHash(v interface{}) int { 481 var buf bytes.Buffer 482 m := v.(map[string]interface{}) 483 // All keys added in alphabetical order. 484 buf.WriteString(fmt.Sprintf("%s-", m["product_code_id"].(string))) 485 buf.WriteString(fmt.Sprintf("%s-", m["product_code_type"].(string))) 486 return hashcode.String(buf.String()) 487 } 488 489 // Generates a hash for the set hash function used by the tags 490 // attribute. 491 func amiTagsHash(v interface{}) int { 492 var buf bytes.Buffer 493 m := v.(map[string]interface{}) 494 // All keys added in alphabetical order. 495 buf.WriteString(fmt.Sprintf("%s-", m["key"].(string))) 496 buf.WriteString(fmt.Sprintf("%s-", m["value"].(string))) 497 return hashcode.String(buf.String()) 498 }