diff --git a/go.mod b/go.mod index 5af07361..d35db5df 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/sovereignCloudStack/cluster-stack-provider-openstack go 1.21 require ( - github.com/SovereignCloudStack/cluster-stack-operator v0.1.0-alpha.2 + github.com/SovereignCloudStack/cluster-stack-operator v0.1.0-alpha.3 github.com/google/go-github/v52 v52.0.0 github.com/gophercloud/gophercloud v1.9.0 github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 @@ -16,7 +16,7 @@ require ( k8s.io/klog/v2 v2.100.1 sigs.k8s.io/cluster-api v1.6.0 sigs.k8s.io/cluster-api-provider-openstack v0.9.0 - sigs.k8s.io/controller-runtime v0.16.3 + sigs.k8s.io/controller-runtime v0.16.5 sigs.k8s.io/yaml v1.4.0 ) diff --git a/go.sum b/go.sum index d2ac8ae3..f38b4a4b 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= -github.com/SovereignCloudStack/cluster-stack-operator v0.1.0-alpha.2 h1:23sNJB4z0wiguCeG0W+GdusBrRNYGcKsUYLZo01wZNs= -github.com/SovereignCloudStack/cluster-stack-operator v0.1.0-alpha.2/go.mod h1:qXyqN2aZ+JCtVIVMNdr736b+3XNmaOhsXjGZuMUUUp0= +github.com/SovereignCloudStack/cluster-stack-operator v0.1.0-alpha.3 h1:FzgRYkdQNc2Frsna4XXPij83PJNuSn4UUa+JAkabCdE= +github.com/SovereignCloudStack/cluster-stack-operator v0.1.0-alpha.3/go.mod h1:1nOm+nYIGs8kansssJC8J/WOMbbbooR4bUJBVae7r90= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230321174746-8dcc6526cfb1 h1:X8MJ0fnN5FPdcGF5Ij2/OW+HgiJrRg3AfHAx1PJtIzM= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230321174746-8dcc6526cfb1/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= @@ -324,8 +324,8 @@ sigs.k8s.io/cluster-api v1.6.0 h1:2bhVSnUbtWI8taCjd9lGiHExsRUpKf7Z1fXqi/IwYx4= sigs.k8s.io/cluster-api v1.6.0/go.mod h1:LB7u/WxiWj4/bbpHNOa1oQ8nq0MQ5iYlD0pGfRSBGLI= sigs.k8s.io/cluster-api-provider-openstack v0.9.0 h1:ScwZIfT1kI88+qMzeO7ppMP9DvEzrfLHuYPg2p1mcho= sigs.k8s.io/cluster-api-provider-openstack v0.9.0/go.mod h1:ecR9lx4XbOr3Gg2CGNgM3wguuV6l31Nd5rUccE+xjKs= -sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= -sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= +sigs.k8s.io/controller-runtime v0.16.5 h1:yr1cEJbX08xsTW6XEIzT13KHHmIyX8Umvme2cULvFZw= +sigs.k8s.io/controller-runtime v0.16.5/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= diff --git a/vendor/github.com/SovereignCloudStack/cluster-stack-operator/pkg/clusterstack/clusterstack.go b/vendor/github.com/SovereignCloudStack/cluster-stack-operator/pkg/clusterstack/clusterstack.go index 38145803..37cead13 100644 --- a/vendor/github.com/SovereignCloudStack/cluster-stack-operator/pkg/clusterstack/clusterstack.go +++ b/vendor/github.com/SovereignCloudStack/cluster-stack-operator/pkg/clusterstack/clusterstack.go @@ -55,8 +55,57 @@ var ( ErrInvalidName = fmt.Errorf("invalid name") ) -// NewFromString returns a ClusterStack based on a cluster stack string. -func NewFromString(str string) (ClusterStack, error) { +// NewFromClusterClassProperties returns a ClusterStack based on a cluster stack string. +// e.g. - "docker-ferrol-1-27-v1", "docker-ferrol-1-27-v1-alpha.1", etc. +func NewFromClusterClassProperties(str string) (ClusterStack, error) { + splitted := strings.Split(str, Separator) + if len(splitted) != 5 && len(splitted) != 6 { + return ClusterStack{}, ErrInvalidFormat + } + + clusterStack := ClusterStack{ + Provider: splitted[0], + Name: splitted[1], + } + + if clusterStack.Provider == "" { + return ClusterStack{}, ErrInvalidProvider + } + + if clusterStack.Name == "" { + return ClusterStack{}, ErrInvalidName + } + + var err error + + clusterStack.KubernetesVersion, err = kubernetesversion.New(splitted[2], splitted[3]) + if err != nil { + return ClusterStack{}, fmt.Errorf("failed to create Kubernetes version from %s-%s: %w", splitted[2], splitted[3], err) + } + + var versionString string + if len(splitted) == 5 { + // e.g. myprovider-myclusterstack-1-26-v1 + versionString = splitted[4] + } else if len(splitted) == 6 { + // e.g. myprovider-myclusterstack-1-26-v1-alpha.0 + versionString = strings.Join(splitted[4:6], Separator) + } + + // version string like v1-alpha.0 + v, err := version.New(versionString) + if err != nil { + return ClusterStack{}, fmt.Errorf("failed to create version from %s: %w", versionString, err) + } + + clusterStack.Version = v + + return clusterStack, nil +} + +// NewFromClusterStackReleaseProperties returns a ClusterStack based on a cluster stack string. +// e.g. - "docker-ferrol-1-27-v1", "docker-ferrol-1-27-v1-alpha-1", etc. +func NewFromClusterStackReleaseProperties(str string) (ClusterStack, error) { splitted := strings.Split(str, Separator) if len(splitted) != 5 && len(splitted) != 7 { return ClusterStack{}, ErrInvalidFormat @@ -91,7 +140,8 @@ func NewFromString(str string) (ClusterStack, error) { versionString = strings.Join(splitted[4:7], Separator) } - v, err := version.New(versionString) + // version string like v1-alpha-0 + v, err := version.ParseVersionString(versionString) if err != nil { return ClusterStack{}, fmt.Errorf("failed to create version from %s: %w", versionString, err) } @@ -101,7 +151,8 @@ func NewFromString(str string) (ClusterStack, error) { return clusterStack, nil } -// New returns a ClusterStack based on a cluster stack string. +// New returns a ClusterStack based on a cluster stack properties and the version is in the form of - +// "v1", "v1-alpha.1", etc. func New(provider, name, kubernetesVersion, csVersion string) (ClusterStack, error) { k8sVersion, err := kubernetesversion.NewFromString(kubernetesVersion) if err != nil { diff --git a/vendor/github.com/SovereignCloudStack/cluster-stack-operator/pkg/release/release.go b/vendor/github.com/SovereignCloudStack/cluster-stack-operator/pkg/release/release.go index 6777e76d..473f668f 100644 --- a/vendor/github.com/SovereignCloudStack/cluster-stack-operator/pkg/release/release.go +++ b/vendor/github.com/SovereignCloudStack/cluster-stack-operator/pkg/release/release.go @@ -24,7 +24,8 @@ import ( "strings" "github.com/SovereignCloudStack/cluster-stack-operator/pkg/clusterstack" - "gopkg.in/yaml.v2" + "github.com/SovereignCloudStack/cluster-stack-operator/pkg/version" + "gopkg.in/yaml.v3" ) // Release contains information for ClusterStack release. @@ -57,8 +58,9 @@ const ( func New(tag, downloadPath string) (Release, bool, error) { // downloadPath is the path where the release is downloaded. // The path is of the form: /// + // For example: /tmp/downloads/cluster-stacks/docker-ferrol-1-26-v2/ downloadPath = filepath.Join(downloadPath, clusterStackSuffix, tag) - cs, err := clusterstack.NewFromString(tag) + cs, err := clusterstack.NewFromClusterStackReleaseProperties(tag) if err != nil { return Release{}, false, fmt.Errorf("failed to parse cluster stack release: %w", err) } @@ -92,6 +94,18 @@ func New(tag, downloadPath string) (Release, bool, error) { return rel, false, nil } +// ConvertFromClusterClassToClusterStackFormat converts `docker-ferrol-1-27-v0-sha.3960147` way to +// `docker-ferrol-1-27-v0-sha-3960147`. +func ConvertFromClusterClassToClusterStackFormat(input string) string { + parts := strings.Split(input, ".") + + if len(parts) == 2 { + return fmt.Sprintf("%s-%s", parts[0], parts[1]) + } + + return input +} + func ensureMetadata(downloadPath, metadataFileName string) (Metadata, error) { // Read the metadata.yaml file from the release. metadataPath := filepath.Join(downloadPath, metadataFileName) @@ -107,6 +121,25 @@ func ensureMetadata(downloadPath, metadataFileName string) (Metadata, error) { return Metadata{}, fmt.Errorf("failed to unmarshal metadata: %w", err) } + // Normalize the versions of metadata from v1-alpha.1 to v1-alpha-1 format. + metaClusterStackVersion, err := version.New(metadata.Versions.ClusterStack) + if err != nil { + return Metadata{}, fmt.Errorf("failed to parse ClusterStack version from metadata: %w", err) + } + metadata.Versions.ClusterStack = metaClusterStackVersion.String() + + metaClusterAddonVersion, err := version.New(metadata.Versions.Components.ClusterAddon) + if err != nil { + return Metadata{}, fmt.Errorf("failed to parse ClusterAddon version from metadata: %w", err) + } + metadata.Versions.Components.ClusterAddon = metaClusterAddonVersion.String() + + metaNodeImageVersion, err := version.New(metadata.Versions.Components.NodeImage) + if err != nil { + return Metadata{}, fmt.Errorf("failed to parse NodeImage version from metadata: %w", err) + } + metadata.Versions.Components.NodeImage = metaNodeImageVersion.String() + return metadata, nil } @@ -165,13 +198,15 @@ func (r *Release) Validate() error { // clusterAddonChartName returns the helm chart name for cluster addon. func (r *Release) clusterAddonChartName() string { - return fmt.Sprintf("%s-%s-%s-cluster-addon-%s", r.ClusterStack.Provider, r.ClusterStack.Name, r.ClusterStack.KubernetesVersion, r.Meta.Versions.Components.ClusterAddon) + clusterAddonVersion, _ := version.ParseVersionString(r.Meta.Versions.Components.ClusterAddon) + return fmt.Sprintf("%s-%s-%s-cluster-addon-%s", r.ClusterStack.Provider, r.ClusterStack.Name, r.ClusterStack.KubernetesVersion, clusterAddonVersion.StringWithDot()) } // ClusterAddonChartPath returns the helm chart name from the given path. func (r *Release) ClusterAddonChartPath() string { // we ignore the error here, since we already checked for the presence of the chart. - path, _ := r.helmChartNamePath(r.clusterAddonChartName()) + name := r.clusterAddonChartName() + path, _ := r.helmChartNamePath(name) return path } @@ -182,7 +217,7 @@ func (r *Release) ClusterAddonValuesPath() string { // clusterClassChartName returns the helm chart name for cluster class. func (r *Release) clusterClassChartName() string { - return fmt.Sprintf("%s-%s-%s-cluster-class-%s", r.ClusterStack.Provider, r.ClusterStack.Name, r.ClusterStack.KubernetesVersion, r.ClusterStack.Version.String()) + return fmt.Sprintf("%s-%s-%s-cluster-class-%s", r.ClusterStack.Provider, r.ClusterStack.Name, r.ClusterStack.KubernetesVersion, r.ClusterStack.Version.StringWithDot()) } // ClusterClassChartPath returns the absolute helm chart path for cluster class. diff --git a/vendor/github.com/SovereignCloudStack/cluster-stack-operator/pkg/version/channel.go b/vendor/github.com/SovereignCloudStack/cluster-stack-operator/pkg/version/channel.go index 3acc415c..1dd33c6f 100644 --- a/vendor/github.com/SovereignCloudStack/cluster-stack-operator/pkg/version/channel.go +++ b/vendor/github.com/SovereignCloudStack/cluster-stack-operator/pkg/version/channel.go @@ -23,18 +23,12 @@ type Channel string const ( // ChannelStable is the stable channel. ChannelStable = Channel("stable") - // ChannelAlpha is the alpha channel. - ChannelAlpha = Channel("alpha") - // ChannelBeta is the beta channel. - ChannelBeta = Channel("beta") - // ChannelRC is the rc channel. - ChannelRC = Channel("rc") + // ChannelCustom is the custom channel where git hash is used in the versioning. + ChannelCustom = Channel("custom") ) // IsValid returns true if the release channel is valid. func (c Channel) IsValid() bool { return c == ChannelStable || - c == ChannelAlpha || - c == ChannelBeta || - c == ChannelRC + c == ChannelCustom } diff --git a/vendor/github.com/SovereignCloudStack/cluster-stack-operator/pkg/version/version.go b/vendor/github.com/SovereignCloudStack/cluster-stack-operator/pkg/version/version.go index 69175d10..ec77f7cf 100644 --- a/vendor/github.com/SovereignCloudStack/cluster-stack-operator/pkg/version/version.go +++ b/vendor/github.com/SovereignCloudStack/cluster-stack-operator/pkg/version/version.go @@ -34,27 +34,28 @@ import ( type Version struct { Major int Channel Channel - Patch int + Patch string } -// New returns a Version struct from a version string -// Sample allowed inputs: "v1-alpha-1", "v1", "v1-alpha-0" -// Sample disallowed inputs: "v1-alpha", "v1-alpha-1.0", "v1-alpha-1.0.0", "v1-alpha.", "v1.0-alpha.1". -func New(version string) (Version, error) { - var major, patch int - var err error +// ParseVersionString returns a Version struct from a version string like - +// "v1", "v1-alpha-1", "v1-beta-3", etc. +func ParseVersionString(version string) (Version, error) { + var ( + major int + patch string + err error + ) channel := ChannelStable - re := regexp.MustCompile(`^v\d+(-\b\w+\b-\d+)?$`) - match := re.FindStringSubmatch(version) - - if len(match) == 0 { + re := regexp.MustCompile(`^v\d+(-\b\w+\b\-\w+)?$`) + match := re.MatchString(version) + if !match { return Version{}, fmt.Errorf("invalid version string %s", version) } // match[0] is the entire string e.g "v1-alpha-1" or "v1" // split match[0] with "-" as the delimiter - ver := strings.Split(match[0], "-") + ver := strings.Split(version, "-") // ver[0] is the major version // trim the "v" prefix and then convert to int @@ -67,9 +68,57 @@ func New(version string) (Version, error) { // ver[2] is the patch if len(ver) == 3 { channel = Channel(ver[1]) - if patch, err = strconv.Atoi(ver[2]); err != nil { - return Version{}, fmt.Errorf("invalid patch value in version %s", ver[2]) + patch = ver[2] + } + clusterStackVersion := Version{ + Major: major, + Channel: channel, + Patch: patch, + } + if err := clusterStackVersion.Validate(); err != nil { + return Version{}, err + } + + return clusterStackVersion, nil +} + +// New returns a Version struct from a version string +// Sample allowed inputs: "v1-alpha.1", "v1", "v1-alpha.0" +// Sample disallowed inputs: "v1-alpha", "v1-alpha-1.0", "v1-alpha-1.0.0", "v1-alpha.", "v1.0-alpha.1". +func New(version string) (Version, error) { + var ( + major int + patch string + err error + ) + channel := ChannelStable + + re := regexp.MustCompile(`^v\d+(-\b\w+\b\.\w+)?$`) + match := re.MatchString(version) + if !match { + return Version{}, fmt.Errorf("invalid version string %s", version) + } + + // match[0] is the entire string e.g "v1-alpha.1" or "v1" + // split match[0] with "-" as the delimiter + ver := strings.Split(version, "-") + + // ver[0] is the major version + // trim the "v" prefix and then convert to int + if major, err = strconv.Atoi(strings.TrimPrefix(ver[0], "v")); err != nil { + return Version{}, fmt.Errorf("invalid major version %s", ver[0]) + } + + // If the length of ver is 2, then the version string is of the form "v1-alpha.1", and split it - + // ver[0] is the channel + // ver[1] is the patch + if len(ver) == 2 { + splittedChannelPatch := strings.Split(ver[1], ".") + if len(splittedChannelPatch) != 2 { + return Version{}, fmt.Errorf("invalid version string %s", version) } + channel = Channel(splittedChannelPatch[0]) + patch = splittedChannelPatch[1] } clusterStackVersion := Version{ @@ -86,13 +135,15 @@ func New(version string) (Version, error) { // FromReleaseTag returns a Version struct from a release tag string. func FromReleaseTag(releaseTag string) (Version, error) { v := strings.Split(releaseTag, "-") - if len(v) != 5 && len(v) != 6 { + if len(v) != 5 && len(v) != 7 { return Version{}, fmt.Errorf("invalid release tag %s", releaseTag) } + // for docker-ferrol-1-26-v1 type tag, v[4] is the version if len(v) == 5 { - return New(v[4]) + return ParseVersionString(v[4]) } - return New(fmt.Sprintf("%s-%s", v[4], v[5])) + // for docker-ferrol-1-26-v1-alpha-0 type tag, v[4] is the version and v[5] is the release channel + patch version + return ParseVersionString(fmt.Sprintf("%s-%s-%s", v[4], v[5], v[6])) } // Validate validates the version. @@ -100,15 +151,32 @@ func (csv *Version) Validate() error { if csv.Major < 0 { return fmt.Errorf("major version should be a non-negative integer") } - if !csv.Channel.IsValid() { - return fmt.Errorf("invalid channel: %s", csv.Channel) - } - if csv.Patch < 0 { - return fmt.Errorf("patch version should be a non-negative integer") + + if csv.Channel != ChannelStable { + // Check if the patch is a valid integer + if isInteger(csv.Patch) { + // If it's an integer, check if it's greater than 0 + patchInt, _ := strconv.Atoi(csv.Patch) + if patchInt < 0 { + return fmt.Errorf("patch version should be a non-negative integer") + } + } + + // If it's alpha numeric, check if it's empty + if csv.Patch == "" { + return fmt.Errorf("patch can't empty") + } } + return nil } +// isInteger checks if the given string is a valid integer. +func isInteger(s string) bool { + _, err := strconv.Atoi(s) + return err == nil +} + // Compare compares two Version structs // Returns 1 if csv is greater than input // Returns -1 if csv is less than input @@ -138,9 +206,23 @@ func (csv Version) Compare(input Version) (int, error) { return 0, nil } +// String converts a Version struct to a string representation. +// If the channel is stable, it returns the version in the format "vMajor". +// Otherwise, it returns the version in the format "vMajor-Channel-Patch". func (csv Version) String() string { if csv.Channel == ChannelStable { return fmt.Sprintf("v%d", csv.Major) } - return fmt.Sprintf("v%d-%s-%d", csv.Major, csv.Channel, csv.Patch) + return fmt.Sprintf("v%d-%s-%s", csv.Major, csv.Channel, csv.Patch) +} + +// StringWithDot converts a Version struct to a string representation. +// If the channel is stable, it returns the version in the format "vMajor". +// Otherwise, it returns the version in the format "vMajor-Channel.Patch", +// similar to String but with a dot separating channel and patch. +func (csv Version) StringWithDot() string { + if csv.Channel == ChannelStable { + return fmt.Sprintf("v%d", csv.Major) + } + return fmt.Sprintf("v%d-%s.%s", csv.Major, csv.Channel, csv.Patch) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 450c01a7..df6fd7b8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -18,7 +18,7 @@ github.com/ProtonMail/go-crypto/openpgp/internal/ecc github.com/ProtonMail/go-crypto/openpgp/internal/encoding github.com/ProtonMail/go-crypto/openpgp/packet github.com/ProtonMail/go-crypto/openpgp/s2k -# github.com/SovereignCloudStack/cluster-stack-operator v0.1.0-alpha.2 +# github.com/SovereignCloudStack/cluster-stack-operator v0.1.0-alpha.3 ## explicit; go 1.19 github.com/SovereignCloudStack/cluster-stack-operator/pkg/clusterstack github.com/SovereignCloudStack/cluster-stack-operator/pkg/github/client @@ -711,7 +711,7 @@ sigs.k8s.io/cluster-api/util/topology # sigs.k8s.io/cluster-api-provider-openstack v0.9.0 ## explicit; go 1.20 sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha7 -# sigs.k8s.io/controller-runtime v0.16.3 +# sigs.k8s.io/controller-runtime v0.16.5 ## explicit; go 1.20 sigs.k8s.io/controller-runtime sigs.k8s.io/controller-runtime/pkg/builder diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/restmapper.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/restmapper.go index d5e03b2b..496b4c78 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/restmapper.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/restmapper.go @@ -21,6 +21,7 @@ import ( "net/http" "sync" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -52,7 +53,7 @@ func NewDynamicRESTMapper(cfg *rest.Config, httpClient *http.Client) (meta.RESTM // client for discovery information to do REST mappings. type mapper struct { mapper meta.RESTMapper - client *discovery.DiscoveryClient + client discovery.DiscoveryInterface knownGroups map[string]*restmapper.APIGroupResources apiGroups map[string]*metav1.APIGroup @@ -179,23 +180,28 @@ func (m *mapper) addKnownGroupAndReload(groupName string, versions ...string) er Group: metav1.APIGroup{Name: groupName}, VersionedResources: make(map[string][]metav1.APIResource), } - if _, ok := m.knownGroups[groupName]; ok { - groupResources = m.knownGroups[groupName] - } // Update information for group resources about versioned resources. // The number of API calls is equal to the number of versions: /apis//. - groupVersionResources, err := m.fetchGroupVersionResources(groupName, versions...) + // If we encounter a missing API version (NotFound error), we will remove the group from + // the m.apiGroups and m.knownGroups caches. + // If this happens, in the next call the group will be added back to apiGroups + // and only the existing versions will be loaded in knownGroups. + groupVersionResources, err := m.fetchGroupVersionResourcesLocked(groupName, versions...) if err != nil { return fmt.Errorf("failed to get API group resources: %w", err) } - for version, resources := range groupVersionResources { - groupResources.VersionedResources[version.Version] = resources.APIResources + + if _, ok := m.knownGroups[groupName]; ok { + groupResources = m.knownGroups[groupName] } // Update information for group resources about the API group by adding new versions. // Ignore the versions that are already registered. - for _, version := range versions { + for groupVersion, resources := range groupVersionResources { + version := groupVersion.Version + + groupResources.VersionedResources[version] = resources.APIResources found := false for _, v := range groupResources.Group.Versions { if v.Version == version { @@ -216,12 +222,7 @@ func (m *mapper) addKnownGroupAndReload(groupName string, versions ...string) er m.knownGroups[groupName] = groupResources // Finally, update the group with received information and regenerate the mapper. - updatedGroupResources := make([]*restmapper.APIGroupResources, 0, len(m.knownGroups)) - for _, agr := range m.knownGroups { - updatedGroupResources = append(updatedGroupResources, agr) - } - - m.mapper = restmapper.NewDiscoveryRESTMapper(updatedGroupResources) + m.refreshMapper() return nil } @@ -267,8 +268,9 @@ func (m *mapper) findAPIGroupByName(groupName string) (*metav1.APIGroup, error) return nil, fmt.Errorf("failed to find API group %q", groupName) } -// fetchGroupVersionResources fetches the resources for the specified group and its versions. -func (m *mapper) fetchGroupVersionResources(groupName string, versions ...string) (map[schema.GroupVersion]*metav1.APIResourceList, error) { +// fetchGroupVersionResourcesLocked fetches the resources for the specified group and its versions. +// This method might modify the cache so it needs to be called under the lock. +func (m *mapper) fetchGroupVersionResourcesLocked(groupName string, versions ...string) (map[schema.GroupVersion]*metav1.APIResourceList, error) { groupVersionResources := make(map[schema.GroupVersion]*metav1.APIResourceList) failedGroups := make(map[schema.GroupVersion]error) @@ -276,9 +278,30 @@ func (m *mapper) fetchGroupVersionResources(groupName string, versions ...string groupVersion := schema.GroupVersion{Group: groupName, Version: version} apiResourceList, err := m.client.ServerResourcesForGroupVersion(groupVersion.String()) + if apierrors.IsNotFound(err) { + // If the version is not found, we remove the group from the cache + // so it gets refreshed on the next call. + cacheInvalidated := false + if m.isAPIGroupCached(groupVersion) { + delete(m.apiGroups, groupName) + cacheInvalidated = true + } + if m.isGroupVersionCached(groupVersion) { + delete(m.knownGroups, groupName) + cacheInvalidated = true + } + // It's important to refresh the mapper after invalidating the cache, since returning an error + // aborts the call and leaves the underlying mapper unchanged. If not refreshed, the next call + // will still return a match for the NotFound version. + if cacheInvalidated { + m.refreshMapper() + } + } + if err != nil { failedGroups[groupVersion] = err } + if apiResourceList != nil { // even in case of error, some fallback might have been returned. groupVersionResources[groupVersion] = apiResourceList @@ -292,3 +315,37 @@ func (m *mapper) fetchGroupVersionResources(groupName string, versions ...string return groupVersionResources, nil } + +// isGroupVersionCached checks if a version for a group is cached in the known groups cache. +func (m *mapper) isGroupVersionCached(gv schema.GroupVersion) bool { + if cachedGroup, ok := m.knownGroups[gv.Group]; ok { + _, cached := cachedGroup.VersionedResources[gv.Version] + return cached + } + + return false +} + +// isAPIGroupCached checks if a version for a group is cached in the api groups cache. +func (m *mapper) isAPIGroupCached(gv schema.GroupVersion) bool { + cachedGroup, ok := m.apiGroups[gv.Group] + if !ok { + return false + } + + for _, version := range cachedGroup.Versions { + if version.Version == gv.Version { + return true + } + } + + return false +} + +func (m *mapper) refreshMapper() { + updatedGroupResources := make([]*restmapper.APIGroupResources, 0, len(m.knownGroups)) + for _, agr := range m.knownGroups { + updatedGroupResources = append(updatedGroupResources, agr) + } + m.mapper = restmapper.NewDiscoveryRESTMapper(updatedGroupResources) +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go index d81bf25d..ee6489b7 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go @@ -514,7 +514,8 @@ type MatchingLabels map[string]string func (m MatchingLabels) ApplyToList(opts *ListOptions) { // TODO(directxman12): can we avoid reserializing this over and over? if opts.LabelSelector == nil { - opts.LabelSelector = labels.NewSelector() + opts.LabelSelector = labels.SelectorFromValidatedSet(map[string]string(m)) + return } // If there's already a selector, we need to AND the two together. noValidSel := labels.SelectorFromValidatedSet(map[string]string(m))