feat: add k8s-version parameter to k8s-bundle

Allow overriding K8s version in the  command.

Signed-off-by: Mateusz Urbanek <mateusz.urbanek@siderolabs.com>
This commit is contained in:
Mateusz Urbanek 2025-12-29 10:48:37 +01:00
parent b764f5f724
commit 080efcbda2
No known key found for this signature in database
GPG Key ID: F16F84591E26D77F
6 changed files with 236 additions and 78 deletions

View File

@ -138,36 +138,56 @@ var imagePullCmd = &cobra.Command{
},
}
// imageDefaultCmd represents the image k8s-bundle command.
var imageDefaultCmd = &cobra.Command{
var imageK8sBundleCmdFlags = struct {
k8sVersion pflag.Value
flannelVersion pflag.Value
corednsVersion pflag.Value
etcdVersion pflag.Value
}{
k8sVersion: helpers.Semver(constants.DefaultKubernetesVersion),
flannelVersion: helpers.Semver(constants.FlannelVersion),
corednsVersion: helpers.Semver(constants.DefaultCoreDNSVersion),
etcdVersion: helpers.Semver(constants.DefaultEtcdVersion),
}
// imageK8sBundleCmd represents the image k8s-bundle command.
var imageK8sBundleCmd = &cobra.Command{
Use: "k8s-bundle",
Aliases: []string{"default"},
Short: "List the default Kubernetes images used by Talos",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
images := images.List(container.NewV1Alpha1(&v1alpha1.Config{
MachineConfig: &v1alpha1.MachineConfig{
MachineKubelet: &v1alpha1.KubeletConfig{},
images := images.ListWithOptions(container.NewV1Alpha1(
&v1alpha1.Config{
MachineConfig: &v1alpha1.MachineConfig{
MachineKubelet: &v1alpha1.KubeletConfig{},
},
ClusterConfig: &v1alpha1.ClusterConfig{
EtcdConfig: &v1alpha1.EtcdConfig{},
APIServerConfig: &v1alpha1.APIServerConfig{},
ControllerManagerConfig: &v1alpha1.ControllerManagerConfig{},
SchedulerConfig: &v1alpha1.SchedulerConfig{},
CoreDNSConfig: &v1alpha1.CoreDNS{},
ProxyConfig: &v1alpha1.ProxyConfig{},
},
}),
images.VersionsListOptions{
KubernetesVersion: imageK8sBundleCmdFlags.k8sVersion.String(),
EtcdVersion: imageK8sBundleCmdFlags.etcdVersion.String(),
FlannelVersion: imageK8sBundleCmdFlags.flannelVersion.String(),
CoreDNSVersion: imageK8sBundleCmdFlags.corednsVersion.String(),
},
ClusterConfig: &v1alpha1.ClusterConfig{
EtcdConfig: &v1alpha1.EtcdConfig{},
APIServerConfig: &v1alpha1.APIServerConfig{},
ControllerManagerConfig: &v1alpha1.ControllerManagerConfig{},
SchedulerConfig: &v1alpha1.SchedulerConfig{},
CoreDNSConfig: &v1alpha1.CoreDNS{},
ProxyConfig: &v1alpha1.ProxyConfig{},
},
}))
)
fmt.Printf("%s\n", images.Flannel)
fmt.Printf("%s\n", images.CoreDNS)
fmt.Printf("%s\n", images.Etcd)
fmt.Printf("%s\n", images.Pause)
fmt.Printf("%s\n", images.KubeAPIServer)
fmt.Printf("%s\n", images.KubeControllerManager)
fmt.Printf("%s\n", images.KubeScheduler)
fmt.Printf("%s\n", images.KubeProxy)
fmt.Printf("%s\n", images.Kubelet)
fmt.Printf("%s\n", images.Pause)
return nil
},
@ -317,15 +337,15 @@ var imageIntegrationCmd = &cobra.Command{
}))
imageNames := []string{
imgs.Flannel,
imgs.CoreDNS,
imgs.Etcd,
imgs.KubeAPIServer,
imgs.KubeControllerManager,
imgs.KubeScheduler,
imgs.KubeProxy,
imgs.Kubelet,
imgs.Pause,
imgs.Flannel.String(),
imgs.CoreDNS.String(),
imgs.Etcd.String(),
imgs.KubeAPIServer.String(),
imgs.KubeControllerManager.String(),
imgs.KubeScheduler.String(),
imgs.KubeProxy.String(),
imgs.Kubelet.String(),
imgs.Pause.String(),
"registry.k8s.io/conformance:v" + constants.DefaultKubernetesVersion,
"docker.io/library/alpine:latest",
"ghcr.io/siderolabs/talosctl:latest",
@ -651,7 +671,11 @@ func init() {
imageTalosBundleCmd.PersistentFlags().BoolVar(&imageTalosBundleCmdFlags.overlays, "overlays", true, "Include images that belong to Talos overlays")
imageTalosBundleCmd.PersistentFlags().BoolVar(&imageTalosBundleCmdFlags.extensions, "extensions", true, "Include images that belong to Talos extensions")
imageCmd.AddCommand(imageDefaultCmd)
imageCmd.AddCommand(imageK8sBundleCmd)
imageK8sBundleCmd.PersistentFlags().Var(imageK8sBundleCmdFlags.k8sVersion, "k8s-version", "Kubernetes semantic version")
imageK8sBundleCmd.PersistentFlags().Var(imageK8sBundleCmdFlags.etcdVersion, "etcd-version", "ETCD semantic version")
imageK8sBundleCmd.PersistentFlags().Var(imageK8sBundleCmdFlags.flannelVersion, "flannel-version", "Flannel CNI semantic version")
imageK8sBundleCmd.PersistentFlags().Var(imageK8sBundleCmdFlags.corednsVersion, "coredns-version", "CoreDNS semantic version")
imageCmd.AddCommand(imageCacheCreateCmd)
imageCacheCreateCmd.PersistentFlags().StringVar(&imageCacheCreateCmdFlags.imageCachePath, "image-cache-path", "", "directory to save the image cache in OCI format")

View File

@ -8,32 +8,32 @@ import (
"fmt"
"slices"
"github.com/blang/semver/v4"
"github.com/spf13/pflag"
)
// choiceValue implements the [pflag.Value] interface.
type choiceValue struct {
value string
validate func(string) error
}
// Set sets the value of the choice.
func (f *choiceValue) Set(s string) error {
err := f.validate(s)
// Set implements pflag.Value interface.
func (v *choiceValue) Set(s string) error {
err := v.validate(s)
if err != nil {
return err
}
f.value = s
v.value = s
return nil
}
// Type returns the type of the choice, which must be "string" for [pflag.FlagSet.GetString].
func (f *choiceValue) Type() string { return "string" }
// Type implements pflag.Value interface.
func (v *choiceValue) Type() string { return "string" }
// String returns the current value of the choice.
func (f *choiceValue) String() string { return f.value }
// String implements pflag.Value interface.
func (v *choiceValue) String() string { return v.value }
// StringChoice returns a [choiceValue] that validates the value against a set
// of choices. Only the last value will be used if multiple values are set.
@ -51,3 +51,54 @@ func StringChoice(defaultValue string, otherChoices ...string) pflag.Value {
},
}
}
type semverValue struct {
value semver.Version
validators []SemverValidateFunc
}
// SemverValidateFunc allows setting restrictions on the version.
type SemverValidateFunc func(v semver.Version) error
// Set implements pflag.Value interface.
func (v *semverValue) Set(s string) error {
vers, err := semver.ParseTolerant(s)
if err != nil {
return err
}
for _, validator := range v.validators {
if err := validator(vers); err != nil {
return err
}
}
v.value = vers
return nil
}
// Type implements pflag.Value interface.
func (v *semverValue) Type() string { return "semver" }
// String implements pflag.Value interface.
func (v *semverValue) String() string { return "v" + v.value.String() }
// Semver returns a pflag.Value that parses and stores a semantic version.
//
// Parsing is performed using semver.ParseTolerant. After parsing, any provided
// SemverValidateFunc validators are applied in order and may reject the version.
//
// The returned value is initialized with defaultValue, which is used until Set
// is called successfully.
func Semver(defaultValue string, validators ...SemverValidateFunc) pflag.Value {
v, err := semver.ParseTolerant(defaultValue)
if err != nil {
panic(err)
}
return &semverValue{
value: v,
validators: validators,
}
}

View File

@ -101,6 +101,12 @@ The switch and inventory backfill is automatic and no action is needed from the
description = """\
The `talosctl images talos-bundle` command now accepts optional `--ovelays` and `--extensions` flags.
If those are set to `false`, the command will not attempt to reach out to the container registry to fetch the latest versions and digests of the overlays and extensions.
"""
[notes.images_k8s_bundle]
title = "Talosctl images k8s-bundle subcommand accepts version parameter"
description = """\
The `talosctl images k8s-bundle` command now accepts an optional version overrides arguments.
"""
[make_deps]

View File

@ -339,7 +339,7 @@ func NewControlPlaneBootstrapManifestsController() *ControlPlaneBootstrapManifes
DNSServiceIPv6: dnsServiceIPv6,
FlannelEnabled: cfgProvider.Cluster().Network().CNI().Name() == constants.FlannelCNI,
FlannelImage: images.Flannel,
FlannelImage: images.Flannel.String(),
FlannelExtraArgs: cfgProvider.Cluster().Network().CNI().Flannel().ExtraArgs(),
FlannelKubeServiceHost: flannelKubeServiceHost,
FlannelKubeServicePort: flannelKubeServicePort,

View File

@ -7,26 +7,25 @@ package images
import (
"fmt"
"github.com/google/go-containerregistry/pkg/name"
"github.com/siderolabs/talos/pkg/machinery/config"
"github.com/siderolabs/talos/pkg/machinery/constants"
)
// Versions holds all the images (and their versions) that are used in Talos.
type Versions struct {
Etcd string
Flannel string
CoreDNS string
Etcd name.Tag
Flannel name.Tag
CoreDNS name.Tag
Kubelet string
KubeAPIServer string
KubeControllerManager string
KubeProxy string
KubeScheduler string
Kubelet name.Tag
KubeAPIServer name.Tag
KubeControllerManager name.Tag
KubeProxy name.Tag
KubeScheduler name.Tag
Installer string
Talos string
Pause string
Pause name.Tag
}
// DefaultSandboxImage is defined as a constant in cri package of containerd, and it's not exported.
@ -38,47 +37,121 @@ const DefaultSandboxImage = "registry.k8s.io/pause:3.10.1"
func List(config config.Config) Versions {
var images Versions
images.Etcd = config.Cluster().Etcd().Image()
images.CoreDNS = config.Cluster().CoreDNS().Image()
images.Flannel = fmt.Sprintf("ghcr.io/siderolabs/flannel:%s", constants.FlannelVersion) // mirrored from docker.io/flannelcni/flannel
images.Kubelet = config.Machine().Kubelet().Image()
images.KubeAPIServer = config.Cluster().APIServer().Image()
images.KubeControllerManager = config.Cluster().ControllerManager().Image()
images.KubeProxy = config.Cluster().Proxy().Image()
images.KubeScheduler = config.Cluster().Scheduler().Image()
images.Etcd = mustParseTag(config.Cluster().Etcd().Image())
images.CoreDNS = mustParseTag(config.Cluster().CoreDNS().Image())
images.Flannel = mustParseTag(fmt.Sprintf("ghcr.io/siderolabs/flannel:%s", constants.FlannelVersion)) // mirrored from docker.io/flannelcni/flannel
images.Kubelet = mustParseTag(config.Machine().Kubelet().Image())
images.KubeAPIServer = mustParseTag(config.Cluster().APIServer().Image())
images.KubeControllerManager = mustParseTag(config.Cluster().ControllerManager().Image())
images.KubeProxy = mustParseTag(config.Cluster().Proxy().Image())
images.KubeScheduler = mustParseTag(config.Cluster().Scheduler().Image())
images.Installer = DefaultInstallerImage
images.Talos = DefaultTalosImage
images.Pause = DefaultSandboxImage
images.Pause = mustParseTag(DefaultSandboxImage)
return images
}
// SourceBundle holds the core images (and their versions) that are used to build Talos.
type SourceBundle struct {
Installer string
InstallerBase string
Imager string
Talos string
TalosctlAll string
// VersionsListOptions allows overriding the default component versions
// displayed to the user.
//
// Any non-empty field value replaces the corresponding default version
// when presenting available or selected versions. Fields left empty
// will fall back to their built-in defaults.
type VersionsListOptions struct {
// KubernetesVersion overrides the default Kubernetes version.
KubernetesVersion string
Overlays string
Extensions string
// CoreDNSVersion overrides the default CoreDNS version.
CoreDNSVersion string
// EtcdVersion overrides the default etcd version.
EtcdVersion string
// FlannelVersion overrides the default Flannel version.
FlannelVersion string
// PauseVersion overrides the default pause container image version.
PauseVersion string
}
// ListWithOptions returns image versions with overrides.
func ListWithOptions(config config.Config, opts VersionsListOptions) Versions {
images := List(config)
if opts.CoreDNSVersion != "" {
images.CoreDNS = images.CoreDNS.Tag(opts.CoreDNSVersion)
}
if opts.EtcdVersion != "" {
images.Etcd = images.Etcd.Tag(opts.EtcdVersion)
}
if opts.FlannelVersion != "" {
images.Flannel = images.Flannel.Tag(opts.FlannelVersion)
}
if opts.PauseVersion != "" {
images.Pause = images.Pause.Tag(opts.PauseVersion)
}
if opts.KubernetesVersion != "" {
images.Kubelet = images.Kubelet.Tag(opts.KubernetesVersion)
images.KubeAPIServer = images.KubeAPIServer.Tag(opts.KubernetesVersion)
images.KubeControllerManager = images.KubeControllerManager.Tag(opts.KubernetesVersion)
images.KubeProxy = images.KubeProxy.Tag(opts.KubernetesVersion)
images.KubeScheduler = images.KubeScheduler.Tag(opts.KubernetesVersion)
}
return images
}
func mustParseTag(s string) name.Tag {
r, err := name.ParseReference(s)
if err != nil {
panic(err)
}
t, ok := r.(name.Tag)
if !ok {
panic(fmt.Sprintf("%T is not name.Tag: %#+v", r, r))
}
return t
}
func mustParseReferenceWithTag(ref, tag string) name.Tag {
r, err := name.ParseReference(ref)
if err != nil {
panic(err)
}
return r.Context().Tag(tag)
}
// TalosBundle holds the core images (and their versions) that are used to build Talos.
type TalosBundle struct {
Installer name.Tag
InstallerBase name.Tag
Imager name.Tag
Talos name.Tag
TalosctlAll name.Tag
Overlays name.Tag
Extensions name.Tag
}
// ListSourcesFor returns source bundle for specific version.
func ListSourcesFor(tag string) SourceBundle {
var bundle SourceBundle
func ListSourcesFor(tag string) TalosBundle {
var bundle TalosBundle
bundle.Installer = DefaultInstallerImageRepository + ":" + tag
bundle.InstallerBase = DefaultInstallerBaseImageRepository + ":" + tag
bundle.Imager = DefaultImagerImageRepository + ":" + tag
bundle.Talos = DefaultTalosImageRepository + ":" + tag
bundle.TalosctlAll = DefaultTalosctlAllImageRepository + ":" + tag
bundle.Installer = mustParseReferenceWithTag(DefaultInstallerImageRepository, tag)
bundle.InstallerBase = mustParseReferenceWithTag(DefaultInstallerBaseImageRepository, tag)
bundle.Imager = mustParseReferenceWithTag(DefaultImagerImageRepository, tag)
bundle.Talos = mustParseReferenceWithTag(DefaultTalosImageRepository, tag)
bundle.TalosctlAll = mustParseReferenceWithTag(DefaultTalosctlAllImageRepository, tag)
bundle.Overlays = DefaultOverlaysManifestRepository + ":" + tag
bundle.Extensions = DefaultExtensionsManifestRepository + ":" + tag
bundle.Overlays = mustParseReferenceWithTag(DefaultOverlaysManifestRepository, tag)
bundle.Extensions = mustParseReferenceWithTag(DefaultExtensionsManifestRepository, tag)
return bundle
}

View File

@ -2215,7 +2215,11 @@ talosctl image k8s-bundle [flags]
### Options
```
-h, --help help for k8s-bundle
--coredns-version semver CoreDNS semantic version (default v1.13.2)
--etcd-version semver ETCD semantic version (default v3.6.7)
--flannel-version semver Flannel CNI semantic version (default v0.27.4)
-h, --help help for k8s-bundle
--k8s-version semver Kubernetes semantic version (default v1.35.0)
```
### Options inherited from parent commands