Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add metalctl console subcommand
Browse files Browse the repository at this point in the history
- Add a `metalctl console` subcommand with which you are able to connect
  to a `Servers` serial console
- Factor out `bmcutils` into an own package
afritzler committed Dec 9, 2024
1 parent 3fa1f4e commit 198f444
Showing 13 changed files with 556 additions and 31 deletions.
1 change: 1 addition & 0 deletions cmd/metalctl/app/app.go
Original file line number Diff line number Diff line change
@@ -30,5 +30,6 @@ func NewCommand() *cobra.Command {
Args: cobra.NoArgs,
}
root.AddCommand(NewMoveCommand())
root.AddCommand(NewConsoleCommand())
return root
}
176 changes: 176 additions & 0 deletions cmd/metalctl/app/console.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package app

import (
"context"
"fmt"
"io"
"log"
"log/slog"
"net"
"os"

"github.com/ironcore-dev/metal-operator/internal/console"

"github.com/spf13/cobra"
"golang.org/x/crypto/ssh"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)

var (
kubeconfigPath string
kubeconfig string
serialConsoleNumber int
)

func NewConsoleCommand() *cobra.Command {
consoleCmd := &cobra.Command{
Use: "console",
Short: "Access the serial console of a Server",
RunE: runConsole,
}

consoleCmd.Flags().StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig.")
consoleCmd.Flags().IntVar(&serialConsoleNumber, "serial-console-number", 1, "Serial console number.")

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

func runConsole(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("server name is required")
}
var serverName string
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
serverName = args[0]

k8sClient, err := createClient()
if err != nil {
return err
}

if err := openConsoleStream(cmd.Context(), k8sClient, serverName); err != nil {
return err
}

return nil
}

func openConsoleStream(ctx context.Context, k8sClient client.Client, serverName string) error {
consoleConfig, err := console.GetConfigForServerName(ctx, k8sClient, serverName)
if err != nil {
return fmt.Errorf("failed to get console config: %w", err)
}
if consoleConfig == nil {
return fmt.Errorf("console config is nil")
}

// Create SSH client configuration
sshConfig := &ssh.ClientConfig{
User: consoleConfig.Username,
Auth: []ssh.AuthMethod{
ssh.Password(consoleConfig.Password),
},
// TODO: use proper key verification
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}

// Connect to the BMC
bmcAddress := net.JoinHostPort(consoleConfig.BMCAddress, "22")
conn, err := ssh.Dial("tcp", bmcAddress, sshConfig)
if err != nil {
return fmt.Errorf("failed to connect to BMC: %w", err)
}
defer func(conn *ssh.Client) {
if err = conn.Close(); err != nil {
log.Printf("failed to close SSH connection: %v", err)
}
}(conn)

// Start a session
session, err := conn.NewSession()
if err != nil {
return fmt.Errorf("failed to create SSH session: %w", err)
}
defer func(session *ssh.Session) {
if err = session.Close(); err != nil {
log.Printf("failed to close SSH session: %v", err)
}
}(session)

// Request a pseudo-terminal for interactive sessions
if err = session.RequestPty("xterm", 80, 40, ssh.TerminalModes{
ssh.ECHO: 0, // Disable echo
ssh.TTY_OP_ISPEED: 14400, // Input speed
ssh.TTY_OP_OSPEED: 14400, // Output speed
}); err != nil {
return fmt.Errorf("failed to request pseudo-terminal failed: %w", err)
}

// Start the SOL session
stdin, err := session.StdinPipe()
if err != nil {
return fmt.Errorf("could not get stdin pipe: %w", err)
}
stdout, err := session.StdoutPipe()
if err != nil {
return fmt.Errorf("could not get stdout pipe: %w", err)
}

go func() {
_, err = io.Copy(os.Stdout, stdout)
if err != nil {
log.Printf("failed to copy stdout: %s", err)
}
}() // Stream the SOL output to the terminal

if err = session.Start(fmt.Sprintf("console %d", serialConsoleNumber)); err != nil {
return fmt.Errorf("failed to start SOL command: %w", err)
}

log.Println("Serial-over-LAN session active. Press Ctrl+C to exit.")
go func() {
// Allow sending input to the session
_, err = io.Copy(stdin, os.Stdin)
if err != nil {
log.Printf("failed to copy stdin: %s", err)
}
}()

// Wait for the session to end
if err := session.Wait(); err != nil {
return fmt.Errorf("error during SOL session: %v", err)
}
return nil
}

