sigs.k8s.io/cluster-api-provider-azure@v1.17.0/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  	"sigs.k8s.io/cluster-api/test/framework/clusterctl"
    33  )
    34  
    35  // GetAKSKubernetesVersion gets the kubernetes version for AKS clusters as specified by the environment variable defined by versionVar.
    36  func GetAKSKubernetesVersion(ctx context.Context, e2eConfig *clusterctl.E2EConfig, versionVar string) (string, error) {
    37  	e2eAKSVersion := e2eConfig.GetVariable(versionVar)
    38  	location := e2eConfig.GetVariable(AzureLocation)
    39  	subscriptionID := getSubscriptionID(Default)
    40  
    41  	var err error
    42  	var maxVersion string
    43  	switch e2eAKSVersion {
    44  	case "latest":
    45  		maxVersion, err = GetLatestStableAKSKubernetesVersion(ctx, subscriptionID, location)
    46  		Expect(err).NotTo(HaveOccurred())
    47  	case "latest-1":
    48  		maxVersion, err = GetNextLatestStableAKSKubernetesVersion(ctx, subscriptionID, location)
    49  		Expect(err).NotTo(HaveOccurred())
    50  	default:
    51  		maxVersion, err = GetWorkingAKSKubernetesVersion(ctx, subscriptionID, location, e2eAKSVersion)
    52  		Expect(err).NotTo(HaveOccurred())
    53  	}
    54  
    55  	return maxVersion, nil
    56  }
    57  
    58  // GetWorkingAKSKubernetesVersion returns an available Kubernetes version of AKS given a desired semver version, if possible.
    59  // If the desired version is available, we return it.
    60  // If the desired version is not available, we check for any available patch version using desired version's Major.Minor semver release.
    61  // If no versions are available in the desired version's Major.Minor semver release, we return an error.
    62  func GetWorkingAKSKubernetesVersion(ctx context.Context, subscriptionID, location, version string) (string, error) {
    63  	cred, err := azidentity.NewDefaultAzureCredential(nil)
    64  	if err != nil {
    65  		return "", errors.Wrap(err, "failed to create a default credential")
    66  	}
    67  	managedClustersClient, err := armcontainerservice.NewManagedClustersClient(subscriptionID, cred, nil)
    68  	if err != nil {
    69  		return "", errors.Wrap(err, "failed to create a ContainerServices client")
    70  	}
    71  	result, err := managedClustersClient.ListKubernetesVersions(ctx, location, nil)
    72  	if err != nil {
    73  		return "", errors.Wrap(err, "failed to list Orchestrators")
    74  	}
    75  
    76  	var latestStableVersionDesired bool
    77  	// We're not doing much input validation here,
    78  	// we assume that if the prefix is 'stable-' that the remainder of the string is in the format <Major>.<Minor>
    79  	if isStableVersion, _ := validateStableReleaseString(version); isStableVersion {
    80  		latestStableVersionDesired = true
    81  		// Form a fully valid semver version @ the initial patch release (".0")
    82  		version = fmt.Sprintf("%s.0", version[7:])
    83  	}
    84  
    85  	// semver comparisons below require a "v" prefix
    86  	if version[:1] != "v" {
    87  		version = fmt.Sprintf("v%s", version)
    88  	}
    89  	// Create a var of the patch ".0" equivalent of the inputted version
    90  	baseVersion := fmt.Sprintf("%s.0", semver.MajorMinor(version))
    91  	maxVersion := fmt.Sprintf("%s.0", semver.MajorMinor(version))
    92  	var foundWorkingVersion bool
    93  	for _, minor := range result.KubernetesVersionListResult.Values {
    94  		for patch := range minor.PatchVersions {
    95  			orchVersion := patch
    96  
    97  			// semver comparisons require a "v" prefix
    98  			if patch[:1] != "v" {
    99  				orchVersion = "v" + patch
   100  			}
   101  			if semver.MajorMinor(orchVersion) != semver.MajorMinor(baseVersion) {
   102  				continue
   103  			}
   104  			// if the inputted version matches with an available AKS version we can return immediately
   105  			if orchVersion == version && !latestStableVersionDesired {
   106  				return version, nil
   107  			}
   108  			// or, keep track of the highest aks version for a given major.minor
   109  			if semver.Compare(orchVersion, maxVersion) >= 0 {
   110  				maxVersion = orchVersion
   111  				foundWorkingVersion = true
   112  			}
   113  		}
   114  	}
   115  
   116  	// This means there is no version supported by AKS for this major.minor
   117  	if !foundWorkingVersion {
   118  		return "", errors.New(fmt.Sprintf("No AKS versions found for %s", semver.MajorMinor(baseVersion)))
   119  	}
   120  
   121  	return maxVersion, nil
   122  }
   123  
   124  // GetLatestStableAKSKubernetesVersion returns the latest stable available Kubernetes version of AKS.
   125  func GetLatestStableAKSKubernetesVersion(ctx context.Context, subscriptionID, location string) (string, error) {
   126  	return getLatestStableAKSKubernetesVersionOffset(ctx, subscriptionID, location, 0)
   127  }
   128  
   129  // GetNextLatestStableAKSKubernetesVersion returns the stable available
   130  // Kubernetes version of AKS immediately preceding the latest.
   131  func GetNextLatestStableAKSKubernetesVersion(ctx context.Context, subscriptionID, location string) (string, error) {
   132  	return getLatestStableAKSKubernetesVersionOffset(ctx, subscriptionID, location, 1)
   133  }
   134  
   135  func getLatestStableAKSKubernetesVersionOffset(ctx context.Context, subscriptionID, location string, offset int) (string, error) {
   136  	cred, err := azidentity.NewDefaultAzureCredential(nil)
   137  	if err != nil {
   138  		return "", errors.Wrap(err, "failed to create a default credential")
   139  	}
   140  	managedClustersClient, err := armcontainerservice.NewManagedClustersClient(subscriptionID, cred, nil)
   141  	if err != nil {
   142  		return "", errors.Wrap(err, "failed to create a ContainerServices client")
   143  	}
   144  	result, err := managedClustersClient.ListKubernetesVersions(ctx, location, nil)
   145  	if err != nil {
   146  		return "", errors.Wrap(err, "failed to list Orchestrators")
   147  	}
   148  
   149  	var orchestratorversions []string
   150  	var foundWorkingVersion bool
   151  	var version string
   152  	var maxVersion string
   153  
   154  	for _, minor := range result.KubernetesVersionListResult.Values {
   155  		for patch := range minor.PatchVersions {
   156  			// semver comparisons require a "v" prefix
   157  			if patch[:1] != "v" && !ptr.Deref(minor.IsPreview, false) {
   158  				version = "v" + patch
   159  			}
   160  			orchestratorversions = append(orchestratorversions, version)
   161  		}
   162  	}
   163  	semver.Sort(orchestratorversions)
   164  	maxVersion = orchestratorversions[len(orchestratorversions)-1-offset]
   165  	if semver.IsValid(maxVersion) {
   166  		foundWorkingVersion = true
   167  	}
   168  	if !foundWorkingVersion {
   169  		return "", errors.New("latest stable AKS version not found")
   170  	}
   171  	return maxVersion, nil
   172  }