From 1c941f0b4d7628b613ce7ec1d12ca83b487de18f Mon Sep 17 00:00:00 2001 From: Siu Wa Wu Date: Thu, 30 Apr 2026 13:13:41 +1000 Subject: [PATCH 1/3] SREP-3695 add a warning message if mac users run podman without rosetta --- pkg/container/container_podman.go | 21 +++++++++++++++ pkg/container/container_test.go | 43 +++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/pkg/container/container_podman.go b/pkg/container/container_podman.go index f504871e..1616a28a 100644 --- a/pkg/container/container_podman.go +++ b/pkg/container/container_podman.go @@ -1,6 +1,7 @@ package container import ( + "bytes" "encoding/base64" "fmt" "os" @@ -10,6 +11,24 @@ import ( logger "github.com/sirupsen/logrus" ) +// checkRosettaEnabled verifies if Rosetta is enabled in Podman on macOS +// This is a non-blocking check that provides a hint to the user if Rosetta is not configured +func checkRosettaEnabled() { + checkCmd := createCommand(PODMAN, "machine", "ssh", "ls /proc/sys/fs/binfmt_misc/") + var out bytes.Buffer + checkCmd.Stdout = &out + checkCmd.Stderr = nil + + if err := checkCmd.Run(); err != nil { + // Silently skip if we can't check + return + } + + if !strings.Contains(out.String(), "rosetta") { + logger.Warnf("Rosetta does not appear to be enabled in Podman. For better compatibility with x86_64 images on Apple Silicon, please configure Rosetta. See docs/macOS.md for setup instructions.") + } +} + type podmanLinux struct { fileMountDir string } @@ -82,6 +101,8 @@ func podmanRunConsoleContainer(containerName string, port string, consoleArgs [] } func (ce *podmanMac) RunConsoleContainer(containerName string, port string, consoleArgs []string, envVars []EnvVar) error { + // Check if Rosetta is enabled for better compatibility + checkRosettaEnabled() return podmanRunConsoleContainer(containerName, port, consoleArgs, envVars) } diff --git a/pkg/container/container_test.go b/pkg/container/container_test.go index 22f27e17..406643ea 100644 --- a/pkg/container/container_test.go +++ b/pkg/container/container_test.go @@ -100,6 +100,44 @@ var _ = Describe("console container implementation", func() { }) }) + Context("when checking Rosetta on macOS Podman", func() { + It("should execute podman machine ssh command to check binfmt_misc", func() { + capturedCommands = nil + checkRosettaEnabled() + Expect(len(capturedCommands)).To(Equal(1)) + command := capturedCommands[0] + Expect(command[0]).To(Equal(PODMAN)) + Expect(command[1]).To(Equal("machine")) + Expect(command[2]).To(Equal("ssh")) + Expect(strings.Join(command[3:], " ")).To(Equal("ls /proc/sys/fs/binfmt_misc/")) + }) + }) + + Context("when running console container on macOS", func() { + ce := podmanMac{} + It("should check Rosetta before running the container", func() { + mockOcmInterface.EXPECT().GetPullSecret().Return(pullSecret, nil).AnyTimes() + capturedCommands = nil + args := []string{"arg1"} + envvars := []EnvVar{{Key: "testkey", Value: "testval"}} + err := ce.RunConsoleContainer("console", "8888", args, envvars) + Expect(err).To(BeNil()) + // Should have 2 commands: 1 for Rosetta check, 1 for running container + Expect(len(capturedCommands)).To(BeNumerically(">=", 2)) + // First command should be Rosetta check + rosettaCheckCmd := capturedCommands[0] + Expect(rosettaCheckCmd[0]).To(Equal(PODMAN)) + Expect(rosettaCheckCmd[1]).To(Equal("machine")) + Expect(rosettaCheckCmd[2]).To(Equal("ssh")) + // Last command should be the actual container run + runCmd := capturedCommands[len(capturedCommands)-1] + fullCommand := strings.Join(runCmd, " ") + Expect(fullCommand).To(ContainSubstring("arg1")) + Expect(fullCommand).To(ContainSubstring("--env")) + Expect(fullCommand).To(ContainSubstring("testkey=testval")) + }) + }) + Context("when running console container", func() { ce := podmanMac{} It("should pass argments and environment variable if specified", func() { @@ -109,8 +147,9 @@ var _ = Describe("console container implementation", func() { envvars := []EnvVar{{Key: "testkey", Value: "testval"}} err := ce.RunConsoleContainer("console", "8888", args, envvars) Expect(err).To(BeNil()) - Expect(len(capturedCommands)).To(Equal(1)) - fullCommand := strings.Join(capturedCommands[0], " ") + Expect(len(capturedCommands)).To(BeNumerically(">=", 1)) + // Find the run command (should be the last one) + fullCommand := strings.Join(capturedCommands[len(capturedCommands)-1], " ") // arg Expect(fullCommand).To(ContainSubstring("arg1")) // env var From 543da63c6b9712e5dbb55f53cedb5f262d01f244 Mon Sep 17 00:00:00 2001 From: Siu Wa Wu Date: Thu, 30 Apr 2026 16:32:47 +1000 Subject: [PATCH 2/3] only check rosetta on darwin/arm64 --- pkg/container/container_podman.go | 6 ++ pkg/container/container_test.go | 94 +++++++++++++++++++++---------- 2 files changed, 71 insertions(+), 29 deletions(-) diff --git a/pkg/container/container_podman.go b/pkg/container/container_podman.go index 1616a28a..94413563 100644 --- a/pkg/container/container_podman.go +++ b/pkg/container/container_podman.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "runtime" "strings" logger "github.com/sirupsen/logrus" @@ -14,6 +15,11 @@ import ( // checkRosettaEnabled verifies if Rosetta is enabled in Podman on macOS // This is a non-blocking check that provides a hint to the user if Rosetta is not configured func checkRosettaEnabled() { + // Rosetta is only relevant on Apple Silicon (arm64); skip on Intel Macs + if runtime.GOARCH != "arm64" { + return + } + checkCmd := createCommand(PODMAN, "machine", "ssh", "ls /proc/sys/fs/binfmt_misc/") var out bytes.Buffer checkCmd.Stdout = &out diff --git a/pkg/container/container_test.go b/pkg/container/container_test.go index 406643ea..acaf9938 100644 --- a/pkg/container/container_test.go +++ b/pkg/container/container_test.go @@ -3,6 +3,7 @@ package container import ( "os" "os/exec" + "runtime" "strings" "testing" @@ -101,40 +102,75 @@ var _ = Describe("console container implementation", func() { }) Context("when checking Rosetta on macOS Podman", func() { - It("should execute podman machine ssh command to check binfmt_misc", func() { - capturedCommands = nil - checkRosettaEnabled() - Expect(len(capturedCommands)).To(Equal(1)) - command := capturedCommands[0] - Expect(command[0]).To(Equal(PODMAN)) - Expect(command[1]).To(Equal("machine")) - Expect(command[2]).To(Equal("ssh")) - Expect(strings.Join(command[3:], " ")).To(Equal("ls /proc/sys/fs/binfmt_misc/")) + It("should execute podman machine ssh command on arm64", func() { + if os.Getenv("GOARCH") == "arm64" || (os.Getenv("GOARCH") == "" && runtime.GOARCH == "arm64") { + capturedCommands = nil + checkRosettaEnabled() + Expect(len(capturedCommands)).To(Equal(1)) + command := capturedCommands[0] + Expect(command[0]).To(Equal(PODMAN)) + Expect(command[1]).To(Equal("machine")) + Expect(command[2]).To(Equal("ssh")) + Expect(strings.Join(command[3:], " ")).To(Equal("ls /proc/sys/fs/binfmt_misc/")) + } else { + Skip("Rosetta check only runs on arm64 architecture") + } + }) + It("should skip the check on non-arm64 architectures", func() { + if os.Getenv("GOARCH") != "arm64" && (os.Getenv("GOARCH") != "" || runtime.GOARCH != "arm64") { + capturedCommands = nil + checkRosettaEnabled() + Expect(len(capturedCommands)).To(Equal(0)) + } else { + Skip("This test only runs on non-arm64 architectures") + } }) }) Context("when running console container on macOS", func() { ce := podmanMac{} - It("should check Rosetta before running the container", func() { - mockOcmInterface.EXPECT().GetPullSecret().Return(pullSecret, nil).AnyTimes() - capturedCommands = nil - args := []string{"arg1"} - envvars := []EnvVar{{Key: "testkey", Value: "testval"}} - err := ce.RunConsoleContainer("console", "8888", args, envvars) - Expect(err).To(BeNil()) - // Should have 2 commands: 1 for Rosetta check, 1 for running container - Expect(len(capturedCommands)).To(BeNumerically(">=", 2)) - // First command should be Rosetta check - rosettaCheckCmd := capturedCommands[0] - Expect(rosettaCheckCmd[0]).To(Equal(PODMAN)) - Expect(rosettaCheckCmd[1]).To(Equal("machine")) - Expect(rosettaCheckCmd[2]).To(Equal("ssh")) - // Last command should be the actual container run - runCmd := capturedCommands[len(capturedCommands)-1] - fullCommand := strings.Join(runCmd, " ") - Expect(fullCommand).To(ContainSubstring("arg1")) - Expect(fullCommand).To(ContainSubstring("--env")) - Expect(fullCommand).To(ContainSubstring("testkey=testval")) + It("should check Rosetta before running the container on arm64", func() { + if os.Getenv("GOARCH") == "arm64" || (os.Getenv("GOARCH") == "" && runtime.GOARCH == "arm64") { + mockOcmInterface.EXPECT().GetPullSecret().Return(pullSecret, nil).AnyTimes() + capturedCommands = nil + args := []string{"arg1"} + envvars := []EnvVar{{Key: "testkey", Value: "testval"}} + err := ce.RunConsoleContainer("console", "8888", args, envvars) + Expect(err).To(BeNil()) + // Should have 2 commands: 1 for Rosetta check, 1 for running container + Expect(len(capturedCommands)).To(BeNumerically(">=", 2)) + // First command should be Rosetta check + rosettaCheckCmd := capturedCommands[0] + Expect(rosettaCheckCmd[0]).To(Equal(PODMAN)) + Expect(rosettaCheckCmd[1]).To(Equal("machine")) + Expect(rosettaCheckCmd[2]).To(Equal("ssh")) + // Last command should be the actual container run + runCmd := capturedCommands[len(capturedCommands)-1] + fullCommand := strings.Join(runCmd, " ") + Expect(fullCommand).To(ContainSubstring("arg1")) + Expect(fullCommand).To(ContainSubstring("--env")) + Expect(fullCommand).To(ContainSubstring("testkey=testval")) + } else { + Skip("Rosetta check only runs on arm64 architecture") + } + }) + It("should skip Rosetta check on non-arm64 architectures", func() { + if os.Getenv("GOARCH") != "arm64" && (os.Getenv("GOARCH") != "" || runtime.GOARCH != "arm64") { + mockOcmInterface.EXPECT().GetPullSecret().Return(pullSecret, nil).AnyTimes() + capturedCommands = nil + args := []string{"arg1"} + envvars := []EnvVar{{Key: "testkey", Value: "testval"}} + err := ce.RunConsoleContainer("console", "8888", args, envvars) + Expect(err).To(BeNil()) + // Should only have 1 command for running container (no Rosetta check) + Expect(len(capturedCommands)).To(Equal(1)) + fullCommand := strings.Join(capturedCommands[0], " ") + Expect(fullCommand).To(ContainSubstring("arg1")) + Expect(fullCommand).To(ContainSubstring("--env")) + Expect(fullCommand).To(ContainSubstring("testkey=testval")) + } else { + Skip("This test only runs on non-arm64 architectures") + } }) }) From d3262ba2e161d11ba58f1b35ff6bb2ff5a87bf78 Mon Sep 17 00:00:00 2001 From: Siu Wa Wu Date: Fri, 1 May 2026 14:42:06 +1000 Subject: [PATCH 3/3] update url --- pkg/container/container_podman.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/container/container_podman.go b/pkg/container/container_podman.go index 94413563..0e497783 100644 --- a/pkg/container/container_podman.go +++ b/pkg/container/container_podman.go @@ -31,7 +31,7 @@ func checkRosettaEnabled() { } if !strings.Contains(out.String(), "rosetta") { - logger.Warnf("Rosetta does not appear to be enabled in Podman. For better compatibility with x86_64 images on Apple Silicon, please configure Rosetta. See docs/macOS.md for setup instructions.") + logger.Warnf("Rosetta does not appear to be enabled in Podman. For better compatibility with x86_64 images on Apple Silicon, please configure Rosetta. See https://github.com/openshift/backplane-cli/blob/main/docs/macOS.md for setup instructions.") } }