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  }