From cb5da5627187d73e238432360124babdf8c17c6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=A5=E5=8D=B3?= Date: Tue, 14 Apr 2026 17:41:31 +0800 Subject: [PATCH] resource/alicloud_cs_kubernetes_addon: fix get next_version; data-source/alicloud_cs_kubernetes_addons: fix get next_version. --- ...resource_alicloud_cs_managed_kubernetes.go | 53 -------------- ...rce_alicloud_cs_managed_kubernetes_test.go | 69 +++++++++++++++++++ alicloud/service_alicloud_cs.go | 67 +++++++++++++++++- go.mod | 2 +- 4 files changed, 135 insertions(+), 56 deletions(-) diff --git a/alicloud/resource_alicloud_cs_managed_kubernetes.go b/alicloud/resource_alicloud_cs_managed_kubernetes.go index 0a356505f40d..3d740e396f4a 100644 --- a/alicloud/resource_alicloud_cs_managed_kubernetes.go +++ b/alicloud/resource_alicloud_cs_managed_kubernetes.go @@ -1562,59 +1562,6 @@ func updateKubernetesClusterTag(d *schema.ResourceData, meta interface{}) error return nil } -// versionCompare check version, -// if cueVersion is newer than neededVersion return 1 -// if curVersion is equal neededVersion return 0 -// if curVersion is older than neededVersion return -1 -// example: neededVersion = 1.20.11-aliyun.1, curVersion = 1.22.3-aliyun.1, it will return 1 -func versionCompare(neededVersion, curVersion string) (int, error) { - if neededVersion == "" || curVersion == "" { - if neededVersion == "" && curVersion == "" { - return 0, nil - } else { - if neededVersion == "" { - return 1, nil - } else { - return -1, nil - } - } - } - - // 取出版本号 - regx := regexp.MustCompile(`[0-9]+\.[0-9]+\.[0-9]+`) - neededVersion = regx.FindString(neededVersion) - curVersion = regx.FindString(curVersion) - - currentVersions := strings.Split(neededVersion, ".") - newVersions := strings.Split(curVersion, ".") - - compare := 0 - - for index, val := range currentVersions { - newVal := newVersions[index] - v1, err1 := strconv.Atoi(val) - v2, err2 := strconv.Atoi(newVal) - - if err1 != nil || err2 != nil { - return -2, fmt.Errorf("NotSupport, current cluster version is not support: %s", curVersion) - } - - if v1 > v2 { - compare = -1 - } else if v1 == v2 { - compare = 0 - } else { - compare = 1 - } - - if compare != 0 { - break - } - } - - return compare, nil -} - func updateControlPlaneLog(d *schema.ResourceData, meta interface{}) error { request := &roacs.UpdateControlPlaneLogRequest{} client := meta.(*connectivity.AliyunClient) diff --git a/alicloud/resource_alicloud_cs_managed_kubernetes_test.go b/alicloud/resource_alicloud_cs_managed_kubernetes_test.go index be61bbad8c5f..9a56a9e21ff2 100644 --- a/alicloud/resource_alicloud_cs_managed_kubernetes_test.go +++ b/alicloud/resource_alicloud_cs_managed_kubernetes_test.go @@ -1154,4 +1154,73 @@ variable "cluster_type" { `, name) } +func Test_versionCompare(t *testing.T) { + tests := []struct { + name string + oldVersion string + newVersion string + expectedResult int + expectError bool + }{ + // basic semver + {name: "equal versions", oldVersion: "1.2.3", newVersion: "1.2.3", expectedResult: 0}, + {name: "new version newer patch", oldVersion: "1.2.3", newVersion: "1.2.4", expectedResult: 1}, + {name: "new version older patch", oldVersion: "1.2.4", newVersion: "1.2.3", expectedResult: -1}, + {name: "new version newer minor", oldVersion: "1.2.3", newVersion: "1.3.0", expectedResult: 1}, + {name: "new version older minor", oldVersion: "1.3.0", newVersion: "1.2.3", expectedResult: -1}, + {name: "new version newer major", oldVersion: "1.2.3", newVersion: "2.0.0", expectedResult: 1}, + {name: "new version older major", oldVersion: "2.0.0", newVersion: "1.2.3", expectedResult: -1}, + + // v prefix + {name: "v prefix both", oldVersion: "v1.2.3", newVersion: "v1.2.4", expectedResult: 1}, + {name: "v prefix old only", oldVersion: "v1.2.3", newVersion: "1.2.4", expectedResult: 1}, + {name: "v prefix new only", oldVersion: "1.2.3", newVersion: "v1.2.4", expectedResult: 1}, + {name: "v prefix equal", oldVersion: "v1.2.3", newVersion: "v1.2.3", expectedResult: 0}, + + // pre-release suffix (per semver: release > pre-release, e.g. 1.2.3 > 1.2.3-alpha.1) + {name: "release vs pre-release", oldVersion: "1.2.3-alpha.1", newVersion: "1.2.3", expectedResult: 1}, + {name: "pre-release vs release", oldVersion: "1.2.3", newVersion: "1.2.3-alpha.1", expectedResult: -1}, + {name: "pre-release equal", oldVersion: "1.2.3-alpha.1", newVersion: "1.2.3-alpha.1", expectedResult: 0}, + {name: "pre-release numeric diff", oldVersion: "1.2.3-alpha.1", newVersion: "1.2.3-alpha.2", expectedResult: 1}, + {name: "pre-release string diff", oldVersion: "1.2.3-alpha.1", newVersion: "1.2.3-beta.1", expectedResult: 1}, + {name: "pre-release with aliyun", oldVersion: "1.9.3-aliyun.1", newVersion: "1.9.7-aliyun.2", expectedResult: 1}, + {name: "pre-release newer major version", oldVersion: "1.9.3-aliyun.1", newVersion: "2.0.0-aliyun.1", expectedResult: 1}, + + // apsara format + {name: "apsara same main version, suffix newer", oldVersion: "v1.31.0-apsara.6.11.6.ad796663", newVersion: "v1.31.0-apsara.6.11.7.17d202a9", expectedResult: 1}, + {name: "apsara same main version, suffix older", oldVersion: "v1.31.0-apsara.6.11.7.17d202a9", newVersion: "v1.31.0-apsara.6.11.6.ad796663", expectedResult: -1}, + {name: "apsara same numeric suffix, diff commit hash", oldVersion: "v1.31.0-apsara.6.11.6.ad796663", newVersion: "v1.31.0-apsara.6.11.6.bf123456", expectedResult: 1}, + {name: "apsara different main version", oldVersion: "v1.30.0-apsara.6.11.6.ad796663", newVersion: "v1.31.0-apsara.6.11.6.ad796663", expectedResult: 1}, + + // empty versions + {name: "both empty", oldVersion: "", newVersion: "", expectedResult: 0}, + {name: "old empty", oldVersion: "", newVersion: "1.2.3", expectedResult: 1}, + {name: "new empty", oldVersion: "1.2.3", newVersion: "", expectedResult: -1}, + + // invalid format + {name: "invalid old version", oldVersion: "latest", newVersion: "1.2.3", expectError: true}, + {name: "invalid new version", oldVersion: "1.2.3", newVersion: "latest", expectError: true}, + {name: "both invalid", oldVersion: "abc", newVersion: "xyz", expectError: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := versionCompare(tt.oldVersion, tt.newVersion) + if tt.expectError { + if err == nil { + t.Errorf("expected error but got nil, result=%d", result) + } + return + } + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + if result != tt.expectedResult { + t.Errorf("versionCompare(%q, %q) = %d, want %d", tt.oldVersion, tt.newVersion, result, tt.expectedResult) + } + }) + } +} + // Test Ack Cluster. <<< Resource test cases, automatically generated. diff --git a/alicloud/service_alicloud_cs.go b/alicloud/service_alicloud_cs.go index 7f76f7ccf397..0a9ccd16a0bb 100644 --- a/alicloud/service_alicloud_cs.go +++ b/alicloud/service_alicloud_cs.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/Masterminds/semver" "github.com/alibabacloud-go/cs-20151215/v7/client" "github.com/alibabacloud-go/tea/tea" "github.com/aliyun/terraform-provider-alicloud/alicloud/connectivity" @@ -428,7 +429,15 @@ func (s *CsClient) DescribeCsKubernetesAllAvailableAddons(clusterId string) (map // DescribeAddon addonInfoDetail, err := s.DescribeAddon(clusterId, name, addonInstance.Version) if err != nil { - return nil, WrapErrorf(err, DefaultErrorMsg, ResourceAlicloudCSKubernetesAddon, "DescribeAddon", err) + if IsExpectedErrors(err, []string{"AddonNotFound"}) { + // If the specified version is not found, retry without version + addonInfoDetail, err = s.DescribeAddon(clusterId, name, "") + if err != nil { + return nil, WrapErrorf(err, DefaultErrorMsg, ResourceAlicloudCSKubernetesAddon, "DescribeAddon", err) + } + } else { + return nil, WrapErrorf(err, DefaultErrorMsg, ResourceAlicloudCSKubernetesAddon, "DescribeAddon", err) + } } if addonInfoDetail == nil || addonInfoDetail.Body == nil { @@ -436,6 +445,13 @@ func (s *CsClient) DescribeCsKubernetesAllAvailableAddons(clusterId string) (map } addon.NextVersion = getNextVersion(addonInfoDetail) + // If retried without version, use the response version as next_version only if it's newer + if addon.NextVersion == "" && addonInfoDetail.Body.Version != nil { + targetVersion := tea.StringValue(addonInfoDetail.Body.Version) + if cmp, err := versionCompare(addonInstance.Version, targetVersion); err == nil && cmp == 1 { + addon.NextVersion = targetVersion + } + } // Update if addon installed addon.Version = addonInstance.Version @@ -487,7 +503,15 @@ func (s *CsClient) DescribeCsKubernetesAddon(id string) (*Component, error) { addonInfo, err := s.DescribeAddon(clusterId, addonName, addonInstance.Version) if err != nil { - return nil, err + if IsExpectedErrors(err, []string{"AddonNotFound"}) { + // If the specified version is not found, retry without version + addonInfo, err = s.DescribeAddon(clusterId, addonName, "") + if err != nil { + return nil, err + } + } else { + return nil, err + } } if addonInfo == nil || addonInfo.Body == nil { @@ -500,6 +524,13 @@ func (s *CsClient) DescribeCsKubernetesAddon(id string) (*Component, error) { if nextVersion := getNextVersion(addonInfo); nextVersion != "" { addonInstance.NextVersion = nextVersion } + // If retried without version, use the response version as next_version only if it's newer + if addonInstance.NextVersion == addonInstance.Version && addonInfo.Body.Version != nil { + targetVersion := tea.StringValue(addonInfo.Body.Version) + if cmp, err := versionCompare(addonInstance.Version, targetVersion); err == nil && cmp == 1 { + addonInstance.NextVersion = targetVersion + } + } addonInstance.CanUpgrade = addonInstance.Version != addonInstance.NextVersion addonInstance.SupportedActions = tea.StringSliceValue(addonInfo.Body.SupportedActions) @@ -517,6 +548,38 @@ func getNextVersion(addonInfo *client.DescribeAddonResponse) string { return "" } +// versionCompare compares two semantic versions using github.com/Masterminds/semver. +// Supports formats: "1.2.3", "v1.2.3", "1.2.3-aliyun.1", "v1.31.0-apsara.6.11.6.ad796663", etc. +// Returns: +// +// 1 if newVersion > oldVersion +// 0 if newVersion == oldVersion +// -1 if newVersion < oldVersion +// +// Example: versionCompare("1.20.11-aliyun.1", "1.22.3-aliyun.1") returns 1 +func versionCompare(oldVersion, newVersion string) (int, error) { + if oldVersion == "" || newVersion == "" { + if oldVersion == "" && newVersion == "" { + return 0, nil + } + if oldVersion == "" { + return 1, nil + } + return -1, nil + } + + oldSemver, err := semver.NewVersion(oldVersion) + if err != nil { + return -2, fmt.Errorf("failed to parse version %q: %v", oldVersion, err) + } + newSemver, err := semver.NewVersion(newVersion) + if err != nil { + return -2, fmt.Errorf("failed to parse version %q: %v", newVersion, err) + } + + return newSemver.Compare(oldSemver), nil +} + func (s *CsClient) CsKubernetesAddonTaskRefreshFunc(clusterId string, addonName string, failStates []string) resource.StateRefreshFunc { return func() (interface{}, string, error) { object, err := s.DescribeCsKubernetesAddonStatus(clusterId, addonName) diff --git a/go.mod b/go.mod index ced52ce1888b..d61a7cd5e2ce 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,7 @@ require ( cloud.google.com/go/iam v1.1.7 // indirect cloud.google.com/go/storage v1.39.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver v1.5.0 // indirect + github.com/Masterminds/semver v1.5.0 github.com/Masterminds/sprig v2.22.0+incompatible // indirect github.com/PaesslerAG/gval v1.0.0 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect