Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 27 additions & 16 deletions pkg/apis/kubeone/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,39 +128,50 @@ func TFOutput(tfOutputPath string) ([]byte, error) {
return tfOutput, nil
}

// BytesToKubeOneCluster parses the bytes of the versioned KubeOneCluster manifests
func BytesToKubeOneCluster(cluster, tfOutput []byte, credentialsFilePath string, logger logrus.FieldLogger, baseDir string) (*kubeoneapi.KubeOneCluster, error) {
// Get the GVK from the given KubeOneCluster manifest
// KubeOneClusterAPIVersion parses the TypeMeta from the raw KubeOneCluster
// manifest, validates that kind and apiVersion are present and supported, and
// returns the apiVersion string. The caller is responsible for acting on
// deprecated API versions.
func KubeOneClusterAPIVersion(cluster []byte) (string, error) {
typeMeta := runtime.TypeMeta{}
if err := yaml.Unmarshal(cluster, &typeMeta); err != nil {
return nil, fail.Config(err, "unmarshal cluster typeMeta")
return "", fail.Config(err, "unmarshal cluster typeMeta")
}
if len(typeMeta.APIVersion) == 0 || len(typeMeta.Kind) == 0 {
return nil, fail.ConfigValidation(fmt.Errorf("apiVersion and kind must be present in the manifest"))
return "", fail.ConfigValidation(fmt.Errorf("apiVersion and kind must be present in the manifest"))
}
if typeMeta.Kind != KubeOneClusterKind {
return nil, fail.ConfigValidation(fmt.Errorf("provided object %q is not KubeOneCluster object", typeMeta.Kind))
return "", fail.ConfigValidation(fmt.Errorf("provided object %q is not KubeOneCluster object", typeMeta.Kind))
}
if _, ok := AllowedAPIs[typeMeta.APIVersion]; !ok {
return nil, fail.ConfigValidation(fmt.Errorf("provided apiVersion %q is not supported", typeMeta.APIVersion))
return "", fail.ConfigValidation(fmt.Errorf("provided apiVersion %q is not supported", typeMeta.APIVersion))
}
if _, ok := DeprecatedAPIs[typeMeta.APIVersion]; ok {
logger.Warningf(`The provided APIVersion %q is deprecated. Please use "kubeone config migrate" command to migrate to the latest version.`, typeMeta.APIVersion)

return typeMeta.APIVersion, nil
}

// BytesToKubeOneCluster parses the bytes of the versioned KubeOneCluster manifests
func BytesToKubeOneCluster(cluster, tfOutput []byte, credentialsFilePath string, logger logrus.FieldLogger, baseDir string) (*kubeoneapi.KubeOneCluster, error) {
apiVersion, err := KubeOneClusterAPIVersion(cluster)
if err != nil {
return nil, err
}

var (
internalCluster *kubeoneapi.KubeOneCluster
err error
)
if _, ok := DeprecatedAPIs[apiVersion]; ok {
logger.Warningf(`The provided APIVersion %q is deprecated. Please use "kubeone config migrate" command to migrate to the latest version.`, apiVersion)
}

var internalCluster *kubeoneapi.KubeOneCluster

// Parse the cluster bytes depending on the GVK
switch typeMeta.APIVersion {
switch apiVersion {
case kubeonev1beta2.SchemeGroupVersion.String():
v1beta2Cluster := kubeonev1beta2.NewKubeOneCluster()
if err = runtime.DecodeInto(kubeonescheme.Codecs.UniversalDecoder(), cluster, v1beta2Cluster); err != nil {
if err := runtime.DecodeInto(kubeonescheme.Codecs.UniversalDecoder(), cluster, v1beta2Cluster); err != nil {
return nil, fail.Config(err, fmt.Sprintf("decoding %s", v1beta2Cluster.GroupVersionKind()))
}

var err error
internalCluster, err = DefaultedV1Beta2KubeOneCluster(v1beta2Cluster, tfOutput)
if err != nil {
return nil, err
Expand All @@ -176,7 +187,7 @@ func BytesToKubeOneCluster(cluster, tfOutput []byte, credentialsFilePath string,
// return nil, err
// }
default:
return nil, fail.Config(fmt.Errorf("invalid api version %q", typeMeta.APIVersion), "api version")
return nil, fail.Config(fmt.Errorf("invalid api version %q", apiVersion), "api version")
}

if len(internalCluster.ControlPlane.NodeSets) > 0 {
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/kubeone/v1beta2/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ func SetCloudProvider(cp *CloudProviderSpec, name string) error {
return nil
}

func (c *KubeOneCluster) CloudProviderName() string {
return c.CloudProvider.Name()
}

func (cps *CloudProviderSpec) Name() string {
switch {
case cps.AWS != nil:
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/kubeone/v1beta3/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ func SetCloudProvider(cp *CloudProviderSpec, name string) error {
return nil
}

func (c *KubeOneCluster) CloudProviderName() string {
return c.CloudProvider.Name()
}

func (cps *CloudProviderSpec) Name() string {
switch {
case cps.AWS != nil:
Expand Down
102 changes: 98 additions & 4 deletions pkg/cmd/config-images.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,28 @@ package cmd

import (
"fmt"
"os"
"strings"

"github.com/MakeNowJust/heredoc/v2"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"

kubeoneconfig "k8c.io/kubeone/pkg/apis/kubeone/config"
kubeonescheme "k8c.io/kubeone/pkg/apis/kubeone/scheme"
kubeonev1beta2 "k8c.io/kubeone/pkg/apis/kubeone/v1beta2"
kubeonev1beta3 "k8c.io/kubeone/pkg/apis/kubeone/v1beta3"
"k8c.io/kubeone/pkg/fail"
"k8c.io/kubeone/pkg/templates/images"
)

type listImagesOpts struct {
ManifestFile string `longflag:"manifest" shortflag:"m"`
Filter string `longflag:"filter"`
Provider string `longflag:"provider"`
KubernetesVersion string `longflag:"kubernetes-version" shortflag:"k"`
AllImages bool `longflag:"all" shortflag:"a"`
}
Expand Down Expand Up @@ -69,6 +78,9 @@ func listImagesCmd(rootFlags *pflag.FlagSet) *cobra.Command {

# To see images list affected by the registryConfiguration configuration (in case if any)
kubeone config images list -m mycluster.yaml

# To see images only related to a specific provider
kubeone config images list --provider aws
`),
SilenceErrors: true,
RunE: func(*cobra.Command, []string) error {
Expand Down Expand Up @@ -101,6 +113,14 @@ func listImagesCmd(rootFlags *pflag.FlagSet) *cobra.Command {
"list all images, including optional ones",
)

cmd.Flags().StringVar(
&opts.Provider,
longFlagName(opts, "Provider"),
"",
fmt.Sprintf("filter images for a specific cloud provider, one of [%s]",
strings.Join(images.SupportedProviders(), "|")),
)

return cmd
}

Expand All @@ -125,16 +145,90 @@ func listImages(opts *listImagesOpts) error {
return err
}

var images []string
// Determine the active provider: explicit flag takes priority, then
// auto-detect from the manifest's cloudProvider field.
provider := opts.Provider
if provider == "" && opts.ManifestFile != "" {
provider, err = detectProviderFromManifest(opts.ManifestFile)
if err != nil {
return err
}
}

var images sets.Set[string]

if opts.AllImages {
images = imgResolver.ListAll()
images = sets.New(imgResolver.ListAll()...)
} else {
images = imgResolver.List(listFilter)
images = sets.New(imgResolver.List(listFilter)...)
}

for _, img := range images {
if provider != "" {
provImages, err := imgResolver.ListForProvider(provider)
if err != nil {
return fail.RuntimeError{Op: "listing images for provider", Err: err}
}
images = images.Intersection(sets.New(provImages...))
}

for _, img := range sets.List(images) {
fmt.Println(img)
}

return nil
}

// detectProviderFromManifest reads the KubeOneCluster manifest and returns the
// cloud provider name as reported by CloudProviderSpec.Name(). Returns an
// empty string when the manifest file cannot be read or when no provider is
// configured ("none" / "unknown").
func detectProviderFromManifest(manifestFile string) (string, error) {
configBuf, err := os.ReadFile(manifestFile)
if err != nil {
// manifest not accessible – silently skip provider detection
return "", nil
}

apiVersion, err := kubeoneconfig.KubeOneClusterAPIVersion(configBuf)
if err != nil {
return "", fail.RuntimeError{Op: "parsing manifest for provider detection", Err: err}
}

var providerName string

switch apiVersion {
case kubeonev1beta2.SchemeGroupVersion.String():
providerName, err = inspectCluster(configBuf, kubeonev1beta2.NewKubeOneCluster)
case kubeonev1beta3.SchemeGroupVersion.String():
providerName, err = inspectCluster(configBuf, kubeonev1beta3.NewKubeOneCluster)
}
if err != nil {
return "", err
}

if providerName == "none" || providerName == "unknown" || providerName == "" {
return "", nil
}

return providerName, nil
}

type cloudProviderNamer interface {
CloudProviderName() string
runtime.Object
}

// inspectCluster decodes a raw KubeOneCluster manifest into the
// versioned type T (created by newCluster), then calls inspectFn to
// extract the cloud provider name string. T must implement runtime.Object.
func inspectCluster[T cloudProviderNamer](
configBuf []byte,
newCluster func() T,
) (string, error) {
cluster := newCluster()
if err := runtime.DecodeInto(kubeonescheme.Codecs.UniversalDecoder(), configBuf, cluster); err != nil {
return "", fail.Config(err, fmt.Sprintf("decoding %s", cluster.GetObjectKind().GroupVersionKind()))
}

return cluster.CloudProviderName(), nil
}
Loading