diff --git a/internal/cmd/gendefaultversions/main.go b/internal/cmd/gendefaultversions/main.go new file mode 100644 index 000000000..565d80c12 --- /dev/null +++ b/internal/cmd/gendefaultversions/main.go @@ -0,0 +1,125 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "bytes" + "flag" + "fmt" + "go/format" + "os" + "slices" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" +) + +const licenseHeader = `/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/` + +func main() { + output := flag.String("output", "default_versions.go", "path to the generated Go file") + flag.Parse() + + versions, err := knownVersions() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + var source bytes.Buffer + fmt.Fprintln(&source, licenseHeader) + fmt.Fprintln(&source) + fmt.Fprintln(&source, "// Code generated by gendefaultversions; DO NOT EDIT.") + fmt.Fprintln(&source) + fmt.Fprintln(&source, "package common") + fmt.Fprintln(&source) + fmt.Fprintln(&source, "// DefaultVersionSet is the default set of Kubernetes API versions used") + fmt.Fprintln(&source, "// when Helm renders without live cluster discovery.") + fmt.Fprintln(&source, "//") + fmt.Fprintln(&source, "// This is a snapshot of the API versions registered by Helm's Kubernetes") + fmt.Fprintln(&source, "// dependencies, not a list of APIs served by every supported cluster. Live") + fmt.Fprintln(&source, "// cluster operations build Capabilities from server discovery instead.") + fmt.Fprintln(&source, "//") + fmt.Fprintln(&source, "// Review API additions and removals against the Kubernetes versions supported") + fmt.Fprintln(&source, "// by this Helm release before committing a regenerated snapshot.") + fmt.Fprintln(&source, "//") + fmt.Fprintln(&source, "// Regenerate after updating Kubernetes dependencies with:") + fmt.Fprintln(&source, "//") + fmt.Fprintln(&source, "//\tgo generate ./pkg/chart/common") + fmt.Fprintln(&source, "var DefaultVersionSet = VersionSet{") + for _, version := range versions { + fmt.Fprintf(&source, "\t%q,\n", version) + } + fmt.Fprintln(&source, "}") + + formatted, err := format.Source(source.Bytes()) + if err != nil { + fmt.Fprintf(os.Stderr, "format generated source: %v\n", err) + os.Exit(1) + } + if err := os.WriteFile(*output, formatted, 0o644); err != nil { + fmt.Fprintf(os.Stderr, "write generated source: %v\n", err) + os.Exit(1) + } +} + +func knownVersions() ([]string, error) { + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + return nil, fmt.Errorf("register client-go scheme: %w", err) + } + if err := apiextensionsv1beta1.AddToScheme(scheme); err != nil { + return nil, fmt.Errorf("register apiextensions v1beta1 scheme: %w", err) + } + if err := apiextensionsv1.AddToScheme(scheme); err != nil { + return nil, fmt.Errorf("register apiextensions v1 scheme: %w", err) + } + + groupSet := make(map[string]struct{}) + for _, groupVersion := range scheme.PrioritizedVersionsAllGroups() { + groupSet[groupVersion.Group] = struct{}{} + } + groups := make([]string, 0, len(groupSet)) + for group := range groupSet { + groups = append(groups, group) + } + slices.Sort(groups) + + versions := make([]string, 0) + for _, group := range groups { + for _, groupVersion := range scheme.PrioritizedVersionsForGroup(group) { + versions = append(versions, groupVersion.String()) + } + } + return versions, nil +} diff --git a/pkg/chart/common/capabilities.go b/pkg/chart/common/capabilities.go index c7217a503..959fda96b 100644 --- a/pkg/chart/common/capabilities.go +++ b/pkg/chart/common/capabilities.go @@ -23,10 +23,7 @@ import ( "testing" "github.com/Masterminds/semver/v3" - "k8s.io/client-go/kubernetes/scheme" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" k8sversion "k8s.io/apimachinery/pkg/util/version" helmversion "helm.sh/helm/v4/internal/version" @@ -37,10 +34,9 @@ const ( kubeVersionMinorTesting = 20 ) -var ( - // DefaultVersionSet is the default version set, which includes only Core V1 ("v1"). - DefaultVersionSet = allKnownVersions() +//go:generate go run helm.sh/helm/v4/internal/cmd/gendefaultversions -output default_versions.go +var ( DefaultCapabilities = func() *Capabilities { caps, err := makeDefaultCapabilities() if err != nil { @@ -128,21 +124,6 @@ func (v VersionSet) Has(apiVersion string) bool { return slices.Contains(v, apiVersion) } -func allKnownVersions() VersionSet { - // We should register the built in extension APIs as well so CRDs are - // supported in the default version set. This has caused problems with `helm - // template` in the past, so let's be safe - apiextensionsv1beta1.AddToScheme(scheme.Scheme) - apiextensionsv1.AddToScheme(scheme.Scheme) - - groups := scheme.Scheme.PrioritizedVersionsAllGroups() - vs := make(VersionSet, 0, len(groups)) - for _, gv := range groups { - vs = append(vs, gv.String()) - } - return vs -} - func makeDefaultCapabilities() (*Capabilities, error) { // Test builds don't include debug info / module info // (And even if they did, we probably want stable capabilities for tests anyway) diff --git a/pkg/chart/common/default_versions.go b/pkg/chart/common/default_versions.go new file mode 100644 index 000000000..3392c12db --- /dev/null +++ b/pkg/chart/common/default_versions.go @@ -0,0 +1,90 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by gendefaultversions; DO NOT EDIT. + +package common + +// DefaultVersionSet is the default set of Kubernetes API versions used +// when Helm renders without live cluster discovery. +// +// This is a snapshot of the API versions registered by Helm's Kubernetes +// dependencies, not a list of APIs served by every supported cluster. Live +// cluster operations build Capabilities from server discovery instead. +// +// Review API additions and removals against the Kubernetes versions supported +// by this Helm release before committing a regenerated snapshot. +// +// Regenerate after updating Kubernetes dependencies with: +// +// go generate ./pkg/chart/common +var DefaultVersionSet = VersionSet{ + "v1", + "admissionregistration.k8s.io/v1", + "admissionregistration.k8s.io/v1alpha1", + "admissionregistration.k8s.io/v1beta1", + "apiextensions.k8s.io/v1beta1", + "apiextensions.k8s.io/v1", + "apps/v1", + "apps/v1beta1", + "apps/v1beta2", + "authentication.k8s.io/v1", + "authentication.k8s.io/v1alpha1", + "authentication.k8s.io/v1beta1", + "authorization.k8s.io/v1", + "authorization.k8s.io/v1beta1", + "autoscaling/v1", + "autoscaling/v2", + "batch/v1", + "batch/v1beta1", + "certificates.k8s.io/v1", + "certificates.k8s.io/v1beta1", + "certificates.k8s.io/v1alpha1", + "coordination.k8s.io/v1alpha2", + "coordination.k8s.io/v1beta1", + "coordination.k8s.io/v1", + "discovery.k8s.io/v1", + "discovery.k8s.io/v1beta1", + "events.k8s.io/v1", + "events.k8s.io/v1beta1", + "extensions/v1beta1", + "flowcontrol.apiserver.k8s.io/v1", + "flowcontrol.apiserver.k8s.io/v1beta1", + "flowcontrol.apiserver.k8s.io/v1beta2", + "flowcontrol.apiserver.k8s.io/v1beta3", + "internal.apiserver.k8s.io/v1alpha1", + "networking.k8s.io/v1", + "networking.k8s.io/v1beta1", + "node.k8s.io/v1", + "node.k8s.io/v1alpha1", + "node.k8s.io/v1beta1", + "policy/v1", + "policy/v1beta1", + "rbac.authorization.k8s.io/v1", + "rbac.authorization.k8s.io/v1beta1", + "rbac.authorization.k8s.io/v1alpha1", + "resource.k8s.io/v1", + "resource.k8s.io/v1beta2", + "resource.k8s.io/v1beta1", + "resource.k8s.io/v1alpha3", + "scheduling.k8s.io/v1alpha2", + "scheduling.k8s.io/v1beta1", + "scheduling.k8s.io/v1", + "storage.k8s.io/v1beta1", + "storage.k8s.io/v1", + "storage.k8s.io/v1alpha1", + "storagemigration.k8s.io/v1beta1", +} diff --git a/pkg/chart/common/default_versions_test.go b/pkg/chart/common/default_versions_test.go new file mode 100644 index 000000000..090668229 --- /dev/null +++ b/pkg/chart/common/default_versions_test.go @@ -0,0 +1,55 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "slices" + "testing" + + "github.com/stretchr/testify/require" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" +) + +func TestDefaultVersionSetMatchesScheme(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, clientgoscheme.AddToScheme(scheme)) + require.NoError(t, apiextensionsv1beta1.AddToScheme(scheme)) + require.NoError(t, apiextensionsv1.AddToScheme(scheme)) + + groupSet := make(map[string]struct{}) + for _, groupVersion := range scheme.PrioritizedVersionsAllGroups() { + groupSet[groupVersion.Group] = struct{}{} + } + groups := make([]string, 0, len(groupSet)) + for group := range groupSet { + groups = append(groups, group) + } + slices.Sort(groups) + + schemeVersions := make(VersionSet, 0) + for _, group := range groups { + for _, groupVersion := range scheme.PrioritizedVersionsForGroup(group) { + schemeVersions = append(schemeVersions, groupVersion.String()) + } + } + + require.Equal(t, schemeVersions, DefaultVersionSet, + "DefaultVersionSet must stay in sync with the Kubernetes scheme; run `go generate ./pkg/chart/common`") +}