func createClient() (client.Client, error) {
if kubeconfig != "" {
kubeconfigPath = kubeconfig
} else {
kubeconfigPath = os.Getenv("KUBECONFIG")
if kubeconfigPath == "" {
fmt.Println("Error: --kubeconfig flag or KUBECONFIG environment variable must be set")
os.Exit(1)
}
}

clientConfig, err := config.GetConfigWithContext("")
if err != nil {
return nil, fmt.Errorf("failed getting client config: %w", err)
}

k8sClient, err := client.New(clientConfig, client.Options{Scheme: scheme})
if err != nil {
return nil, fmt.Errorf("failed creating controller-runtime client: %w", err)
}
return k8sClient, nil
}
15 changes: 15 additions & 0 deletions docs/usage/metalctl.md
Original file line number Diff line number Diff line change
@@ -10,6 +10,21 @@ go install https://github.com/ironcore-dev/metal-operator/cmd/metalctl@latest

## Commands

### console

The `metalctl console` command allows you to access the serial console of a `Server`.

To open a connection to the `Servers` serial console run

```bash
metalctl console my-server
```

In order to authenticate against the API server you need either to provide a path to a `kubeconfig` via `--kubeconfig`
or set the `KUBECONFIG` environment variable by pointing to an effective `kubeconfig` file.

By default, the serial console on `ttyS1` will be opened. You can override this by setting `--serial-console-number`.

### move

