sigs.k8s.io/cluster-api-provider-azure@v1.14.3/test/e2e/aks_versions.go (about) 1 //go:build e2e 2 // +build e2e 3 4 /* 5 Copyright 2022 The Kubernetes Authors. 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 */ 19 20 package e2e 21 22 import ( 23 "context" 24 "fmt" 25 26 "github.com/Azure/azure-sdk-for-go/sdk/azidentity" 27 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v4" 28 . "github.com/onsi/gomega" 29 "github.com/pkg/errors" 30 "golang.org/x/mod/semver" 31 "k8s.io/utils/ptr" 32 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 33 "sigs.k8s.io/cluster-api/test/framework/clusterctl" 34 "sigs.k8s.io/controller-runtime/pkg/client" 35 ) 36 37 // GetAKSKubernetesVersion gets the kubernetes version for AKS clusters as specified by the environment variable defined by versionVar. 38 func GetAKSKubernetesVersion(ctx context.Context, e2eConfig *clusterctl.E2EConfig, versionVar string) (string, error) { 39 e2eAKSVersion := e2eConfig.GetVariable(versionVar) 40 location := e2eConfig.GetVariable(AzureLocation) 41 subscriptionID := getSubscriptionID(Default) 42 43 var err error 44 var maxVersion string 45 switch e2eAKSVersion { 46 case "latest": 47 maxVersion, err = GetLatestStableAKSKubernetesVersion(ctx, subscriptionID, location) 48 Expect(err).NotTo(HaveOccurred()) 49 case "latest-1": 50 maxVersion, err = GetNextLatestStableAKSKubernetesVersion(ctx, subscriptionID, location) 51 Expect(err).NotTo(HaveOccurred()) 52 default: 53 maxVersion, err = GetWorkingAKSKubernetesVersion(ctx, subscriptionID, location, e2eAKSVersion) 54 Expect(err).NotTo(HaveOccurred()) 55 } 56 57 return maxVersion, nil 58 } 59 60 // byClusterOptions returns a set of ListOptions that will identify all the objects belonging to a Cluster. 61 func byClusterOptions(name, namespace string) []client.ListOption { 62 return []client.ListOption{ 63 client.InNamespace(namespace), 64 client.MatchingLabels{ 65 clusterv1.ClusterNameLabel: name, 66 }, 67 } 68 } 69 70 // GetWorkingAKSKubernetesVersion returns an available Kubernetes version of AKS given a desired semver version, if possible. 71 // If the desired version is available, we return it. 72 // If the desired version is not available, we check for any available patch version using desired version's Major.Minor semver release. 73 // If no versions are available in the desired version's Major.Minor semver release, we return an error. 74 func GetWorkingAKSKubernetesVersion(ctx context.Context, subscriptionID, location, version string) (string, error) { 75 cred, err := azidentity.NewDefaultAzureCredential(nil) 76 if err != nil { 77 return "", errors.Wrap(err, "failed to create a default credential") 78 } 79 managedClustersClient, err := armcontainerservice.NewManagedClustersClient(subscriptionID, cred, nil) 80 if err != nil { 81 return "", errors.Wrap(err, "failed to create a ContainerServices client") 82 } 83 result, err := managedClustersClient.ListKubernetesVersions(ctx, location, nil) 84 if err != nil { 85 return "", errors.Wrap(err, "failed to list Orchestrators") 86 } 87 88 var latestStableVersionDesired bool 89 // We're not doing much input validation here, 90 // we assume that if the prefix is 'stable-' that the remainder of the string is in the format <Major>.<Minor> 91 if isStableVersion, _ := validateStableReleaseString(version); isStableVersion { 92 latestStableVersionDesired = true 93 // Form a fully valid semver version @ the initial patch release (".0") 94 version = fmt.Sprintf("%s.0", version[7:]) 95 } 96 97 // semver comparisons below require a "v" prefix 98 if version[:1] != "v" { 99 version = fmt.Sprintf("v%s", version) 100 } 101 // Create a var of the patch ".0" equivalent of the inputted version 102 baseVersion := fmt.Sprintf("%s.0", semver.MajorMinor(version)) 103 maxVersion := fmt.Sprintf("%s.0", semver.MajorMinor(version)) 104 var foundWorkingVersion bool 105 for _, minor := range result.KubernetesVersionListResult.Values { 106 for patch := range minor.PatchVersions { 107 orchVersion := patch 108 109 // semver comparisons require a "v" prefix 110 if patch[:1] != "v" { 111 orchVersion = "v" + patch 112 } 113 if semver.MajorMinor(orchVersion) != semver.MajorMinor(baseVersion) { 114 continue 115 } 116 // if the inputted version matches with an available AKS version we can return immediately 117 if orchVersion == version && !latestStableVersionDesired { 118 return version, nil 119 } 120 // or, keep track of the highest aks version for a given major.minor 121 if semver.Compare(orchVersion, maxVersion) >= 0 { 122 maxVersion = orchVersion 123 foundWorkingVersion = true 124 } 125 } 126 } 127 128 // This means there is no version supported by AKS for this major.minor 129 if !foundWorkingVersion { 130 return "", errors.New(fmt.Sprintf("No AKS versions found for %s", semver.MajorMinor(baseVersion))) 131 } 132 133 return maxVersion, nil 134 } 135 136 // GetLatestStableAKSKubernetesVersion returns the latest stable available Kubernetes version of AKS. 137 func GetLatestStableAKSKubernetesVersion(ctx context.Context, subscriptionID, location string) (string, error) { 138 return getLatestStableAKSKubernetesVersionOffset(ctx, subscriptionID, location, 0) 139 } 140 141 // GetNextLatestStableAKSKubernetesVersion returns the stable available 142 // Kubernetes version of AKS immediately preceding the latest. 143 func GetNextLatestStableAKSKubernetesVersion(ctx context.Context, subscriptionID, location string) (string, error) { 144 return getLatestStableAKSKubernetesVersionOffset(ctx, subscriptionID, location, 1) 145 } 146 147 func getLatestStableAKSKubernetesVersionOffset(ctx context.Context, subscriptionID, location string, offset int) (string, error) { 148 cred, err := azidentity.NewDefaultAzureCredential(nil) 149 if err != nil { 150 return "", errors.Wrap(err, "failed to create a default credential") 151 } 152 managedClustersClient, err := armcontainerservice.NewManagedClustersClient(subscriptionID, cred, nil) 153 if err != nil { 154 return "", errors.Wrap(err, "failed to create a ContainerServices client") 155 } 156 result, err := managedClustersClient.ListKubernetesVersions(ctx, location, nil) 157 if err != nil { 158 return "", errors.Wrap(err, "failed to list Orchestrators") 159 } 160 161 var orchestratorversions []string 162 var foundWorkingVersion bool 163 var version string 164 var maxVersion string 165 166 for _, minor := range result.KubernetesVersionListResult.Values { 167 for patch := range minor.PatchVersions { 168 // semver comparisons require a "v" prefix 169 if patch[:1] != "v" && !ptr.Deref(minor.IsPreview, false) { 170 version = "v" + patch 171 } 172 orchestratorversions = append(orchestratorversions, version) 173 } 174 } 175 semver.Sort(orchestratorversions) 176 maxVersion = orchestratorversions[len(orchestratorversions)-1-offset] 177 if semver.IsValid(maxVersion) { 178 foundWorkingVersion = true 179 } 180 if !foundWorkingVersion { 181 return "", errors.New("latest stable AKS version not found") 182 } 183 return maxVersion, nil 184 }