Skip to content
Merged
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
4 changes: 2 additions & 2 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ SPDX-PackageDownloadLocation = "https://github.com/ironcore-dev/boot-operator"
[[annotations]]
path = [".github/**", ".gitignore", "CODEOWNERS", "Dockerfile", "Makefile", "PROJECT", "config/**", "gen/**", "go.mod", "go.sum", "hack/**", "server/**", "templates/**", "internal/**", "cmd/**", "api/**", "config/**", "test/**", "CONTRIBUTING.md", "PROJECT", "mkdocs.yml", ".dockerignore", ".golangci.yml", "REUSE.toml"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2024 SAP SE or an SAP affiliate company and IronCore contributors"
SPDX-FileCopyrightText = "2025 SAP SE or an SAP affiliate company and IronCore contributors"
SPDX-License-Identifier = "Apache-2.0"

[[annotations]]
path = ["docs/**", "README.md"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2024 SAP SE or an SAP affiliate company and IronCore contributors"
SPDX-FileCopyrightText = "2025 SAP SE or an SAP affiliate company and IronCore contributors"
SPDX-License-Identifier = "Apache-2.0"
2 changes: 1 addition & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions cmd/bootctl/app/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package app

import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"

bootv1alphav1 "github.com/ironcore-dev/boot-operator/api/v1alpha1"
metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
)

const Name string = "bootctl"

var scheme = runtime.NewScheme()

func init() {
utilruntime.Must(bootv1alphav1.AddToScheme(scheme))
utilruntime.Must(metalv1alpha1.AddToScheme(scheme))
}

func NewCommand() *cobra.Command {
root := &cobra.Command{
Use: Name,
Short: "CLI client for boot-operator",
Args: cobra.NoArgs,
}
root.AddCommand(NewMoveCommand())
return root
}
79 changes: 79 additions & 0 deletions cmd/bootctl/app/move.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package app

import (
"fmt"
"log/slog"

"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/controller-runtime/pkg/client"

utils "github.com/ironcore-dev/boot-operator/cmdutils"
)

var (
sourceKubeconfig string
targetKubeconfig string
namespace string
requireOwners bool
dryRun bool
verbose bool
)

func NewMoveCommand() *cobra.Command {
move := &cobra.Command{
Use: "move",
Short: "Move boot-operator 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().StringVar(&namespace, "namespace", "",
"namespace to filter CRs to migrate. Defaults to all namespaces if not specified")
move.Flags().BoolVar(&requireOwners, "require-owners", false, "if set to true, an error will be returned if for any custom resource an owner ServerBootConfiguration is not present in the target cluster")
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")
_ = move.MarkFlagRequired("target-kubeconfig")

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

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

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

clients.Source, err = makeClient(sourceKubeconfig)
if err != nil {
return clients, fmt.Errorf("failed to construct a source cluster client: %w", err)
}
clients.Target, err = makeClient(targetKubeconfig)
if err != nil {
return clients, fmt.Errorf("failed to construct a target cluster client: %w", err)
}
return clients, nil
}

func runMove(cmd *cobra.Command, args []string) error {
clients, err := makeClients()
if err != nil {
return err
}
ctx := cmd.Context()

return utils.Move(ctx, clients, scheme, namespace, requireOwners, dryRun)
}
19 changes: 19 additions & 0 deletions cmd/bootctl/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package main

import (
"fmt"
"os"

"github.com/ironcore-dev/boot-operator/cmd/bootctl/app"
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
)

func main() {
if err := app.NewCommand().ExecuteContext(signals.SetupSignalHandler()); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
12 changes: 12 additions & 0 deletions cmdutils/clients.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package cmdutils

import "sigs.k8s.io/controller-runtime/pkg/client"

// Clients structure stores information about source and destination cluster clients.
type Clients struct {
Source client.Client
Target client.Client
}
Loading
Loading