Skip to content
Open
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
13 changes: 7 additions & 6 deletions api/v1alpha1/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
package v1alpha1

const (
DefaultIgnitionKey = "ignition" // Key for accessing Ignition configuration data within a Kubernetes Secret object.
DefaultIPXEScriptKey = "ipxe-script" // Key for accessing iPXE script data within the iPXE-specific Secret object.
SystemUUIDIndexKey = "spec.systemUUID" // Field to index resources by their system UUID.
SystemIPIndexKey = "spec.systemIPs" // Field to index resources by their system IP addresses.
DefaultFormatKey = "format" // Key for determining the format of the data stored in a Secret, such as fcos or plain-ignition.
FCOSFormat = "fcos" // Specifies the format value used for Fedora CoreOS specific configurations.
DefaultIgnitionKey = "ignition" // Key for accessing Ignition configuration data within a Kubernetes Secret object.
DefaultIPXEScriptKey = "ipxe-script" // Key for accessing iPXE script data within the iPXE-specific Secret object.
SystemUUIDIndexKey = "spec.systemUUID" // Field to index resources by their system UUID.
SystemIPIndexKey = "spec.systemIPs" // Field to index resources by their system IP addresses.
NetworkIdentifierIndexKey = "spec.networkIdentifiers" // Field to index resources by their network identifiers (IP addresses and MAC addresses).
DefaultFormatKey = "format" // Key for determining the format of the data stored in a Secret, such as fcos or plain-ignition.
FCOSFormat = "fcos" // Specifies the format value used for Fedora CoreOS specific configurations.
)
2 changes: 1 addition & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ func IndexHTTPBootConfigByNetworkIDs(ctx context.Context, mgr ctrl.Manager) erro
return mgr.GetFieldIndexer().IndexField(
ctx,
&bootv1alpha1.HTTPBootConfig{},
bootv1alpha1.SystemIPIndexKey,
bootv1alpha1.NetworkIdentifierIndexKey,
func(Obj client.Object) []string {
HTTPBootConfig := Obj.(*bootv1alpha1.HTTPBootConfig)
return HTTPBootConfig.Spec.NetworkIdentifiers
Expand Down
2 changes: 1 addition & 1 deletion server/bootserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ func handleHTTPBoot(w http.ResponseWriter, r *http.Request, k8sClient client.Cli

var httpBootConfigs bootv1alpha1.HTTPBootConfigList
for _, ip := range clientIPs {
if err := k8sClient.List(ctx, &httpBootConfigs, client.MatchingFields{bootv1alpha1.SystemIPIndexKey: ip}); err != nil {
if err := k8sClient.List(ctx, &httpBootConfigs, client.MatchingFields{bootv1alpha1.NetworkIdentifierIndexKey: ip}); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a field indexer setup for this field?

log.Info("Failed to list HTTPBootConfig for IP", "IP", ip, "error", err)
continue
}
Expand Down
53 changes: 53 additions & 0 deletions server/bootserver_suit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package server

import (
"net/http"
"testing"

"github.com/go-logr/logr"
bootv1alpha1 "github.com/ironcore-dev/boot-operator/api/v1alpha1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

var (
testServerAddr = ":30003"
testServerURL = "http://localhost:30003"

defaultUKIURL = "https://example.com/default.efi"
ipxeServiceURL = "http://localhost:30004"

k8sClient client.Client
)

func TestBootServer(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Boot Server Suite")
}

var _ = BeforeSuite(func() {
scheme := runtime.NewScheme()
Expect(corev1.AddToScheme(scheme)).To(Succeed())
Expect(bootv1alpha1.AddToScheme(scheme)).To(Succeed())

k8sClient = fake.NewClientBuilder().
WithScheme(scheme).
Build()

go func() {
defer GinkgoRecover()
RunBootServer(testServerAddr, ipxeServiceURL, k8sClient, logr.Discard(), defaultUKIURL)
}()

Eventually(func() error {
_, err := http.Get(testServerURL + "/httpboot")
return err
}, "5s", "200ms").Should(Succeed())
})
119 changes: 119 additions & 0 deletions server/bootserver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package server

import (
"context"
"encoding/json"
"net/http"

"github.com/go-logr/logr"
bootv1alpha1 "github.com/ironcore-dev/boot-operator/api/v1alpha1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type httpBootResponse struct {
ClientIPs string `json:"ClientIPs"`
UKIURL string `json:"UKIURL"`
SystemUUID string `json:"SystemUUID,omitempty"`
}

var _ = Describe("BootServer", func() {
Context("/httpboot endpoint", func() {
It("delivers default httpboot data when no HTTPBootConfig matches the client IP", func() {
resp, err := http.Get(testServerURL + "/httpboot")
Expect(err).NotTo(HaveOccurred())
defer func() {
_ = resp.Body.Close()
}()

Expect(resp.StatusCode).To(Equal(http.StatusOK))
Expect(resp.Header.Get("Content-Type")).To(Equal("application/json"))

var body httpBootResponse
Expect(json.NewDecoder(resp.Body).Decode(&body)).To(Succeed())

By("returning the default UKI URL")
Expect(body.UKIURL).To(Equal(defaultUKIURL))

By("including the recorded client IPs")
Expect(body.ClientIPs).NotTo(BeEmpty())

By("not setting a SystemUUID in the default case")
Expect(body.SystemUUID).To(SatisfyAny(BeEmpty(), Equal("")))
})
})

It("converts valid Butane YAML to JSON", func() {
butaneYAML := []byte(`
variant: fcos
version: 1.5.0
systemd:
units:
- name: test.service
enabled: true
`)

jsonData, err := renderIgnition(butaneYAML)
Expect(err).ToNot(HaveOccurred())
Expect(jsonData).ToNot(BeEmpty())
Expect(string(jsonData)).To(ContainSubstring(`"systemd"`))
})

It("returns an error for invalid YAML", func() {
bad := []byte("this ::: is not yaml")
_, err := renderIgnition(bad)
Expect(err).To(HaveOccurred())
})

Context("Verify the SetStatusCondition method", func() {

var testLog = logr.Discard()

It("returns an error for unknown condition type", func() {
cfg := &bootv1alpha1.IPXEBootConfig{
ObjectMeta: v1.ObjectMeta{
Name: "unknown-cond",
Namespace: "default",
},
}
Expect(k8sClient.Create(context.Background(), cfg)).To(Succeed())

err := SetStatusCondition(
context.Background(),
k8sClient,
testLog,
cfg,
"DoesNotExist",
)

Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("condition type DoesNotExist not found"))
})

It("returns an error for unsupported resource types", func() {
secret := &corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Name: "bad-type",
Namespace: "default",
},
}
_ = k8sClient.Create(context.Background(), secret)

err := SetStatusCondition(
context.Background(),
k8sClient,
testLog,
secret,
"IgnitionDataFetched",
)

Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("unsupported resource type"))
})
})
})
Loading