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/6] 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/6] 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/6] 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.") } } From 524e4c804fcffc5ea5dc1f51784b3a8b5f900235 Mon Sep 17 00:00:00 2001 From: Siu Wa Wu Date: Wed, 6 May 2026 11:16:58 +1000 Subject: [PATCH 4/6] only run rosetta check on macos --- pkg/container/container_podman.go | 4 +-- pkg/container/container_test.go | 49 ++++++++++++++++++++++++------- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/pkg/container/container_podman.go b/pkg/container/container_podman.go index 0e497783..505d030d 100644 --- a/pkg/container/container_podman.go +++ b/pkg/container/container_podman.go @@ -15,8 +15,8 @@ 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" { + // Rosetta is only relevant on Apple Silicon (arm64) macOS; skip on other platforms + if runtime.GOOS != "darwin" || runtime.GOARCH != "arm64" { return } diff --git a/pkg/container/container_test.go b/pkg/container/container_test.go index acaf9938..83973721 100644 --- a/pkg/container/container_test.go +++ b/pkg/container/container_test.go @@ -102,8 +102,8 @@ var _ = Describe("console container implementation", func() { }) Context("when checking Rosetta on macOS Podman", func() { - It("should execute podman machine ssh command on arm64", func() { - if os.Getenv("GOARCH") == "arm64" || (os.Getenv("GOARCH") == "" && runtime.GOARCH == "arm64") { + It("should execute podman machine ssh command on darwin/arm64", func() { + if runtime.GOOS == "darwin" && (os.Getenv("GOARCH") == "arm64" || (os.Getenv("GOARCH") == "" && runtime.GOARCH == "arm64")) { capturedCommands = nil checkRosettaEnabled() Expect(len(capturedCommands)).To(Equal(1)) @@ -113,24 +113,33 @@ var _ = Describe("console container implementation", func() { 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") + Skip("Rosetta check only runs on darwin/arm64") + } + }) + It("should skip the check on non-darwin platforms", func() { + if runtime.GOOS != "darwin" { + capturedCommands = nil + checkRosettaEnabled() + Expect(len(capturedCommands)).To(Equal(0)) + } else { + Skip("This test only runs on non-darwin platforms") } }) It("should skip the check on non-arm64 architectures", func() { - if os.Getenv("GOARCH") != "arm64" && (os.Getenv("GOARCH") != "" || runtime.GOARCH != "arm64") { + if runtime.GOOS == "darwin" && 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") + Skip("This test only runs on darwin with non-arm64 architectures") } }) }) Context("when running console container on macOS", func() { ce := podmanMac{} - It("should check Rosetta before running the container on arm64", func() { - if os.Getenv("GOARCH") == "arm64" || (os.Getenv("GOARCH") == "" && runtime.GOARCH == "arm64") { + It("should check Rosetta before running the container on darwin/arm64", func() { + if runtime.GOOS == "darwin" && (os.Getenv("GOARCH") == "arm64" || (os.Getenv("GOARCH") == "" && runtime.GOARCH == "arm64")) { mockOcmInterface.EXPECT().GetPullSecret().Return(pullSecret, nil).AnyTimes() capturedCommands = nil args := []string{"arg1"} @@ -151,11 +160,29 @@ var _ = Describe("console container implementation", func() { Expect(fullCommand).To(ContainSubstring("--env")) Expect(fullCommand).To(ContainSubstring("testkey=testval")) } else { - Skip("Rosetta check only runs on arm64 architecture") + Skip("Rosetta check only runs on darwin/arm64") + } + }) + It("should skip Rosetta check on non-darwin platforms", func() { + if runtime.GOOS != "darwin" { + 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-darwin platforms") } }) - It("should skip Rosetta check on non-arm64 architectures", func() { - if os.Getenv("GOARCH") != "arm64" && (os.Getenv("GOARCH") != "" || runtime.GOARCH != "arm64") { + It("should skip Rosetta check on darwin with non-arm64 architectures", func() { + if runtime.GOOS == "darwin" && os.Getenv("GOARCH") != "arm64" && (os.Getenv("GOARCH") != "" || runtime.GOARCH != "arm64") { mockOcmInterface.EXPECT().GetPullSecret().Return(pullSecret, nil).AnyTimes() capturedCommands = nil args := []string{"arg1"} @@ -169,7 +196,7 @@ var _ = Describe("console container implementation", func() { Expect(fullCommand).To(ContainSubstring("--env")) Expect(fullCommand).To(ContainSubstring("testkey=testval")) } else { - Skip("This test only runs on non-arm64 architectures") + Skip("This test only runs on darwin with non-arm64 architectures") } }) }) From 582a7bdc1e274cbc849e996c0f7446915af42ea2 Mon Sep 17 00:00:00 2001 From: Siu Wa Wu Date: Wed, 6 May 2026 11:31:38 +1000 Subject: [PATCH 5/6] simplify test condition --- pkg/container/container_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/container/container_test.go b/pkg/container/container_test.go index 83973721..0bd85f63 100644 --- a/pkg/container/container_test.go +++ b/pkg/container/container_test.go @@ -103,7 +103,7 @@ var _ = Describe("console container implementation", func() { Context("when checking Rosetta on macOS Podman", func() { It("should execute podman machine ssh command on darwin/arm64", func() { - if runtime.GOOS == "darwin" && (os.Getenv("GOARCH") == "arm64" || (os.Getenv("GOARCH") == "" && runtime.GOARCH == "arm64")) { + if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { capturedCommands = nil checkRosettaEnabled() Expect(len(capturedCommands)).To(Equal(1)) @@ -126,7 +126,7 @@ var _ = Describe("console container implementation", func() { } }) It("should skip the check on non-arm64 architectures", func() { - if runtime.GOOS == "darwin" && os.Getenv("GOARCH") != "arm64" && (os.Getenv("GOARCH") != "" || runtime.GOARCH != "arm64") { + if runtime.GOOS == "darwin" && runtime.GOARCH != "arm64" { capturedCommands = nil checkRosettaEnabled() Expect(len(capturedCommands)).To(Equal(0)) @@ -139,7 +139,7 @@ var _ = Describe("console container implementation", func() { Context("when running console container on macOS", func() { ce := podmanMac{} It("should check Rosetta before running the container on darwin/arm64", func() { - if runtime.GOOS == "darwin" && (os.Getenv("GOARCH") == "arm64" || (os.Getenv("GOARCH") == "" && runtime.GOARCH == "arm64")) { + if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { mockOcmInterface.EXPECT().GetPullSecret().Return(pullSecret, nil).AnyTimes() capturedCommands = nil args := []string{"arg1"} @@ -182,7 +182,7 @@ var _ = Describe("console container implementation", func() { } }) It("should skip Rosetta check on darwin with non-arm64 architectures", func() { - if runtime.GOOS == "darwin" && os.Getenv("GOARCH") != "arm64" && (os.Getenv("GOARCH") != "" || runtime.GOARCH != "arm64") { + if runtime.GOOS == "darwin" && runtime.GOARCH != "arm64" { mockOcmInterface.EXPECT().GetPullSecret().Return(pullSecret, nil).AnyTimes() capturedCommands = nil args := []string{"arg1"} From 399d7ddb8938e0e75e6a88a9270b886d964ff6ad Mon Sep 17 00:00:00 2001 From: Siu Wa Wu Date: Wed, 6 May 2026 11:54:14 +1000 Subject: [PATCH 6/6] improve rosetta check test --- pkg/container/container_test.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/container/container_test.go b/pkg/container/container_test.go index 0bd85f63..895f07cb 100644 --- a/pkg/container/container_test.go +++ b/pkg/container/container_test.go @@ -146,8 +146,15 @@ var _ = Describe("console container implementation", func() { 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)) + // Count Rosetta check commands + rosettaCheckCount := 0 + for _, cmd := range capturedCommands { + if len(cmd) >= 3 && cmd[0] == PODMAN && cmd[1] == "machine" && cmd[2] == "ssh" { + rosettaCheckCount++ + } + } + // Should have exactly 1 Rosetta check + Expect(rosettaCheckCount).To(Equal(1)) // First command should be Rosetta check rosettaCheckCmd := capturedCommands[0] Expect(rosettaCheckCmd[0]).To(Equal(PODMAN))