Skip to content

Commit

Permalink
Add flags to metalctl move
Browse files Browse the repository at this point in the history
  • Loading branch information
SzymonSAP committed Nov 4, 2024
1 parent 0a325e6 commit dadab5a
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 45 deletions.
9 changes: 0 additions & 9 deletions cmd/metalctl/app/app.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
package app

import (
"path/filepath"

"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"

metalv1alphav1 "github.com/ironcore-dev/metal-operator/api/v1alpha1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
Expand All @@ -34,7 +29,3 @@ func NewCommand() *cobra.Command {
root.AddCommand(NewMoveCommand())
return root
}

func GetKubeconfig() (*rest.Config, error) {
return clientcmd.BuildConfigFromFlags("", filepath.Join(homedir.HomeDir(), ".kube", "config"))
}
109 changes: 73 additions & 36 deletions cmd/metalctl/app/move.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"log/slog"

metalv1alphav1 "github.com/ironcore-dev/metal-operator/api/v1alpha1"
"github.com/spf13/cobra"
Expand All @@ -14,8 +15,13 @@ import (
)

var (
sourceKubeconfig string
targetKubeconfig string
errCrdCreate error = errors.New("failed to create metal CRDs")
crdsOnly bool
crsOnly bool
namespace string
dryRun bool
verbose bool
)

func NewMoveCommand() *cobra.Command {
Expand All @@ -24,48 +30,83 @@ func NewMoveCommand() *cobra.Command {
Short: "Move metal-operator CRDs and CRs from one cluster to another",
RunE: runMove,
}
move.Flags().StringVar(&sourceKubeconfig, "source-kubeconfig", "", "Kubeconfig pointing to the source cluster")
move.Flags().StringVar(&targetKubeconfig, "target-kubeconfig", "", "Kubeconfig pointing to the target cluster")
move.Flags().BoolVar(&crdsOnly, "crds-only", false, "migrate only the CRDs without CRs")
move.Flags().BoolVar(&crsOnly, "crs-only", false, "migrate only the CRs without CRDs")
move.Flags().StringVar(&namespace, "namespace", "", "namespace to filter CRDs and CRs to migrate. Defaults to all namespaces if not specified")
move.Flags().BoolVar(&dryRun, "dry-run", false, "show what would be moved without executing the migration")
move.Flags().BoolVar(&verbose, "verbose", false, "enable verbose logging for detailed output during migration")
move.MarkFlagRequired("source-kubeconfig")

Check failure on line 40 in cmd/metalctl/app/move.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `move.MarkFlagRequired` is not checked (errcheck)
move.MarkFlagRequired("target-kubeconfig")

Check failure on line 41 in cmd/metalctl/app/move.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `move.MarkFlagRequired` is not checked (errcheck)

if verbose {
slog.SetLogLoggerLevel(slog.LevelDebug)
}
return move
}

type clients struct {
type Clients struct {
source client.Client
target client.Client
}

func makeClients() (clients, error) {
var clients clients
sourceCfg, err := GetKubeconfig()
func makeClient(kubeconfig string) (client.Client, error) {
cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
return clients, fmt.Errorf("failed to load source cluster kubeconfig: %w", err)
return nil, fmt.Errorf("failed to load cluster kubeconfig: %w", err)
}
clients.source, err = client.New(sourceCfg, client.Options{Scheme: scheme})
return client.New(cfg, client.Options{Scheme: scheme})
}

func makeClients() (Clients, error) {
var clients Clients
var err error

clients.source, err = makeClient(sourceKubeconfig)
if err != nil {
return clients, fmt.Errorf("failed to construct source cluster client: %w", err)
}
targetCfg, err := clientcmd.BuildConfigFromFlags("", targetKubeconfig)
if err != nil {
return clients, fmt.Errorf("failed to load target cluster kubeconfig: %w", err)
}
clients.target, err = client.New(targetCfg, client.Options{Scheme: scheme})
clients.target, err = makeClient(targetKubeconfig)
if err != nil {
return clients, fmt.Errorf("failed to construct target cluster client: %w", err)
}
return clients, nil
}

func moveCRDs(ctx context.Context, clients clients) error {
func getMetalCRDs(ctx context.Context, cl client.Client) ([]apiextensionsv1.CustomResourceDefinition, error) {
var crds apiextensionsv1.CustomResourceDefinitionList
if err := clients.source.List(ctx, &crds); err != nil {
return err
listOpts := []client.ListOption{}
if namespace != "" {
listOpts = append(listOpts, client.InNamespace(namespace))
}
if err := cl.List(ctx, &crds, listOpts...); err != nil {
return nil, err
}
metalCrds := make([]apiextensionsv1.CustomResourceDefinition, 0)
for _, crd := range crds.Items {
if crd.Spec.Group == metalv1alphav1.GroupVersion.Group {
metalCrds = append(metalCrds, crd)
}
}
return metalCrds, nil
}

func getMetalCRDsNames(metalCRDs []apiextensionsv1.CustomResourceDefinition) []string {
ret := make([]string, len(metalCRDs))
for i, crd := range metalCRDs {
ret[i] = crd.Spec.Names.Plural
}
return ret
}

func moveCRDs(ctx context.Context, clients Clients) error {
metalCrds, err := getMetalCRDs(ctx, clients.source)
if err != nil {
return err
}
slog.Debug(fmt.Sprintf("found %s CRDs in the source cluster", metalv1alphav1.GroupVersion.Group), slog.Any("CRDs", getMetalCRDsNames(metalCrds)))

// it may be better to compare on semantics instead of CRD name
for _, sourceCrd := range metalCrds {
var targetCrd apiextensionsv1.CustomResourceDefinition
Expand All @@ -78,26 +119,29 @@ func moveCRDs(ctx context.Context, clients clients) error {
}
return fmt.Errorf("CRD for %s/%s already exists in the target cluster", sourceCrd.Spec.Group, sourceCrd.Spec.Names.Plural)
}
for _, crd := range metalCrds {
crd.ResourceVersion = ""
if err := clients.target.Create(ctx, &crd); err != nil {
return errCrdCreate
slog.Debug(fmt.Sprintf("all %s CRDs from the source cluster are absent in the target cluster", metalv1alphav1.GroupVersion.Group))

if !dryRun {
for _, crd := range metalCrds {
crd.ResourceVersion = ""
if err := clients.target.Create(ctx, &crd); err != nil {
cleanupErr := cleanupCRDs(ctx, clients)
return fmt.Errorf("CRD %s/%s couldn't be created in the target cluster: %w. Clean up was performed to restore a target cluster's state with error result: %w", crd.Spec.Group, crd.Spec.Names.Plural, err, cleanupErr)
}
}
slog.Debug(fmt.Sprintf("all %s CRDs was moved", metalv1alphav1.GroupVersion.Group))
}
return nil
}

func cleanupCRDs(ctx context.Context, clients clients) error {
var crds apiextensionsv1.CustomResourceDefinitionList
if err := clients.target.List(ctx, &crds); err != nil {
func cleanupCRDs(ctx context.Context, clients Clients) error {
slog.Debug(fmt.Sprintf("cleaning %s CRDs", metalv1alphav1.GroupVersion.Group))
metalCrds, err := getMetalCRDs(ctx, clients.target)
if err != nil {
return err
}
metalCrds := make([]apiextensionsv1.CustomResourceDefinition, 0)
for _, crd := range crds.Items {
if crd.Spec.Group == metalv1alphav1.GroupVersion.Group {
metalCrds = append(metalCrds, crd)
}
}
slog.Debug(fmt.Sprintf("found %s CRDs in the target cluster", metalv1alphav1.GroupVersion.Group), slog.Any("CRDs", getMetalCRDsNames(metalCrds)))

errs := make([]error, 0)
for _, crd := range metalCrds {
crd.ResourceVersion = ""
Expand All @@ -114,12 +158,5 @@ func runMove(cmd *cobra.Command, args []string) error {
return err
}
ctx := cmd.Context()
err = moveCRDs(ctx, clients)
switch {
case errors.Is(err, errCrdCreate):
return cleanupCRDs(ctx, clients)
case err != nil:
return err
}
return nil
return moveCRDs(ctx, clients)
}

0 comments on commit dadab5a

Please sign in to comment.