pull/32666/merge
Jakob Möller 4 days ago committed by GitHub
commit 434c14c9b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -20,10 +20,16 @@ import (
"errors" "errors"
"runtime/debug" "runtime/debug"
"slices" "slices"
_ "k8s.io/client-go/kubernetes" // Force k8s.io/client-go to be included in the build
) )
// K8sIOClientGoModVersion reports the version of k8s.io/client-go this binary
// was built against, read from the module build info.
//
// client-go is included in the build graph by whatever links a Kubernetes
// client (for the helm CLI, that is cmd/helm). When a consumer links Helm's
// chart libraries WITHOUT a client-go dependency, build info will not list it
// and this returns an error; callers must tolerate that (see version.Get and
// chart/common capabilities).
func K8sIOClientGoModVersion() (string, error) { func K8sIOClientGoModVersion() (string, error) {
info, ok := debug.ReadBuildInfo() info, ok := debug.ReadBuildInfo()
if !ok { if !ok {

@ -23,10 +23,6 @@ import (
"testing" "testing"
"github.com/Masterminds/semver/v3" "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" k8sversion "k8s.io/apimachinery/pkg/util/version"
helmversion "helm.sh/helm/v4/internal/version" helmversion "helm.sh/helm/v4/internal/version"
@ -128,19 +124,19 @@ func (v VersionSet) Has(apiVersion string) bool {
return slices.Contains(v, apiVersion) return slices.Contains(v, apiVersion)
} }
// allKnownVersions returns the built-in Kubernetes API group/versions that seed
// the default capability version set.
//
// The list is generated (see gen_known_versions.go and
// zz_generated_known_versions.go) by enumerating client-go's runtime scheme
// plus the apiextensions groups. Returning the pre-computed slice here keeps
// this package — and therefore every chart-handling binary — free of the
// client-go, k8s.io/api and apiextensions-apiserver dependency trees, which
// are only needed to build the list, not to consult it.
//
//go:generate go run gen_known_versions.go
func allKnownVersions() VersionSet { func allKnownVersions() VersionSet {
// We should register the built in extension APIs as well so CRDs are return slices.Clone(defaultKnownVersions)
// 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) { func makeDefaultCapabilities() (*Capabilities, error) {
@ -151,9 +147,13 @@ func makeDefaultCapabilities() (*Capabilities, error) {
return newCapabilities(kubeVersionMajorTesting, kubeVersionMinorTesting) return newCapabilities(kubeVersionMajorTesting, kubeVersionMinorTesting)
} }
// K8sIOClientGoModVersion reads the client-go version from build info. When a
// consumer links Helm's chart libraries without client-go, that lookup fails;
// fall back to the built-in default kube version rather than erroring, since
// this default is advisory (it only seeds Capabilities.KubeVersion).
vstr, err := helmversion.K8sIOClientGoModVersion() vstr, err := helmversion.K8sIOClientGoModVersion()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to retrieve k8s.io/client-go version: %w", err) return newCapabilities(kubeVersionMajorTesting, kubeVersionMinorTesting)
} }
v, err := semver.NewVersion(vstr) v, err := semver.NewVersion(vstr)

@ -0,0 +1,98 @@
//go:build ignore
/*
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.
*/
// This program regenerates zz_generated_known_versions.go, the built-in
// Kubernetes API group/versions that seed the default capability version set.
//
// It enumerates client-go's runtime scheme plus the apiextensions groups —
// exactly as pkg/chart/common used to do at runtime — and writes the result as
// a static slice. Doing this at generate time keeps client-go, k8s.io/api and
// apiextensions-apiserver out of the runtime dependency graph of every binary
// that links Helm's chart packages.
//
// Run via `go generate ./pkg/chart/common/...`.
package main
import (
"bytes"
"fmt"
"go/format"
"os"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/client-go/kubernetes/scheme"
)
const target = "zz_generated_known_versions.go"
func main() {
// 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.
if err := apiextensionsv1beta1.AddToScheme(scheme.Scheme); err != nil {
fatal(err)
}
if err := apiextensionsv1.AddToScheme(scheme.Scheme); err != nil {
fatal(err)
}
var buf bytes.Buffer
buf.WriteString(`/*
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 gen_known_versions.go; DO NOT EDIT.
package common
// defaultKnownVersions is the set of built-in Kubernetes API group/versions
// used to seed the default capability version set. Regenerate with
// go generate ./pkg/chart/common/... after a client-go bump.
var defaultKnownVersions = VersionSet{
`)
for _, gv := range scheme.Scheme.PrioritizedVersionsAllGroups() {
fmt.Fprintf(&buf, "\t%q,\n", gv.String())
}
buf.WriteString("}\n")
out, err := format.Source(buf.Bytes())
if err != nil {
fatal(err)
}
if err := os.WriteFile(target, out, 0o644); err != nil {
fatal(err)
}
}
func fatal(err error) {
fmt.Fprintln(os.Stderr, "gen_known_versions:", err)
os.Exit(1)
}

@ -0,0 +1,82 @@
/*
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 gen_known_versions.go; DO NOT EDIT.
package common
// defaultKnownVersions is the set of built-in Kubernetes API group/versions
// used to seed the default capability version set. Regenerate with
// go generate ./pkg/chart/common/... after a client-go bump.
var defaultKnownVersions = VersionSet{
"v1",
"admissionregistration.k8s.io/v1",
"admissionregistration.k8s.io/v1alpha1",
"admissionregistration.k8s.io/v1beta1",
"internal.apiserver.k8s.io/v1alpha1",
"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",
"lifecycle.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/v1alpha3",
"scheduling.k8s.io/v1beta1",
"scheduling.k8s.io/v1",
"storage.k8s.io/v1beta1",
"storage.k8s.io/v1",
"storage.k8s.io/v1alpha1",
"storagemigration.k8s.io/v1",
"storagemigration.k8s.io/v1beta1",
"apiextensions.k8s.io/v1beta1",
"apiextensions.k8s.io/v1",
}

@ -16,7 +16,13 @@ limitations under the License.
package cmd package cmd
import ( import (
"runtime/debug"
"strings"
"testing" "testing"
"github.com/stretchr/testify/require"
"helm.sh/helm/v4/internal/version"
) )
func TestVersion(t *testing.T) { func TestVersion(t *testing.T) {
@ -39,3 +45,16 @@ func TestVersion(t *testing.T) {
func TestVersionFileCompletion(t *testing.T) { func TestVersionFileCompletion(t *testing.T) {
checkFileCompletion(t, "version", false) checkFileCompletion(t, "version", false)
} }
// TestVersionReportsKubeClient ensures the helm binary keeps client-go linked so
// `helm version` still reports KubeClientVersion. clientgo.go no longer forces
// the dependency, so guard the CLI's own linkage here (this test binary mirrors
// it) to catch a future change that unlinks client-go from the command surface.
func TestVersionReportsKubeClient(t *testing.T) {
if _, ok := debug.ReadBuildInfo(); !ok {
t.Skip("build info unavailable (Go < 1.27)")
}
v, err := version.K8sIOClientGoModVersion()
require.NoError(t, err)
require.True(t, strings.HasPrefix(v, "v"), "expected client-go version, got %q", v)
}

@ -23,7 +23,6 @@ import (
"slices" "slices"
"time" "time"
"helm.sh/helm/v4/pkg/cli"
"helm.sh/helm/v4/pkg/registry" "helm.sh/helm/v4/pkg/registry"
) )
@ -221,12 +220,23 @@ func Getters(extraOpts ...Option) Providers {
} }
} }
// EnvProvider supplies the Helm environment used to discover getter plugins.
// *cli.EnvSettings satisfies it. Accepting an interface here keeps pkg/getter
// free of a dependency on pkg/cli, which would otherwise pull the Kubernetes
// client-go, cli-runtime and kustomize trees into every binary that links a
// getter.
type EnvProvider interface {
// EnvVars returns the Helm environment variables, including HELM_PLUGINS,
// which locates the plugin directory scanned for getter plugins.
EnvVars() map[string]string
}
// All finds all of the registered getters as a list of Provider instances. // All finds all of the registered getters as a list of Provider instances.
// Currently, the built-in getters and the discovered plugins with downloader // Currently, the built-in getters and the discovered plugins with downloader
// notations are collected. // notations are collected.
func All(settings *cli.EnvSettings, opts ...Option) Providers { func All(env EnvProvider, opts ...Option) Providers {
result := Getters(opts...) result := Getters(opts...)
pluginDownloaders, _ := collectGetterPlugins(settings) pluginDownloaders, _ := collectGetterPlugins(env)
result = append(result, pluginDownloaders...) result = append(result, pluginDownloaders...)
return result return result
} }

@ -24,26 +24,26 @@ import (
"helm.sh/helm/v4/internal/plugin" "helm.sh/helm/v4/internal/plugin"
"helm.sh/helm/v4/internal/plugin/schema" "helm.sh/helm/v4/internal/plugin/schema"
"helm.sh/helm/v4/pkg/cli"
) )
// collectGetterPlugins scans for getter plugins. // collectGetterPlugins scans for getter plugins in the plugin directory named
// This will load plugins according to the cli. // by the HELM_PLUGINS entry of the provided environment.
func collectGetterPlugins(settings *cli.EnvSettings) (Providers, error) { func collectGetterPlugins(env EnvProvider) (Providers, error) {
envVars := env.EnvVars()
d := plugin.Descriptor{ d := plugin.Descriptor{
Type: "getter/v1", Type: "getter/v1",
} }
plgs, err := plugin.FindPlugins([]string{settings.PluginsDirectory}, d) plgs, err := plugin.FindPlugins([]string{envVars["HELM_PLUGINS"]}, d)
if err != nil { if err != nil {
return nil, err return nil, err
} }
env := plugin.FormatEnv(settings.EnvVars()) pluginEnv := plugin.FormatEnv(envVars)
pluginConstructorBuilder := func(plg plugin.Plugin) Constructor { pluginConstructorBuilder := func(plg plugin.Plugin) Constructor {
return func(option ...Option) (Getter, error) { return func(option ...Option) (Getter, error) {
return &getterPlugin{ return &getterPlugin{
options: append([]Option{}, option...), options: append([]Option{}, option...),
plg: plg, plg: plg,
env: env, env: pluginEnv,
}, nil }, nil
} }
} }

Loading…
Cancel
Save