The `metalctl move` command allows to move the metal Custom Resources, like e.g. `Endpoint`, `BMC`, `Server`, etc. from one
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -7,8 +7,9 @@ require (
github.com/ironcore-dev/controller-utils v0.9.5
github.com/onsi/ginkgo/v2 v2.22.0
github.com/onsi/gomega v1.36.0
github.com/stmcginnis/gofish v0.20.0
github.com/spf13/cobra v1.8.1
github.com/stmcginnis/gofish v0.20.0
golang.org/x/crypto v0.28.0
k8s.io/api v0.31.1
k8s.io/apiextensions-apiserver v0.31.0
k8s.io/apimachinery v0.31.1
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -51,10 +51,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/ironcore-dev/controller-utils v0.9.5 h1:vdUnCNolC0uDMZy5d8Noib5kdqdK9YpelVSgPc30qTc=
github.com/ironcore-dev/controller-utils v0.9.5/go.mod h1:pzrmJmc6LXtn48cTAgKHm5i8ry6q1Qw7SUh0XaYfIP4=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/ironcore-dev/controller-utils v0.9.5 h1:vdUnCNolC0uDMZy5d8Noib5kdqdK9YpelVSgPc30qTc=
github.com/ironcore-dev/controller-utils v0.9.5/go.mod h1:pzrmJmc6LXtn48cTAgKHm5i8ry6q1Qw7SUh0XaYfIP4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -129,6 +129,8 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
65 changes: 50 additions & 15 deletions internal/controller/bmcutils.go → internal/bmcutils/bmcutils.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,66 @@
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package controller
package bmcutils

import (
"context"
"fmt"
"net"

metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1"
"github.com/ironcore-dev/metal-operator/bmc"

metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func GetBMCCredentialsFromSecret(secret *metalv1alpha1.BMCSecret) (string, string, error) {
// TODO: use constants for secret keys
username, ok := secret.Data["username"]
if !ok {
return "", "", fmt.Errorf("no username found in the BMC secret")
}
password, ok := secret.Data["password"]
if !ok {
return "", "", fmt.Errorf("no password found in the BMC secret")
}
return string(username), string(password), nil
}

func GetBMCFromBMCName(ctx context.Context, c client.Client, bmcName string) (*metalv1alpha1.BMC, error) {
bmcObj := &metalv1alpha1.BMC{}
if err := c.Get(ctx, client.ObjectKey{Name: bmcName}, bmcObj); err != nil {
return nil, fmt.Errorf("failed to get bmc %q: %w", bmcName, err)
}
return bmcObj, nil
}

func GetBMCCredentialsForBMCSecretName(ctx context.Context, c client.Client, bmcSecretName string) (string, string, error) {
bmcSecret := &metalv1alpha1.BMCSecret{}
if err := c.Get(ctx, client.ObjectKey{Name: bmcSecretName}, bmcSecret); err != nil {
return "", "", fmt.Errorf("failed to get bmc secret: %w", err)
}
return GetBMCCredentialsFromSecret(bmcSecret)
}

func GetBMCAddressForBMC(ctx context.Context, c client.Client, bmcObj *metalv1alpha1.BMC) (string, error) {
var address string

if bmcObj.Spec.EndpointRef != nil {
endpoint := &metalv1alpha1.Endpoint{}
if err := c.Get(ctx, client.ObjectKey{Name: bmcObj.Spec.EndpointRef.Name}, endpoint); err != nil {
return "", fmt.Errorf("failed to get Endpoints for BMC: %w", err)
}
return endpoint.Spec.IP.String(), nil
}

if bmcObj.Spec.Endpoint != nil {
return bmcObj.Spec.Endpoint.IP.String(), nil
}

return address, nil
}

const DefaultKubeNamespace = "default"

func GetBMCClientForServer(ctx context.Context, c client.Client, server *metalv1alpha1.Server, insecure bool, options bmc.BMCOptions) (bmc.BMC, error) {
@@ -124,19 +172,6 @@ func CreateBMCClient(
return bmcClient, nil
}

func GetBMCCredentialsFromSecret(secret *metalv1alpha1.BMCSecret) (string, string, error) {
// TODO: use constants for secret keys
username, ok := secret.Data["username"]
if !ok {
return "", "", fmt.Errorf("no username found in the BMC secret")
}
password, ok := secret.Data["password"]
if !ok {
return "", "", fmt.Errorf("no password found in the BMC secret")
}
return string(username), string(password), nil
}

func GetServerNameFromBMCandIndex(index int, bmc *metalv1alpha1.BMC) string {
return fmt.Sprintf("%s-%s-%d", bmc.Name, "system", index)
}
63 changes: 63 additions & 0 deletions internal/console/console.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package console

import (
"context"
"fmt"

"github.com/ironcore-dev/metal-operator/api/v1alpha1"
"github.com/ironcore-dev/metal-operator/internal/bmcutils"
"sigs.k8s.io/controller-runtime/pkg/client"
)

type Config struct {
BMCAddress string
Username string
Password string
}

func GetConfigForServerName(ctx context.Context, c client.Client, serverName string) (*Config, error) {
server := &v1alpha1.Server{}
if err := c.Get(ctx, client.ObjectKey{Name: serverName}, server); err != nil {
return nil, fmt.Errorf("failed to get server %q: %w", serverName, err)
}

// Inline BMC configuration
if server.Spec.BMC != nil {
username, password, err := bmcutils.GetBMCCredentialsForBMCSecretName(ctx, c, server.Spec.BMC.BMCSecretRef.Name)
if err != nil {
return nil, err
}
return &Config{
BMCAddress: server.Spec.BMC.Address,
Username: username,
Password: password,
}, nil
}

// BMC by reference
if server.Spec.BMCRef != nil {
bmc, err := bmcutils.GetBMCFromBMCName(ctx, c, server.Spec.BMCRef.Name)
if err != nil {
return nil, err
}
username, password, err := bmcutils.GetBMCCredentialsForBMCSecretName(ctx, c, bmc.Spec.BMCSecretRef.Name)
if err != nil {
return nil, err
}
address, err := bmcutils.GetBMCAddressForBMC(ctx, c, bmc)
if err != nil {
return nil, err
}

return &Config{
BMCAddress: address,
Username: username,
Password: password,
}, nil
}

return nil, nil
}
111 changes: 111 additions & 0 deletions internal/console/console_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package console

import (
metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var _ = Describe("Console Access", func() {
_ = SetupTest()

It("Should successfully construct console config for Server with inline configuration", func(ctx SpecContext) {
By("Creating a BMCSecret")
bmcSecret := &metalv1alpha1.BMCSecret{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
},
Data: map[string][]byte{
"username": []byte("foo"),
"password": []byte("bar"),
},
}
Expect(k8sClient.Create(ctx, bmcSecret)).To(Succeed())
DeferCleanup(k8sClient.Delete, bmcSecret)

By("Creating a Server object")
server := &metalv1alpha1.Server{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
},
Spec: metalv1alpha1.ServerSpec{
BMC: &metalv1alpha1.BMCAccess{
Protocol: metalv1alpha1.Protocol{},
Address: "10.0.0.1",
BMCSecretRef: corev1.LocalObjectReference{
Name: bmcSecret.Name,
},
},
},
}
Expect(k8sClient.Create(ctx, server)).To(Succeed())
DeferCleanup(k8sClient.Delete, server)

config, err := GetConfigForServerName(ctx, k8sClient, server.Name)
Expect(err).NotTo(HaveOccurred())
Expect(config).To(Equal(&Config{
BMCAddress: "10.0.0.1",
Username: "foo",
Password: "bar",
}))
})

It("Should successfully construct console config for Server with a BMC ref", func(ctx SpecContext) {
By("Creating a BMCSecret")
bmcSecret := &metalv1alpha1.BMCSecret{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
},
Data: map[string][]byte{
"username": []byte("foo"),
"password": []byte("bar"),
},
}
Expect(k8sClient.Create(ctx, bmcSecret)).To(Succeed())
DeferCleanup(k8sClient.Delete, bmcSecret)

bmc := &metalv1alpha1.BMC{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
},
Spec: metalv1alpha1.BMCSpec{
BMCSecretRef: corev1.LocalObjectReference{
Name: bmcSecret.Name,
},
Endpoint: &metalv1alpha1.InlineEndpoint{
MACAddress: "aa:bb:cc:dd",
IP: metalv1alpha1.MustParseIP("10.0.0.1"),
},
},
}
Expect(k8sClient.Create(ctx, bmc)).To(Succeed())
DeferCleanup(k8sClient.Delete, bmc)

By("Creating a Server object")
server := &metalv1alpha1.Server{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
},
Spec: metalv1alpha1.ServerSpec{
BMCRef: &corev1.LocalObjectReference{
Name: bmc.Name,
},
},
}
Expect(k8sClient.Create(ctx, server)).To(Succeed())
DeferCleanup(k8sClient.Delete, server)

config, err := GetConfigForServerName(ctx, k8sClient, server.Name)
Expect(err).NotTo(HaveOccurred())
Expect(config).To(Equal(&Config{
BMCAddress: "10.0.0.1",
Username: "foo",
Password: "bar",
}))
})
})
116 changes: 116 additions & 0 deletions internal/console/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package console

