diff --git a/internal/controllers/resources/init_containers.go b/internal/controllers/resources/init_containers.go index 31d433469..30484830a 100644 --- a/internal/controllers/resources/init_containers.go +++ b/internal/controllers/resources/init_containers.go @@ -97,10 +97,26 @@ func (f *PodFactory) copyWekaVersionToContainer(pod *v1.Pod) { Command: []string{"sh", "-c"}, Args: []string{ ` - # Copy the actual binary file that command resolves to (follows symlinks) - mkdir -p /shared-weka-version/cli && - cp -a -- "$(readlink -f -- "$(command -v weka)")" /shared-weka-version/cli/weka && - echo "copy-cli init container done" + # Stage the weka CLI outside /opt/weka: the extraction step below bind-mounts + # over /opt/weka, where the CLI lives, and would otherwise hide it. + mkdir -p /shared-weka-version/cli || exit 1 + # the wekactl filename carries a per-image hash and the machine arch + ARCH=$(uname -m) + CLI=$(ls -1 /opt/weka/dist/image/wekactl-*-"$ARCH" 2>/dev/null | head -1) + if [ -z "$CLI" ]; then + # older images ship only the weka binary; resolve the path we shadow + # rather than PATH, so a wrapper script can never be staged onto the + # very path it delegates to + CLI=$(readlink -f -- /usr/bin/weka) + fi + if [ -z "$CLI" ] || [ ! -f "$CLI" ]; then + echo "ERROR: no weka CLI found to stage" >&2 + exit 1 + fi + cp -- "$CLI" /shared-weka-version/cli/weka || exit 1 + # the source is not executable in the image, so set the bit explicitly + chmod 0755 /shared-weka-version/cli/weka || exit 1 + echo "copy-cli init container done: staged $CLI" `, }, VolumeMounts: []v1.VolumeMount{ @@ -148,6 +164,13 @@ func (f *PodFactory) copyWekaVersionToContainer(pod *v1.Pod) { Name: sharedVolumeName, MountPath: sharedVolumeMountPath, }) + // the wrapper at /usr/local/bin/weka execs /usr/bin/weka, so shadowing that path makes the + // container use the CLI staged from the cluster image instead of its own older one + pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, v1.VolumeMount{ + Name: sharedVolumeName, + MountPath: "/usr/bin/weka", + SubPath: "cli/weka", + }) pod.Spec.Volumes = append(pod.Spec.Volumes, v1.Volume{ Name: sharedVolumeName, VolumeSource: v1.VolumeSource{ diff --git a/internal/controllers/wekacontainer/funcs_pod_ensure.go b/internal/controllers/wekacontainer/funcs_pod_ensure.go index 46171b58f..cbd053562 100644 --- a/internal/controllers/wekacontainer/funcs_pod_ensure.go +++ b/internal/controllers/wekacontainer/funcs_pod_ensure.go @@ -123,9 +123,14 @@ func (r *containerReconcilerLoop) ensurePod(ctx context.Context) error { image = drivers.GetBuilderImageForNode(node) } + builderFlags, ffErr := r.GetFeatureFlags(ctx) + if ffErr != nil { + return errors.Wrap(ffErr, "failed to get feature flags for drivers-builder") + } + payloadBytes, _ := json.Marshal(map[string]string{ //nolint:errcheck // error return value intentionally not checked "targetImage": container.Spec.Image, - "cliImage": image, + "cliImage": drivers.GetBuilderCliImage(builderFlags, container.Spec.Image, image), }) container.Spec.Instructions = &weka.Instructions{ Type: weka.InstructionCopyWekaFilesToDriverLoader, diff --git a/internal/drivers/drivers.go b/internal/drivers/drivers.go index e9a2d51f4..5fec24090 100644 --- a/internal/drivers/drivers.go +++ b/internal/drivers/drivers.go @@ -7,23 +7,25 @@ import ( "strings" "github.com/weka/weka-operator/internal/config" + "github.com/weka/weka-operator/internal/pkg/domain" "github.com/weka/weka-operator/internal/services" v1 "k8s.io/api/core/v1" ) var ( ubuntuRe = regexp.MustCompile(`(?i)ubuntu\s+(\d+)\.(\d+)`) - rhelRe = regexp.MustCompile(`(?i)rhel\s*(\d+)`) - rockyRe = regexp.MustCompile(`(?i)rocky\s*(\d+)`) + rhelRe = regexp.MustCompile(`(?i)(?:rhel|red\s+hat\s+enterprise\s+linux)\s*(\d+)`) + rockyRe = regexp.MustCompile(`(?i)rocky(?:\s+linux)?\s*(\d+)`) ) // NormalizeOSImageName converts OS image names into short, DNS-1123 compliant canonical IDs. // Examples: // -// "Ubuntu 22.04.5 LTS" -> "ubuntu-22-04" -// "Ubuntu 24.04.3 LTS" -> "ubuntu-24" -// "RHEL 9.4" -> "rhel09" -// "Rocky Linux 8.10" -> "rocky08" +// "Ubuntu 22.04.5 LTS" -> "ubuntu-22-04" +// "Ubuntu 24.04.3 LTS" -> "ubuntu-24" +// "RHEL 9.4" -> "rhel09" +// "Red Hat Enterprise Linux 9.7 (Plow)" -> "rhel09" +// "Rocky Linux 8.10" -> "rocky08" func NormalizeOSImageName(input string) string { s := strings.TrimSpace(input) @@ -65,13 +67,22 @@ func GetBuilderImageForNode(node *v1.Node) string { func GetLoaderImageForNode(ctx context.Context, node *v1.Node, image string) string { flags, err := services.GetFeatureFlags(ctx, image) - if err == nil && flags != nil { + if err != nil { + flags = nil + } + + return GetBuilderCliImage(flags, image, GetBuilderImageForNode(node)) +} + +// GetBuilderCliImage picks the image that supplies the weka CLI staged for the +// drivers-builder init containers. The extraction step bind-mounts over /opt/weka, +// where the cluster image keeps its CLI, so a CLI is always staged out of band; +// prefer the cluster image's own CLI when it can copy driver files itself. +func GetBuilderCliImage(flags *domain.FeatureFlags, clusterImage, builderImage string) string { + if flags != nil && flags.WekaGetCopyLocalDriverFiles { // innovation cli --kernel-build-id etc. - if flags.WekaGetCopyLocalDriverFiles { - return image - } + return clusterImage } - // else - can use the builder image that has "innovation" cli - return GetBuilderImageForNode(node) + return builderImage } diff --git a/internal/drivers/drivers_test.go b/internal/drivers/drivers_test.go index 715a6f0b7..43eae1736 100644 --- a/internal/drivers/drivers_test.go +++ b/internal/drivers/drivers_test.go @@ -37,6 +37,33 @@ var _ = AfterSuite(func() { } }) +func TestNormalizeOSImageName(t *testing.T) { + tests := []struct { + name string + input string + want string + }{ + {"Ubuntu 22.04.5 LTS", "Ubuntu 22.04.5 LTS", "ubuntu-22-04"}, + {"Ubuntu 24.04.3 LTS", "Ubuntu 24.04.3 LTS", "ubuntu-24"}, + {"Ubuntu 24.04.4 LTS", "Ubuntu 24.04.4 LTS", "ubuntu-24"}, + {"RHEL 9.4", "RHEL 9.4", "rhel09"}, + {"Red Hat Enterprise Linux 9.7 (Plow)", "Red Hat Enterprise Linux 9.7 (Plow)", "rhel09"}, + {"Red Hat Enterprise Linux 8.10", "Red Hat Enterprise Linux 8.10", "rhel08"}, + {"Rocky Linux 8.10", "Rocky Linux 8.10", "rocky08"}, + {"empty string", "", "unknown-os"}, + {"Some Weird Distro 1.2", "Some Weird Distro 1.2", "unknown-os"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := NormalizeOSImageName(tt.input) + if got != tt.want { + t.Errorf("NormalizeOSImageName(%q) = %q, want %q", tt.input, got, tt.want) + } + }) + } +} + var _ = Describe("Driver Image Selection", func() { BeforeEach(func() {