Skip to content

Commit 1867d28

Browse files
authored
Auto-detect CRD versions to deprecate (#5050)
* Auto-detect CRD versions to deprecate Fixes #5045. * Fix tests and PR feedback
1 parent debd8f7 commit 1867d28

File tree

4 files changed

+581
-31
lines changed

4 files changed

+581
-31
lines changed

v2/cmd/asoctl/internal/crd/cleaner.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,8 @@ func (c *Cleaner) Run(ctx context.Context) error {
9797
asoCRDsSeen++
9898
newStoredVersions, deprecatedVersions := crdmanagement.GetDeprecatedStorageVersions(crd)
9999

100-
// If there is no new version found other than the matched version, we short circuit here, as there is no updated version found in the CRDs
101-
if len(newStoredVersions) <= 0 {
102-
// TODO: test?
100+
// If there is no new version found other than alpha/beta, it means there wasn't a newer version installed/reconciled yet and we short circuit here, as there is no updated version found in the CRDs
101+
if len(newStoredVersions) == 1 && (strings.HasPrefix(newStoredVersions[0], "v1alpha") || strings.HasPrefix(newStoredVersions[0], "v1beta")) {
103102
return eris.Errorf("it doesn't look like your version of ASO is one that supports deprecating CRD %q, versions %q. Have you upgraded ASO yet?", crd.Name, deprecatedVersions)
104103
}
105104

v2/internal/crdmanagement/deprecation.go

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,49 +7,33 @@ package crdmanagement
77

88
import (
99
"regexp"
10+
"slices"
1011

1112
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1213
)
1314

14-
var deprecatedVersionRegexp = regexp.MustCompile(`((v1alpha1api|v1beta)\d{8}(preview)?(storage)?|v1beta1)`) // handcrafted (non-ARM) resources have v1beta1 version
15-
16-
// deprecatedVersionsMap is a map of crd name to a collection of deprecated versions
17-
var deprecatedVersionsMap = map[string][]string{
18-
"trustedaccessrolebindings.containerservice.azure.com": {"v1api20230202previewstorage"},
19-
"managedclusters.containerservice.azure.com": {"v1api20210501storage", "v1api20231102previewstorage"},
20-
"managedclustersagentpools.containerservice.azure.com": {"v1api20210501storage", "v1api20231102previewstorage"},
21-
}
22-
23-
// IsVersionDeprecated checks if a given version of a CRD is deprecated.
24-
func IsVersionDeprecated(crd apiextensions.CustomResourceDefinition, version string) bool {
25-
if deprecatedVersionRegexp.MatchString(version) {
26-
return true
27-
}
28-
29-
for _, deprecatedVersion := range deprecatedVersionsMap[crd.Name] {
30-
if deprecatedVersion == version {
31-
return true
32-
}
33-
}
34-
35-
return false
36-
}
15+
var versionRegexp = regexp.MustCompile(`^v(1api|1alpha1api|1beta)?(\d{8}|\d)(preview)?(storage)?$`)
3716

3817
// GetDeprecatedStorageVersions returns a new list of storedVersions by removing the deprecated versions
3918
// The first return value is the list of versions that are NOT deprecated.
4019
// The second return value is the list of versions that ARE deprecated.
4120
func GetDeprecatedStorageVersions(crd apiextensions.CustomResourceDefinition) ([]string, []string) {
4221
storedVersions := crd.Status.StoredVersions
4322

23+
if len(storedVersions) <= 1 {
24+
// No versions to remove
25+
return storedVersions, nil
26+
}
27+
28+
maxVersion := slices.MaxFunc(storedVersions, CompareVersions)
4429
newStoredVersions := make([]string, 0, len(storedVersions))
4530
var removedVersions []string
4631
for _, version := range storedVersions {
47-
if IsVersionDeprecated(crd, version) {
32+
if version == maxVersion {
33+
newStoredVersions = append(newStoredVersions, version)
34+
} else {
4835
removedVersions = append(removedVersions, version)
49-
continue
5036
}
51-
52-
newStoredVersions = append(newStoredVersions, version)
5337
}
5438

5539
return newStoredVersions, removedVersions
@@ -66,3 +50,74 @@ func GetAllDeprecatedStorageVersions(crds map[string]apiextensions.CustomResourc
6650
}
6751
return result
6852
}
53+
54+
// CompareVersions compares two group version strings, for example: v1api20230101preview, v1api20220101, v20220101, v1api20220101storage, and v20220101storage
55+
// and returns -1 if a < b, 0 if a == b, and 1 if a > b. The storage suffix is ignored in the comparison.
56+
// Preview versions are considered less than non-preview versions.
57+
func CompareVersions(a string, b string) int {
58+
matchA := versionRegexp.FindStringSubmatch(a)
59+
matchB := versionRegexp.FindStringSubmatch(b)
60+
61+
if matchA == nil || matchB == nil {
62+
// If either version doesn't match the expected pattern, fall back to string comparison
63+
// TODO: Do we actually want to do this?
64+
if a < b {
65+
return -1
66+
} else if a > b {
67+
return 1
68+
}
69+
return 0
70+
}
71+
72+
// Prefer non-preview over preview
73+
isAPreview := matchA[3] == "preview"
74+
isBPreview := matchB[3] == "preview"
75+
76+
if isAPreview && !isBPreview {
77+
return -1
78+
} else if !isAPreview && isBPreview {
79+
return 1
80+
}
81+
82+
// Prefer everything over v1alpha1apiYYYYMMDD
83+
hasV1Alpha1A := matchA[1] == "1alpha1api"
84+
hasV1Alpha1B := matchB[1] == "1alpha1api"
85+
86+
if hasV1Alpha1A && !hasV1Alpha1B {
87+
return -1
88+
} else if !hasV1Alpha1A && hasV1Alpha1B {
89+
return 1
90+
}
91+
92+
// Prefer everything over v1betaYYYYMMDD
93+
hasV1BetaA := matchA[1] == "1beta"
94+
hasV1BetaB := matchB[1] == "1beta"
95+
96+
if hasV1BetaA && !hasV1BetaB {
97+
return -1
98+
} else if !hasV1BetaA && hasV1BetaB {
99+
return 1
100+
}
101+
102+
// Prefer newer date over older date
103+
dateA := matchA[2]
104+
dateB := matchB[2]
105+
106+
if dateA < dateB {
107+
return -1
108+
} else if dateA > dateB {
109+
return 1
110+
}
111+
112+
// Prefer vYYYYMMDD over v1apiYYYYMMDD
113+
hasV1ApiA := matchA[1] == "1api"
114+
hasV1ApiB := matchB[1] == "1api"
115+
116+
if hasV1ApiA && !hasV1ApiB {
117+
return -1
118+
} else if !hasV1ApiA && hasV1ApiB {
119+
return 1
120+
}
121+
122+
return 0
123+
}

0 commit comments

Comments
 (0)