sigs.k8s.io/cluster-api-provider-aws@v1.5.5/cmd/clusterawsadm/ami/helper.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package ami 18 19 import ( 20 "fmt" 21 "io" 22 "net/http" 23 "strconv" 24 "strings" 25 26 "github.com/aws/aws-sdk-go/aws" 27 "github.com/aws/aws-sdk-go/service/ec2" 28 "github.com/aws/aws-sdk-go/service/ec2/ec2iface" 29 "github.com/blang/semver" 30 "github.com/pkg/errors" 31 32 ec2service "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/ec2" 33 ) 34 35 const ( 36 latestStableReleaseURL = "https://dl.k8s.io/release/stable%s.txt" 37 tagPrefix = "v" 38 ) 39 40 func getSupportedOsList() []string { 41 return []string{"centos-7", "ubuntu-18.04", "ubuntu-20.04", "amazon-2", "flatcar-stable"} 42 } 43 44 func getimageRegionList() []string { 45 return []string{ 46 "ap-northeast-1", 47 "ap-northeast-2", 48 "ap-south-1", 49 "ap-southeast-1", 50 "ap-southeast-2", 51 "ca-central-1", 52 "eu-central-1", 53 "eu-west-1", 54 "eu-west-2", 55 "eu-west-3", 56 "sa-east-1", 57 "us-east-1", 58 "us-east-2", 59 "us-west-1", 60 "us-west-2", 61 } 62 } 63 64 // LatestPatchRelease returns the latest patch release matching. 65 func LatestPatchRelease(searchVersion string) (string, error) { 66 searchSemVer, err := semver.Make(strings.TrimPrefix(searchVersion, tagPrefix)) 67 if err != nil { 68 return "", err 69 } 70 resp, err := http.Get(fmt.Sprintf(latestStableReleaseURL, "-"+strconv.Itoa(int(searchSemVer.Major))+"."+strconv.Itoa(int(searchSemVer.Minor)))) 71 if err != nil { 72 return "", err 73 } 74 defer resp.Body.Close() 75 b, err := io.ReadAll(resp.Body) 76 if err != nil { 77 return "", err 78 } 79 80 return strings.TrimSpace(string(b)), nil 81 } 82 83 // PreviousMinorRelease returns the latest patch release for the previous version 84 // of Kubernetes, e.g. v1.19.1 returns v1.18.8 as of Sep 2020. 85 func PreviousMinorRelease(searchVersion string) (string, error) { 86 semVer, err := semver.Make(strings.TrimPrefix(searchVersion, tagPrefix)) 87 if err != nil { 88 return "", err 89 } 90 semVer.Minor-- 91 92 return LatestPatchRelease(semVer.String()) 93 } 94 95 // getSupportedKubernetesVersions returns all possible k8s versions till last nth kubernetes release. 96 func getSupportedKubernetesVersions(lastNReleases int) ([]string, error) { 97 currentVersion, err := latestStableRelease() 98 if err != nil { 99 return nil, err 100 } 101 102 versionPatches, err := allPatchesForVersion(currentVersion) 103 if err != nil { 104 return nil, err 105 } 106 107 versionPatches = append(versionPatches, currentVersion) 108 109 for lastNReleases != 0 { 110 currentVersion, err = PreviousMinorRelease(currentVersion) 111 if err != nil { 112 return nil, err 113 } 114 115 currentVersionPatches, err := allPatchesForVersion(currentVersion) 116 if err != nil { 117 return nil, err 118 } 119 120 versionPatches = append(versionPatches, currentVersion) 121 versionPatches = append(versionPatches, currentVersionPatches...) 122 123 lastNReleases-- 124 } 125 return versionPatches, nil 126 } 127 128 // allPatchesForVersion return all patches for a given version starting with given minor release. 129 func allPatchesForVersion(latestVersion string) ([]string, error) { 130 semVer, err := semver.Make(strings.TrimPrefix(latestVersion, tagPrefix)) 131 if err != nil { 132 return nil, err 133 } 134 patchVersions := make([]string, 0) 135 versionStr := fmt.Sprintf("%s%d.%d", tagPrefix, semVer.Major, semVer.Minor) 136 for semVer.Patch != 0 { 137 semVer.Patch-- 138 patchVersions = append(patchVersions, fmt.Sprintf("%s.%d", versionStr, semVer.Patch)) 139 } 140 return patchVersions, nil 141 } 142 143 // latestStableRelease fetches the latest stable Kubernetes version 144 // If it is a x.x.0 release, it gets the previous minor version. 145 func latestStableRelease() (string, error) { 146 resp, err := http.Get(fmt.Sprintf(latestStableReleaseURL, "")) 147 if err != nil { 148 return "", err 149 } 150 defer resp.Body.Close() 151 152 b, err := io.ReadAll(resp.Body) 153 if err != nil { 154 return "", err 155 } 156 latestVersion := strings.TrimSpace(string(b)) 157 tagPrefix := "v" 158 latestVersionSemVer, err := semver.Make(strings.TrimPrefix(latestVersion, tagPrefix)) 159 if err != nil { 160 return "", err 161 } 162 163 // If it is the first release, use the previous version instead 164 if latestVersionSemVer.Patch == 0 { 165 latestVersionSemVer.Minor-- 166 // Address to get stable release for a particular version is: https://dl.k8s.io/release/stable-1.19.txt" 167 olderVersion := fmt.Sprintf("-%v.%v", latestVersionSemVer.Major, latestVersionSemVer.Minor) 168 resp, err = http.Get(fmt.Sprintf(latestStableReleaseURL, olderVersion)) 169 if err != nil { 170 return "", err 171 } 172 defer resp.Body.Close() 173 b, err = io.ReadAll(resp.Body) 174 if err != nil { 175 return "", err 176 } 177 latestVersion = strings.TrimSpace(string(b)) 178 } 179 return latestVersion, nil 180 } 181 182 func getAllImages(ec2Client ec2iface.EC2API, ownerID string) (map[string][]*ec2.Image, error) { 183 if ownerID == "" { 184 ownerID = ec2service.DefaultMachineAMIOwnerID 185 } 186 187 describeImageInput := &ec2.DescribeImagesInput{ 188 Filters: []*ec2.Filter{ 189 { 190 Name: aws.String("owner-id"), 191 Values: []*string{aws.String(ownerID)}, 192 }, 193 { 194 Name: aws.String("architecture"), 195 Values: []*string{aws.String("x86_64")}, 196 }, 197 { 198 Name: aws.String("state"), 199 Values: []*string{aws.String("available")}, 200 }, 201 { 202 Name: aws.String("virtualization-type"), 203 Values: []*string{aws.String("hvm")}, 204 }, 205 }, 206 } 207 208 out, err := ec2Client.DescribeImages(describeImageInput) 209 if err != nil { 210 return nil, errors.Wrap(err, "failed to fetch AMIs") 211 } 212 if len(out.Images) == 0 { 213 return nil, nil 214 } 215 216 imagesMap := make(map[string][]*ec2.Image) 217 for _, image := range out.Images { 218 arr := strings.Split(aws.StringValue(image.Name), "-") 219 arr = arr[:len(arr)-2] 220 name := strings.Join(arr, "-") 221 images, ok := imagesMap[name] 222 if !ok { 223 images = make([]*ec2.Image, 0) 224 } 225 imagesMap[name] = append(images, image) 226 } 227 228 return imagesMap, nil 229 } 230 231 func findAMI(imagesMap map[string][]*ec2.Image, baseOS, kubernetesVersion string) (*ec2.Image, error) { 232 amiNameFormat := "capa-ami-{{.BaseOS}}-{{.K8sVersion}}" 233 amiName, err := ec2service.GenerateAmiName(amiNameFormat, baseOS, kubernetesVersion) 234 if err != nil { 235 return nil, errors.Wrapf(err, "failed to process ami format: %q", amiNameFormat) 236 } 237 238 if val, ok := imagesMap[amiName]; ok && val != nil { 239 latestImage, err := ec2service.GetLatestImage(val) 240 if err != nil { 241 return nil, err 242 } 243 return latestImage, nil 244 } 245 246 return nil, nil 247 }