github.com/darmach/terratest@v0.34.8-0.20210517103231-80931f95e3ff/modules/aws/ami.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "sort" 6 "time" 7 8 "github.com/aws/aws-sdk-go/aws" 9 "github.com/aws/aws-sdk-go/service/ec2" 10 "github.com/gruntwork-io/terratest/modules/logger" 11 "github.com/gruntwork-io/terratest/modules/testing" 12 ) 13 14 // These are commonly used AMI account IDs. 15 const ( 16 CanonicalAccountId = "099720109477" 17 CentOsAccountId = "679593333241" 18 AmazonAccountId = "amazon" 19 ) 20 21 // DeleteAmiAndAllSnapshots will delete the given AMI along with all EBS snapshots that backed that AMI 22 func DeleteAmiAndAllSnapshots(t testing.TestingT, region string, ami string) { 23 err := DeleteAmiAndAllSnapshotsE(t, region, ami) 24 if err != nil { 25 t.Fatal(err) 26 } 27 } 28 29 // DeleteAmiAndAllSnapshotsE will delete the given AMI along with all EBS snapshots that backed that AMI 30 func DeleteAmiAndAllSnapshotsE(t testing.TestingT, region string, ami string) error { 31 snapshots, err := GetEbsSnapshotsForAmiE(t, region, ami) 32 if err != nil { 33 return err 34 } 35 36 err = DeleteAmiE(t, region, ami) 37 if err != nil { 38 return err 39 } 40 41 for _, snapshot := range snapshots { 42 err = DeleteEbsSnapshotE(t, region, snapshot) 43 if err != nil { 44 return err 45 } 46 } 47 48 return nil 49 } 50 51 // GetEbsSnapshotsForAmi retrieves the EBS snapshots which back the given AMI 52 func GetEbsSnapshotsForAmi(t testing.TestingT, region string, ami string) []string { 53 snapshots, err := GetEbsSnapshotsForAmiE(t, region, ami) 54 if err != nil { 55 t.Fatal(err) 56 } 57 return snapshots 58 } 59 60 // GetEbsSnapshotsForAmi retrieves the EBS snapshots which back the given AMI 61 func GetEbsSnapshotsForAmiE(t testing.TestingT, region string, ami string) ([]string, error) { 62 logger.Logf(t, "Retrieving EBS snapshots backing AMI %s", ami) 63 ec2Client, err := NewEc2ClientE(t, region) 64 if err != nil { 65 return nil, err 66 } 67 68 images, err := ec2Client.DescribeImages(&ec2.DescribeImagesInput{ 69 ImageIds: []*string{ 70 aws.String(ami), 71 }, 72 }) 73 if err != nil { 74 return nil, err 75 } 76 77 var snapshots []string 78 for _, image := range images.Images { 79 for _, mapping := range image.BlockDeviceMappings { 80 if mapping.Ebs != nil && mapping.Ebs.SnapshotId != nil { 81 snapshots = append(snapshots, aws.StringValue(mapping.Ebs.SnapshotId)) 82 } 83 } 84 } 85 86 return snapshots, err 87 } 88 89 // GetMostRecentAmiId gets the ID of the most recent AMI in the given region that has the given owner and matches the given filters. Each 90 // filter should correspond to the name and values of a filter supported by DescribeImagesInput: 91 // https://docs.aws.amazon.com/sdk-for-go/api/service/ec2/#DescribeImagesInput 92 func GetMostRecentAmiId(t testing.TestingT, region string, ownerId string, filters map[string][]string) string { 93 amiID, err := GetMostRecentAmiIdE(t, region, ownerId, filters) 94 if err != nil { 95 t.Fatal(err) 96 } 97 return amiID 98 } 99 100 // GetMostRecentAmiIdE gets the ID of the most recent AMI in the given region that has the given owner and matches the given filters. Each 101 // filter should correspond to the name and values of a filter supported by DescribeImagesInput: 102 // https://docs.aws.amazon.com/sdk-for-go/api/service/ec2/#DescribeImagesInput 103 func GetMostRecentAmiIdE(t testing.TestingT, region string, ownerId string, filters map[string][]string) (string, error) { 104 ec2Client, err := NewEc2ClientE(t, region) 105 if err != nil { 106 return "", err 107 } 108 109 ec2Filters := []*ec2.Filter{} 110 for name, values := range filters { 111 ec2Filters = append(ec2Filters, &ec2.Filter{Name: aws.String(name), Values: aws.StringSlice(values)}) 112 } 113 114 input := ec2.DescribeImagesInput{ 115 Filters: ec2Filters, 116 Owners: []*string{aws.String(ownerId)}, 117 } 118 119 out, err := ec2Client.DescribeImages(&input) 120 if err != nil { 121 return "", err 122 } 123 124 if len(out.Images) == 0 { 125 return "", NoImagesFound{Region: region, OwnerId: ownerId, Filters: filters} 126 } 127 128 mostRecentImage := mostRecentAMI(out.Images) 129 return aws.StringValue(mostRecentImage.ImageId), nil 130 } 131 132 // Image sorting code borrowed from: https://github.com/hashicorp/packer/blob/7f4112ba229309cfc0ebaa10ded2abdfaf1b22c8/builder/amazon/common/step_source_ami_info.go 133 type imageSort []*ec2.Image 134 135 func (a imageSort) Len() int { return len(a) } 136 func (a imageSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 137 func (a imageSort) Less(i, j int) bool { 138 iTime, _ := time.Parse(time.RFC3339, *a[i].CreationDate) 139 jTime, _ := time.Parse(time.RFC3339, *a[j].CreationDate) 140 return iTime.Unix() < jTime.Unix() 141 } 142 143 // mostRecentAMI returns the most recent AMI out of a slice of images. 144 func mostRecentAMI(images []*ec2.Image) *ec2.Image { 145 sortedImages := images 146 sort.Sort(imageSort(sortedImages)) 147 return sortedImages[len(sortedImages)-1] 148 } 149 150 // GetUbuntu1404Ami gets the ID of the most recent Ubuntu 14.04 HVM x86_64 EBS GP2 AMI in the given region. 151 func GetUbuntu1404Ami(t testing.TestingT, region string) string { 152 amiID, err := GetUbuntu1404AmiE(t, region) 153 if err != nil { 154 t.Fatal(err) 155 } 156 return amiID 157 } 158 159 // GetUbuntu1404AmiE gets the ID of the most recent Ubuntu 14.04 HVM x86_64 EBS GP2 AMI in the given region. 160 func GetUbuntu1404AmiE(t testing.TestingT, region string) (string, error) { 161 filters := map[string][]string{ 162 "name": {"*ubuntu-trusty-14.04-amd64-server-*"}, 163 "virtualization-type": {"hvm"}, 164 "architecture": {"x86_64"}, 165 "root-device-type": {"ebs"}, 166 "block-device-mapping.volume-type": {"gp2"}, 167 } 168 169 return GetMostRecentAmiIdE(t, region, CanonicalAccountId, filters) 170 } 171 172 // GetUbuntu1604Ami gets the ID of the most recent Ubuntu 16.04 HVM x86_64 EBS GP2 AMI in the given region. 173 func GetUbuntu1604Ami(t testing.TestingT, region string) string { 174 amiID, err := GetUbuntu1604AmiE(t, region) 175 if err != nil { 176 t.Fatal(err) 177 } 178 return amiID 179 } 180 181 // GetUbuntu1604AmiE gets the ID of the most recent Ubuntu 16.04 HVM x86_64 EBS GP2 AMI in the given region. 182 func GetUbuntu1604AmiE(t testing.TestingT, region string) (string, error) { 183 filters := map[string][]string{ 184 "name": {"*ubuntu-xenial-16.04-amd64-server-*"}, 185 "virtualization-type": {"hvm"}, 186 "architecture": {"x86_64"}, 187 "root-device-type": {"ebs"}, 188 "block-device-mapping.volume-type": {"gp2"}, 189 } 190 191 return GetMostRecentAmiIdE(t, region, CanonicalAccountId, filters) 192 } 193 194 // GetCentos7Ami returns a CentOS 7 public AMI from the given region. 195 // WARNING: you may have to accept the terms & conditions of this AMI in AWS MarketPlace for your AWS Account before 196 // you can successfully launch the AMI. 197 func GetCentos7Ami(t testing.TestingT, region string) string { 198 amiID, err := GetCentos7AmiE(t, region) 199 if err != nil { 200 t.Fatal(err) 201 } 202 return amiID 203 } 204 205 // GetCentos7AmiE returns a CentOS 7 public AMI from the given region. 206 // WARNING: you may have to accept the terms & conditions of this AMI in AWS MarketPlace for your AWS Account before 207 // you can successfully launch the AMI. 208 func GetCentos7AmiE(t testing.TestingT, region string) (string, error) { 209 filters := map[string][]string{ 210 "name": {"*CentOS Linux 7 x86_64 HVM EBS*"}, 211 "virtualization-type": {"hvm"}, 212 "architecture": {"x86_64"}, 213 "root-device-type": {"ebs"}, 214 "block-device-mapping.volume-type": {"gp2"}, 215 } 216 217 return GetMostRecentAmiIdE(t, region, CentOsAccountId, filters) 218 } 219 220 // GetAmazonLinuxAmi returns an Amazon Linux AMI HVM, SSD Volume Type public AMI for the given region. 221 func GetAmazonLinuxAmi(t testing.TestingT, region string) string { 222 amiID, err := GetAmazonLinuxAmiE(t, region) 223 if err != nil { 224 t.Fatal(err) 225 } 226 return amiID 227 } 228 229 // GetAmazonLinuxAmiE returns an Amazon Linux AMI HVM, SSD Volume Type public AMI for the given region. 230 func GetAmazonLinuxAmiE(t testing.TestingT, region string) (string, error) { 231 filters := map[string][]string{ 232 "name": {"*amzn-ami-hvm-*-x86_64*"}, 233 "virtualization-type": {"hvm"}, 234 "architecture": {"x86_64"}, 235 "root-device-type": {"ebs"}, 236 "block-device-mapping.volume-type": {"gp2"}, 237 } 238 239 return GetMostRecentAmiIdE(t, region, AmazonAccountId, filters) 240 } 241 242 // GetEcsOptimizedAmazonLinuxAmi returns an Amazon ECS-Optimized Amazon Linux AMI for the given region. This AMI is useful for running an ECS cluster. 243 func GetEcsOptimizedAmazonLinuxAmi(t testing.TestingT, region string) string { 244 amiID, err := GetEcsOptimizedAmazonLinuxAmiE(t, region) 245 if err != nil { 246 t.Fatal(err) 247 } 248 return amiID 249 } 250 251 // GetEcsOptimizedAmazonLinuxAmiE returns an Amazon ECS-Optimized Amazon Linux AMI for the given region. This AMI is useful for running an ECS cluster. 252 func GetEcsOptimizedAmazonLinuxAmiE(t testing.TestingT, region string) (string, error) { 253 filters := map[string][]string{ 254 "name": {"*amzn-ami*amazon-ecs-optimized*"}, 255 "virtualization-type": {"hvm"}, 256 "architecture": {"x86_64"}, 257 "root-device-type": {"ebs"}, 258 "block-device-mapping.volume-type": {"gp2"}, 259 } 260 261 return GetMostRecentAmiIdE(t, region, AmazonAccountId, filters) 262 } 263 264 // NoImagesFound is an error that occurs if no images were found. 265 type NoImagesFound struct { 266 Region string 267 OwnerId string 268 Filters map[string][]string 269 } 270 271 func (err NoImagesFound) Error() string { 272 return fmt.Sprintf("No AMIs found in %s for owner ID %s and filters: %v", err.Region, err.OwnerId, err.Filters) 273 }