import (
"context"
"fmt"
"path/filepath"
"runtime"
"testing"
"time"

metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1"
"github.com/ironcore-dev/metal-operator/internal/registry"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
. "sigs.k8s.io/controller-runtime/pkg/envtest/komega"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
//+kubebuilder:scaffold:imports
)

const (
pollingInterval = 50 * time.Millisecond
eventuallyTimeout = 3 * time.Second
consistentlyDuration = 1 * time.Second
)

var (
cfg *rest.Config
k8sClient client.Client
testEnv *envtest.Environment
)

func TestControllers(t *testing.T) {
SetDefaultConsistentlyPollingInterval(pollingInterval)
SetDefaultEventuallyPollingInterval(pollingInterval)
SetDefaultEventuallyTimeout(eventuallyTimeout)
SetDefaultConsistentlyDuration(consistentlyDuration)
RegisterFailHandler(Fail)

RunSpecs(t, "Controller Suite")
}

var _ = BeforeSuite(func() {
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))

By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")},
ErrorIfCRDPathMissing: true,

// The BinaryAssetsDirectory is only required if you want to run the tests directly
// without call the makefile target test. If not informed it will look for the
// default path defined in controller-runtime which is /usr/local/kubebuilder/.
// Note that you must have the required binaries setup under the bin directory to perform
// the tests directly. When we run make test it will be setup and used automatically.
BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s",
fmt.Sprintf("1.31.0-%s-%s", runtime.GOOS, runtime.GOARCH)),
}

