github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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 o := expandStringList(owners.([]interface{})) 197 198 if len(o) > 0 { 199 params.Owners = o 200 } 201 } 202 203 resp, err := conn.DescribeImages(params) 204 if err != nil { 205 return err 206 } 207 208 var filteredImages []*ec2.Image 209 if nameRegexOk { 210 r := regexp.MustCompile(nameRegex.(string)) 211 for _, image := range resp.Images { 212 // Check for a very rare case where the response would include no 213 // image name. No name means nothing to attempt a match against, 214 // therefore we are skipping such image. 215 if image.Name == nil || *image.Name == "" { 216 log.Printf("[WARN] Unable to find AMI name to match against "+ 217 "for image ID %q owned by %q, nothing to do.", 218 *image.ImageId, *image.OwnerId) 219 continue 220 } 221 if r.MatchString(*image.Name) { 222 filteredImages = append(filteredImages, image) 223 } 224 } 225 } else { 226 filteredImages = resp.Images[:] 227 } 228 229 var image *ec2.Image 230 if len(filteredImages) < 1 { 231 return fmt.Errorf("Your query returned no results. Please change your search criteria and try again.") 232 } 233 234 if len(filteredImages) > 1 { 235 recent := d.Get("most_recent").(bool) 236 log.Printf("[DEBUG] aws_ami - multiple results found and `most_recent` is set to: %t", recent) 237 if recent { 238 image = mostRecentAmi(filteredImages) 239 } else { 240 return fmt.Errorf("Your query returned more than one result. Please try a more " + 241 "specific search criteria, or set `most_recent` attribute to true.") 242 } 243 } else { 244 // Query returned single result. 245 image = filteredImages[0] 246 } 247 248 log.Printf("[DEBUG] aws_ami - Single AMI found: %s", *image.ImageId) 249 return amiDescriptionAttributes(d, image) 250 } 251 252 type imageSort []*ec2.Image 253 254 func (a imageSort) Len() int { return len(a) } 255 func (a imageSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 256 func (a imageSort) Less(i, j int) bool { 257 itime, _ := time.Parse(time.RFC3339, *a[i].CreationDate) 258 jtime, _ := time.Parse(time.RFC3339, *a[j].CreationDate) 259 return itime.Unix() < jtime.Unix() 260 } 261 262 // Returns the most recent AMI out of a slice of images. 263 func mostRecentAmi(images []*ec2.Image) *ec2.Image { 264 sortedImages := images 265 sort.Sort(imageSort(sortedImages)) 266 return sortedImages[len(sortedImages)-1] 267 } 268 269 // populate the numerous fields that the image description returns. 270 func amiDescriptionAttributes(d *schema.ResourceData, image *ec2.Image) error { 271 // Simple attributes first 272 d.SetId(*image.ImageId) 273 d.Set("architecture", image.Architecture) 274 d.Set("creation_date", image.CreationDate) 275 if image.Description != nil { 276 d.Set("description", image.Description) 277 } 278 d.Set("hypervisor", image.Hypervisor) 279 d.Set("image_id", image.ImageId) 280 d.Set("image_location", image.ImageLocation) 281 if image.ImageOwnerAlias != nil { 282 d.Set("image_owner_alias", image.ImageOwnerAlias) 283 } 284 d.Set("image_type", image.ImageType) 285 if image.KernelId != nil { 286 d.Set("kernel_id", image.KernelId) 287 } 288 d.Set("name", image.Name) 289 d.Set("owner_id", image.OwnerId) 290 if image.Platform != nil { 291 d.Set("platform", image.Platform) 292 } 293 d.Set("public", image.Public) 294 if image.RamdiskId != nil { 295 d.Set("ramdisk_id", image.RamdiskId) 296 } 297 if image.RootDeviceName != nil { 298 d.Set("root_device_name", image.RootDeviceName) 299 } 300 d.Set("root_device_type", image.RootDeviceType) 301 if image.SriovNetSupport != nil { 302 d.Set("sriov_net_support", image.SriovNetSupport) 303 } 304 d.Set("state", image.State) 305 d.Set("virtualization_type", image.VirtualizationType) 306 // Complex types get their own functions 307 if err := d.Set("block_device_mappings", amiBlockDeviceMappings(image.BlockDeviceMappings)); err != nil { 308 return err 309 } 310 if err := d.Set("product_codes", amiProductCodes(image.ProductCodes)); err != nil { 311 return err 312 } 313 if err := d.Set("state_reason", amiStateReason(image.StateReason)); err != nil { 314 return err 315 } 316 if err := d.Set("tags", dataSourceTags(image.Tags)); err != nil { 317 return err 318 } 319 return nil 320 } 321 322 // Returns a set of block device mappings. 323 func amiBlockDeviceMappings(m []*ec2.BlockDeviceMapping) *schema.Set { 324 s := &schema.Set{ 325 F: amiBlockDeviceMappingHash, 326 } 327 for _, v := range m { 328 mapping := map[string]interface{}{ 329 "device_name": *v.DeviceName, 330 } 331 if v.Ebs != nil { 332 ebs := map[string]interface{}{ 333 "delete_on_termination": fmt.Sprintf("%t", *v.Ebs.DeleteOnTermination), 334 "encrypted": fmt.Sprintf("%t", *v.Ebs.Encrypted), 335 "volume_size": fmt.Sprintf("%d", *v.Ebs.VolumeSize), 336 "volume_type": *v.Ebs.VolumeType, 337 } 338 // Iops is not always set 339 if v.Ebs.Iops != nil { 340 ebs["iops"] = fmt.Sprintf("%d", *v.Ebs.Iops) 341 } else { 342 ebs["iops"] = "0" 343 } 344 // snapshot id may not be set 345 if v.Ebs.SnapshotId != nil { 346 ebs["snapshot_id"] = *v.Ebs.SnapshotId 347 } 348 349 mapping["ebs"] = ebs 350 } 351 if v.VirtualName != nil { 352 mapping["virtual_name"] = *v.VirtualName 353 } 354 log.Printf("[DEBUG] aws_ami - adding block device mapping: %v", mapping) 355 s.Add(mapping) 356 } 357 return s 358 } 359 360 // Returns a set of product codes. 361 func amiProductCodes(m []*ec2.ProductCode) *schema.Set { 362 s := &schema.Set{ 363 F: amiProductCodesHash, 364 } 365 for _, v := range m { 366 code := map[string]interface{}{ 367 "product_code_id": *v.ProductCodeId, 368 "product_code_type": *v.ProductCodeType, 369 } 370 s.Add(code) 371 } 372 return s 373 } 374 375 // Returns the state reason. 376 func amiStateReason(m *ec2.StateReason) map[string]interface{} { 377 s := make(map[string]interface{}) 378 if m != nil { 379 s["code"] = *m.Code 380 s["message"] = *m.Message 381 } else { 382 s["code"] = "UNSET" 383 s["message"] = "UNSET" 384 } 385 return s 386 } 387 388 // Generates a hash for the set hash function used by the block_device_mappings 389 // attribute. 390 func amiBlockDeviceMappingHash(v interface{}) int { 391 var buf bytes.Buffer 392 // All keys added in alphabetical order. 393 m := v.(map[string]interface{}) 394 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 395 if d, ok := m["ebs"]; ok { 396 if len(d.(map[string]interface{})) > 0 { 397 e := d.(map[string]interface{}) 398 buf.WriteString(fmt.Sprintf("%s-", e["delete_on_termination"].(string))) 399 buf.WriteString(fmt.Sprintf("%s-", e["encrypted"].(string))) 400 buf.WriteString(fmt.Sprintf("%s-", e["iops"].(string))) 401 buf.WriteString(fmt.Sprintf("%s-", e["volume_size"].(string))) 402 buf.WriteString(fmt.Sprintf("%s-", e["volume_type"].(string))) 403 } 404 } 405 if d, ok := m["no_device"]; ok { 406 buf.WriteString(fmt.Sprintf("%s-", d.(string))) 407 } 408 if d, ok := m["virtual_name"]; ok { 409 buf.WriteString(fmt.Sprintf("%s-", d.(string))) 410 } 411 if d, ok := m["snapshot_id"]; ok { 412 buf.WriteString(fmt.Sprintf("%s-", d.(string))) 413 } 414 return hashcode.String(buf.String()) 415 } 416 417 // Generates a hash for the set hash function used by the product_codes 418 // attribute. 419 func amiProductCodesHash(v interface{}) int { 420 var buf bytes.Buffer 421 m := v.(map[string]interface{}) 422 // All keys added in alphabetical order. 423 buf.WriteString(fmt.Sprintf("%s-", m["product_code_id"].(string))) 424 buf.WriteString(fmt.Sprintf("%s-", m["product_code_type"].(string))) 425 return hashcode.String(buf.String()) 426 } 427 428 func validateNameRegex(v interface{}, k string) (ws []string, errors []error) { 429 value := v.(string) 430 431 if _, err := regexp.Compile(value); err != nil { 432 errors = append(errors, fmt.Errorf( 433 "%q contains an invalid regular expression: %s", 434 k, err)) 435 } 436 return 437 }