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