var err error
// cfg is defined in this file globally.
cfg, err = testEnv.Start()
Expect(err).NotTo(HaveOccurred())
Expect(cfg).NotTo(BeNil())

DeferCleanup(testEnv.Stop)

Expect(metalv1alpha1.AddToScheme(scheme.Scheme)).NotTo(HaveOccurred())

err = metalv1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())

//+kubebuilder:scaffold:scheme

k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient).NotTo(BeNil())

// set komega client
SetClient(k8sClient)

By("Starting the registry server")
var mgrCtx context.Context
mgrCtx, cancel := context.WithCancel(context.Background())
DeferCleanup(cancel)
registryServer := registry.NewServer(":30000")
go func() {
defer GinkgoRecover()
Expect(registryServer.Start(mgrCtx)).To(Succeed(), "failed to start registry server")
}()
})

func SetupTest() *corev1.Namespace {
ns := &corev1.Namespace{}

BeforeEach(func(ctx SpecContext) {
*ns = corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
},
}
Expect(k8sClient.Create(ctx, ns)).To(Succeed(), "failed to create test namespace")
DeferCleanup(k8sClient.Delete, ns)
})

return ns
}
7 changes: 4 additions & 3 deletions internal/controller/bmc_controller.go
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ import (
"github.com/ironcore-dev/controller-utils/clientutils"
metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1"
"github.com/ironcore-dev/metal-operator/bmc"
"github.com/ironcore-dev/metal-operator/internal/bmcutils"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -119,7 +120,7 @@ func (r *BMCReconciler) updateBMCStatusDetails(ctx context.Context, log logr.Log
return fmt.Errorf("failed to patch IP and MAC address status: %w", err)
}

bmcClient, err := GetBMCClientFromBMC(ctx, r.Client, bmcObj, r.Insecure, r.BMCPollingOptions)
bmcClient, err := bmcutils.GetBMCClientFromBMC(ctx, r.Client, bmcObj, r.Insecure, r.BMCPollingOptions)
if err != nil {
return fmt.Errorf("failed to create BMC client: %w", err)
}
@@ -149,7 +150,7 @@ func (r *BMCReconciler) updateBMCStatusDetails(ctx context.Context, log logr.Log
}

func (r *BMCReconciler) discoverServers(ctx context.Context, log logr.Logger, bmcObj *metalv1alpha1.BMC) error {
bmcClient, err := GetBMCClientFromBMC(ctx, r.Client, bmcObj, r.Insecure, r.BMCPollingOptions)
bmcClient, err := bmcutils.GetBMCClientFromBMC(ctx, r.Client, bmcObj, r.Insecure, r.BMCPollingOptions)
if err != nil {
return fmt.Errorf("failed to create BMC client: %w", err)
}
@@ -166,7 +167,7 @@ func (r *BMCReconciler) discoverServers(ctx context.Context, log logr.Logger, bm
Kind: "Server",
},
ObjectMeta: metav1.ObjectMeta{
Name: GetServerNameFromBMCandIndex(i, bmcObj),
Name: bmcutils.GetServerNameFromBMCandIndex(i, bmcObj),
},
Spec: metalv1alpha1.ServerSpec{
UUID: strings.ToLower(s.UUID), // always use lower-case uuids
5 changes: 3 additions & 2 deletions internal/controller/bmc_controller_test.go
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ package controller

import (
metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1"
"github.com/ironcore-dev/metal-operator/internal/bmcutils"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
@@ -51,7 +52,7 @@ var _ = Describe("BMC Controller", func() {
By("Ensuring that the Server resource will be removed")
server := &metalv1alpha1.Server{
ObjectMeta: metav1.ObjectMeta{
Name: GetServerNameFromBMCandIndex(0, bmc),
Name: bmcutils.GetServerNameFromBMCandIndex(0, bmc),
},
}
DeferCleanup(k8sClient.Delete, server)
@@ -138,7 +139,7 @@ var _ = Describe("BMC Controller", func() {
By("Ensuring that the Server resource has been created")
server := &metalv1alpha1.Server{
ObjectMeta: metav1.ObjectMeta{
Name: GetServerNameFromBMCandIndex(0, bmc),
Name: bmcutils.GetServerNameFromBMCandIndex(0, bmc),
},
}
Eventually(Object(server)).Should(SatisfyAll(
3 changes: 2 additions & 1 deletion internal/controller/endpoint_controller.go
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ import (
metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1"
"github.com/ironcore-dev/metal-operator/bmc"
"github.com/ironcore-dev/metal-operator/internal/api/macdb"
"github.com/ironcore-dev/metal-operator/internal/bmcutils"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -135,7 +136,7 @@ func (r *EndpointReconciler) reconcile(ctx context.Context, log logr.Logger, end
bmcClient, err := bmc.NewRedfishKubeBMCClient(
ctx,
bmcOptions,
r.Client, DefaultKubeNamespace)
r.Client, bmcutils.DefaultKubeNamespace)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to create BMC client: %w", err)
}
16 changes: 9 additions & 7 deletions internal/controller/server_controller.go
Original file line number Diff line number Diff line change
@@ -12,6 +12,8 @@ import (
"sort"
"time"

"github.com/ironcore-dev/metal-operator/internal/bmcutils"

"github.com/go-logr/logr"
"github.com/ironcore-dev/controller-utils/clientutils"
metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1"
@@ -272,7 +274,7 @@ func (r *ServerReconciler) handleDiscoveryState(ctx context.Context, log logr.Lo
}
log.V(1).Info("Server state set to power on")

bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions)
bmcClient, err := bmcutils.GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions)
if err != nil {
return false, fmt.Errorf("failed to create BMC client: %w", err)
}
@@ -416,7 +418,7 @@ func (r *ServerReconciler) updateServerStatus(ctx context.Context, log logr.Logg
log.V(1).Info("Server has no BMC connection configured")
return nil
}
bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions)
bmcClient, err := bmcutils.GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions)
if err != nil {
return fmt.Errorf("failed to create BMC client: %w", err)
}
@@ -615,7 +617,7 @@ func (r *ServerReconciler) pxeBootServer(ctx context.Context, log logr.Logger, s
return fmt.Errorf("can only PXE boot server with valid BMC ref or inline BMC configuration")
}

bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions)
bmcClient, err := bmcutils.GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions)
defer func() {
if bmcClient != nil {
bmcClient.Logout()
@@ -706,7 +708,7 @@ func (r *ServerReconciler) ensureServerPowerState(ctx context.Context, log logr.
return nil
}

bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions)
bmcClient, err := bmcutils.GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions)
defer func() {
if bmcClient != nil {
bmcClient.Logout()
@@ -816,7 +818,7 @@ func (r *ServerReconciler) applyBootOrder(ctx context.Context, log logr.Logger,
log.V(1).Info("Server has no BMC connection configured")
return nil
}
bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions)
bmcClient, err := bmcutils.GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions)
if err != nil {
return fmt.Errorf("failed to create BMC client: %w", err)
}
@@ -850,7 +852,7 @@ func (r *ServerReconciler) applyBiosSettings(ctx context.Context, log logr.Logge
log.V(1).Info("Server has no BMC connection configured")
return nil
}
bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions)
bmcClient, err := bmcutils.GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions)
if err != nil {
return fmt.Errorf("failed to create BMC client: %w", err)
}
@@ -902,7 +904,7 @@ func (r *ServerReconciler) handleAnnotionOperations(ctx context.Context, log log
if !ok {
return false, nil
}
bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions)
bmcClient, err := bmcutils.GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions)
if err != nil {
return false, fmt.Errorf("failed to create BMC client: %w", err)
}

0 comments on commit 198f444

Please sign in to comment.