Skip to content

Commit bb47200

Browse files
authored
feat: include preview versions as fallback (#674)
* feat: include preview versions as fallback * feat: cleanup code * fix: review findings * fix: review findings * fix: linter warning * fix: applied review comments
1 parent f0168cf commit bb47200

File tree

2 files changed

+278
-45
lines changed

2 files changed

+278
-45
lines changed

stackit/internal/services/ske/cluster/resource.go

Lines changed: 122 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net/http"
77
"regexp"
8+
"sort"
89
"strings"
910
"time"
1011

@@ -743,6 +744,33 @@ func (r *clusterResource) Create(ctx context.Context, req resource.CreateRequest
743744
tflog.Info(ctx, "SKE cluster created")
744745
}
745746

747+
func sortK8sVersions(versions []ske.KubernetesVersion) {
748+
sort.Slice(versions, func(i, j int) bool {
749+
v1, v2 := (versions)[i].Version, (versions)[j].Version
750+
if v1 == nil {
751+
return false
752+
}
753+
if v2 == nil {
754+
return true
755+
}
756+
757+
// we have to make copies of the input strings to add prefixes,
758+
// otherwise we would be changing the passed elements
759+
t1, t2 := *v1, *v2
760+
761+
if !strings.HasPrefix(t1, "v") {
762+
t1 = "v" + t1
763+
}
764+
if !strings.HasPrefix(t2, "v") {
765+
t2 = "v" + t2
766+
}
767+
return semver.Compare(t1, t2) > 0
768+
})
769+
}
770+
771+
// loadAvailableVersions loads the available k8s and machine versions from the API.
772+
// The k8s versions are sorted descending order, i.e. the latest versions (including previews)
773+
// are listed first
746774
func (r *clusterResource) loadAvailableVersions(ctx context.Context) ([]ske.KubernetesVersion, []ske.MachineImage, error) {
747775
c := r.skeClient
748776
res, err := c.ListProviderOptions(ctx).Execute()
@@ -793,7 +821,7 @@ func (r *clusterResource) createOrUpdateCluster(ctx context.Context, diags *diag
793821
// cluster vars
794822
projectId := model.ProjectId.ValueString()
795823
name := model.Name.ValueString()
796-
kubernetes, hasDeprecatedVersion, err := toKubernetesPayload(model, availableKubernetesVersions, currentKubernetesVersion)
824+
kubernetes, hasDeprecatedVersion, err := toKubernetesPayload(model, availableKubernetesVersions, currentKubernetesVersion, diags)
797825
if err != nil {
798826
core.LogAndAddError(ctx, diags, "Error creating/updating cluster", fmt.Sprintf("Creating cluster config API payload: %v", err))
799827
return
@@ -1813,7 +1841,7 @@ func mapExtensions(ctx context.Context, cl *ske.Cluster, m *Model) error {
18131841
return nil
18141842
}
18151843

1816-
func toKubernetesPayload(m *Model, availableVersions []ske.KubernetesVersion, currentKubernetesVersion *string) (kubernetesPayload *ske.Kubernetes, hasDeprecatedVersion bool, err error) {
1844+
func toKubernetesPayload(m *Model, availableVersions []ske.KubernetesVersion, currentKubernetesVersion *string, diags *diag.Diagnostics) (kubernetesPayload *ske.Kubernetes, hasDeprecatedVersion bool, err error) {
18171845
providedVersionMin := m.KubernetesVersionMin.ValueStringPointer()
18181846

18191847
if !m.KubernetesVersion.IsNull() {
@@ -1823,7 +1851,7 @@ func toKubernetesPayload(m *Model, availableVersions []ske.KubernetesVersion, cu
18231851
providedVersionMin = conversion.StringValueToPointer(m.KubernetesVersion)
18241852
}
18251853

1826-
versionUsed, hasDeprecatedVersion, err := latestMatchingKubernetesVersion(availableVersions, providedVersionMin, currentKubernetesVersion)
1854+
versionUsed, hasDeprecatedVersion, err := latestMatchingKubernetesVersion(availableVersions, providedVersionMin, currentKubernetesVersion, diags)
18271855
if err != nil {
18281856
return nil, false, fmt.Errorf("getting latest matching kubernetes version: %w", err)
18291857
}
@@ -1835,9 +1863,7 @@ func toKubernetesPayload(m *Model, availableVersions []ske.KubernetesVersion, cu
18351863
return k, hasDeprecatedVersion, nil
18361864
}
18371865

1838-
func latestMatchingKubernetesVersion(availableVersions []ske.KubernetesVersion, kubernetesVersionMin, currentKubernetesVersion *string) (version *string, deprecated bool, err error) {
1839-
deprecated = false
1840-
1866+
func latestMatchingKubernetesVersion(availableVersions []ske.KubernetesVersion, kubernetesVersionMin, currentKubernetesVersion *string, diags *diag.Diagnostics) (version *string, deprecated bool, err error) {
18411867
if availableVersions == nil {
18421868
return nil, false, fmt.Errorf("nil available kubernetes versions")
18431869
}
@@ -1865,58 +1891,113 @@ func latestMatchingKubernetesVersion(availableVersions []ske.KubernetesVersion,
18651891
}
18661892
}
18671893

1868-
var fullVersion bool
1869-
versionExp := validate.FullVersionRegex
1870-
versionRegex := regexp.MustCompile(versionExp)
1871-
if versionRegex.MatchString(*kubernetesVersionMin) {
1872-
fullVersion = true
1873-
}
1894+
versionRegex := regexp.MustCompile(validate.FullVersionRegex)
1895+
fullVersion := versionRegex.MatchString(*kubernetesVersionMin)
18741896

18751897
providedVersionPrefixed := "v" + *kubernetesVersionMin
1876-
18771898
if !semver.IsValid(providedVersionPrefixed) {
18781899
return nil, false, fmt.Errorf("provided version is invalid")
18791900
}
18801901

1881-
var versionUsed *string
1882-
var state *string
1883-
var availableVersionsArray []string
1884-
// Get the higher available version that matches the major, minor and patch version provided by the user
1885-
for _, v := range availableVersions {
1886-
if v.State == nil || v.Version == nil {
1902+
var (
1903+
selectedVersion *ske.KubernetesVersion
1904+
availableVersionsArray []string
1905+
)
1906+
if fullVersion {
1907+
availableVersionsArray, selectedVersion = selectFullVersion(availableVersions, providedVersionPrefixed)
1908+
} else {
1909+
availableVersionsArray, selectedVersion = selectMatchingVersion(availableVersions, providedVersionPrefixed)
1910+
}
1911+
1912+
deprecated = isDeprecated(selectedVersion)
1913+
1914+
if isPreview(selectedVersion) {
1915+
diags.AddWarning("preview version selected", fmt.Sprintf("only the preview version %q matched the selection criteria", *selectedVersion.Version))
1916+
}
1917+
1918+
// Throwing error if we could not match the version with the available versions
1919+
if selectedVersion == nil {
1920+
return nil, false, fmt.Errorf("provided version is not one of the available kubernetes versions, available versions are: %s", strings.Join(availableVersionsArray, ","))
1921+
}
1922+
1923+
return selectedVersion.Version, deprecated, nil
1924+
}
1925+
1926+
func selectFullVersion(availableVersions []ske.KubernetesVersion, kubernetesVersionMin string) (availableVersionsArray []string, selectedVersion *ske.KubernetesVersion) {
1927+
for _, versionCandidate := range availableVersions {
1928+
if versionCandidate.State == nil || versionCandidate.Version == nil {
18871929
continue
18881930
}
1889-
availableVersionsArray = append(availableVersionsArray, *v.Version)
1890-
vPreffixed := "v" + *v.Version
1931+
availableVersionsArray = append(availableVersionsArray, *versionCandidate.Version)
1932+
vPrefixed := "v" + *versionCandidate.Version
18911933

1892-
if fullVersion {
1893-
// [MAJOR].[MINOR].[PATCH] version provided, match available version
1894-
if semver.Compare(vPreffixed, providedVersionPrefixed) == 0 {
1895-
versionUsed = v.Version
1896-
state = v.State
1897-
break
1898-
}
1899-
} else {
1900-
// [MAJOR].[MINOR] version provided, get the latest non-preview patch version
1901-
if semver.MajorMinor(vPreffixed) == semver.MajorMinor(providedVersionPrefixed) &&
1902-
(semver.Compare(vPreffixed, providedVersionPrefixed) == 1 || semver.Compare(vPreffixed, providedVersionPrefixed) == 0) &&
1903-
(v.State != nil && *v.State != VersionStatePreview) {
1904-
versionUsed = v.Version
1905-
state = v.State
1934+
// [MAJOR].[MINOR].[PATCH] version provided, match available version
1935+
if semver.Compare(vPrefixed, kubernetesVersionMin) == 0 {
1936+
selectedVersion = &versionCandidate
1937+
break
1938+
}
1939+
}
1940+
return availableVersionsArray, selectedVersion
1941+
}
1942+
1943+
func selectMatchingVersion(availableVersions []ske.KubernetesVersion, kubernetesVersionMin string) (availableVersionsArray []string, selectedVersion *ske.KubernetesVersion) {
1944+
sortK8sVersions(availableVersions)
1945+
for _, candidateVersion := range availableVersions {
1946+
if candidateVersion.State == nil || candidateVersion.Version == nil {
1947+
continue
1948+
}
1949+
availableVersionsArray = append(availableVersionsArray, *candidateVersion.Version)
1950+
vPreffixed := "v" + *candidateVersion.Version
1951+
1952+
// [MAJOR].[MINOR] version provided, get the latest non-preview patch version
1953+
if semver.MajorMinor(vPreffixed) == semver.MajorMinor(kubernetesVersionMin) &&
1954+
(semver.Compare(vPreffixed, kubernetesVersionMin) >= 0) &&
1955+
(candidateVersion.State != nil) {
1956+
// take the current version as a candidate, if we have no other version inspected before
1957+
// OR the previously found version was a preview version
1958+
if selectedVersion == nil || (isSupported(&candidateVersion) && isPreview(selectedVersion)) {
1959+
selectedVersion = &candidateVersion
19061960
}
1961+
// all other cases are ignored
19071962
}
19081963
}
1964+
return availableVersionsArray, selectedVersion
1965+
}
19091966

1910-
if versionUsed != nil {
1911-
deprecated = strings.EqualFold(*state, VersionStateDeprecated)
1967+
func isDeprecated(v *ske.KubernetesVersion) bool {
1968+
if v == nil {
1969+
return false
19121970
}
19131971

1914-
// Throwing error if we could not match the version with the available versions
1915-
if versionUsed == nil {
1916-
return nil, false, fmt.Errorf("provided version is not one of the available kubernetes versions, available versions are: %s", strings.Join(availableVersionsArray, ","))
1972+
if v.State == nil {
1973+
return false
19171974
}
19181975

1919-
return versionUsed, deprecated, nil
1976+
return *v.State == VersionStateDeprecated
1977+
}
1978+
1979+
func isPreview(v *ske.KubernetesVersion) bool {
1980+
if v == nil {
1981+
return false
1982+
}
1983+
1984+
if v.State == nil {
1985+
return false
1986+
}
1987+
1988+
return *v.State == VersionStatePreview
1989+
}
1990+
1991+
func isSupported(v *ske.KubernetesVersion) bool {
1992+
if v == nil {
1993+
return false
1994+
}
1995+
1996+
if v.State == nil {
1997+
return false
1998+
}
1999+
2000+
return *v.State == VersionStateSupported
19202001
}
19212002

19222003
func getLatestSupportedKubernetesVersion(versions []ske.KubernetesVersion) (*string, error) {

0 commit comments

Comments
 (0)