diff --git a/cmd/podman/machine/inspect.go b/cmd/podman/machine/inspect.go index 92ed761ce1..c94781a172 100644 --- a/cmd/podman/machine/inspect.go +++ b/cmd/podman/machine/inspect.go @@ -74,6 +74,11 @@ func inspect(cmd *cobra.Command, args []string) error { return err } + rosetta, err := provider.GetRosetta(mc) + if err != nil { + return err + } + ii := machine.InspectInfo{ ConfigDir: *dirs.ConfigDir, ConnectionInfo: machine.ConnectionConfig{ @@ -88,6 +93,7 @@ func inspect(cmd *cobra.Command, args []string) error { State: state, UserModeNetworking: provider.UserModeNetworkEnabled(mc), Rootful: mc.HostUser.Rootful, + Rosetta: rosetta, } vms = append(vms, ii) diff --git a/docs/source/markdown/podman-machine-inspect.1.md b/docs/source/markdown/podman-machine-inspect.1.md index 0823889804..2a5bb34639 100644 --- a/docs/source/markdown/podman-machine-inspect.1.md +++ b/docs/source/markdown/podman-machine-inspect.1.md @@ -32,6 +32,7 @@ Print results with a Go template. | .Name | Name of the machine | | .Resources ... | Resources used by the machine | | .Rootful | Whether the machine prefers rootful or rootless container execution | +| .Rosetta | Whether this machine uses Rosetta | | .SSHConfig ... | SSH configuration info for communicating with machine | | .State | Machine state | | .UserModeNetworking | Whether this machine uses user-mode networking | diff --git a/pkg/machine/apple/vfkit.go b/pkg/machine/apple/vfkit.go index 45544f037f..05a44e3515 100644 --- a/pkg/machine/apple/vfkit.go +++ b/pkg/machine/apple/vfkit.go @@ -42,6 +42,18 @@ func GetDefaultDevices(mc *vmconfigs.MachineConfig) ([]vfConfig.VirtioDevice, *d return nil, nil, err } devices = append(devices, disk, rng, serial, readyDevice) + + rosettaCfg := mc.AppleHypervisor.Vfkit.Rosetta + if rosettaCfg { + rosetta := &vfConfig.RosettaShare{ + DirectorySharingConfig: vfConfig.DirectorySharingConfig{ + MountTag: define.MountTag, + }, + InstallRosetta: true, + } + devices = append(devices, rosetta) + } + return devices, readySocket, nil } diff --git a/pkg/machine/apple/vfkit/helper.go b/pkg/machine/apple/vfkit/helper.go index 647e936dbd..07b1566f70 100644 --- a/pkg/machine/apple/vfkit/helper.go +++ b/pkg/machine/apple/vfkit/helper.go @@ -124,4 +124,5 @@ type Helper struct { Endpoint string BinaryPath *define.VMFile VirtualMachine *config.VirtualMachine + Rosetta bool } diff --git a/pkg/machine/applehv/stubber.go b/pkg/machine/applehv/stubber.go index 301a1928dc..b46767e798 100644 --- a/pkg/machine/applehv/stubber.go +++ b/pkg/machine/applehv/stubber.go @@ -4,8 +4,10 @@ package applehv import ( "fmt" + "runtime" "strconv" + "github.com/containers/common/pkg/config" gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types" "github.com/containers/podman/v5/pkg/machine" "github.com/containers/podman/v5/pkg/machine/apple" @@ -65,6 +67,16 @@ func (a AppleHVStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.Machine } ignBuilder.WithUnit(virtIOIgnitionMounts...) + cfg, err := config.Default() + if err != nil { + return err + } + rosetta := cfg.Machine.Rosetta + if runtime.GOARCH != "arm64" { + rosetta = false + } + mc.AppleHypervisor.Vfkit.Rosetta = rosetta + return apple.ResizeDisk(mc, mc.Resources.DiskSize) } @@ -104,6 +116,18 @@ func (a AppleHVStubber) StartVM(mc *vmconfigs.MachineConfig) (func() error, func return nil, nil, fmt.Errorf("unable to determine boot loader for this machine") } + cfg, err := config.Default() + if err != nil { + return nil, nil, err + } + rosetta := cfg.Machine.Rosetta + rosettaNew := rosetta + if runtime.GOARCH == "arm64" { + rosettaMC := mc.AppleHypervisor.Vfkit.Rosetta + if rosettaMC != rosettaNew { + mc.AppleHypervisor.Vfkit.Rosetta = rosettaNew + } + } return apple.StartGenericAppleVM(mc, vfkitCommand, bl, mc.AppleHypervisor.Vfkit.Endpoint) } @@ -131,3 +155,8 @@ func (a AppleHVStubber) PostStartNetworking(mc *vmconfigs.MachineConfig, noInfo func (a AppleHVStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error { return diskpull.GetDisk(userInputPath, dirs, mc.ImagePath, a.VMType(), mc.Name) } + +func (a *AppleHVStubber) GetRosetta(mc *vmconfigs.MachineConfig) (bool, error) { + rosetta := mc.AppleHypervisor.Vfkit.Rosetta + return rosetta, nil +} diff --git a/pkg/machine/config.go b/pkg/machine/config.go index e317273eb9..4c5c5cdc0b 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -71,8 +71,9 @@ type SSHOptions struct { } type StartOptions struct { - NoInfo bool - Quiet bool + NoInfo bool + Quiet bool + Rosetta bool } type StopOptions struct{} @@ -117,6 +118,7 @@ type InspectInfo struct { State define.Status UserModeNetworking bool Rootful bool + Rosetta bool } // ImageConfig describes the bootable image for the VM diff --git a/pkg/machine/define/config.go b/pkg/machine/define/config.go index 245080122c..90135f76fd 100644 --- a/pkg/machine/define/config.go +++ b/pkg/machine/define/config.go @@ -5,6 +5,10 @@ import "os" const UserCertsTargetPath = "/etc/containers/certs.d" const DefaultIdentityName = "machine" +// MountTag is an identifier to mount a VirtioFS file system tag on a mount point in the VM. +// Ref: https://developer.apple.com/documentation/virtualization/running_intel_binaries_in_linux_vms_with_rosetta +const MountTag = "rosetta" + var ( DefaultFilePerm os.FileMode = 0644 ) diff --git a/pkg/machine/e2e/init_test.go b/pkg/machine/e2e/init_test.go index e3844cb8c2..4b0d9df3ce 100644 --- a/pkg/machine/e2e/init_test.go +++ b/pkg/machine/e2e/init_test.go @@ -370,6 +370,100 @@ var _ = Describe("podman machine init", func() { Expect(err).ToNot(HaveOccurred()) Expect(inspectShouldPass).To(Exit(0)) }) + + It("machine init with rosetta=true", func() { + skipIfVmtype(define.QemuVirt, "Test is only for AppleHv") + skipIfVmtype(define.WSLVirt, "Test is only for AppleHv") + skipIfVmtype(define.HyperVVirt, "Test is only for AppleHv") + skipIfVmtype(define.LibKrun, "Test is only for AppleHv") + if runtime.GOARCH != "arm64" { + Skip("Test is only for AppleHv with arm64 architecture") + } + + i := initMachine{} + name := randomString() + session, err := mb.setName(name).setCmd(i.withImage(mb.imagePath)).run() + Expect(err).ToNot(HaveOccurred()) + Expect(session).To(Exit(0)) + + s := startMachine{} + ssession, err := mb.setCmd(s).setTimeout(time.Minute * 10).run() + Expect(err).ToNot(HaveOccurred()) + Expect(ssession).Should(Exit(0)) + + inspect := new(inspectMachine) + inspect = inspect.withFormat("{{.Rosetta}}") + inspectSession, err := mb.setName(name).setCmd(inspect).run() + Expect(err).ToNot(HaveOccurred()) + Expect(inspectSession).To(Exit(0)) + Expect(inspectSession.outputToString()).To(Equal("true")) + + mnt := sshMachine{} + mntSession, err := mb.setName(name).setCmd(mnt.withSSHCommand([]string{"ls -d /mnt/rosetta"})).run() + Expect(err).ToNot(HaveOccurred()) + Expect(mntSession).To(Exit(0)) + Expect(mntSession.outputToString()).To(ContainSubstring("/mnt/rosetta")) + + proc := sshMachine{} + procSession, err := mb.setName(name).setCmd(proc.withSSHCommand([]string{"ls -d /proc/sys/fs/binfmt_misc/rosetta"})).run() + Expect(err).ToNot(HaveOccurred()) + Expect(procSession).To(Exit(0)) + Expect(procSession.outputToString()).To(ContainSubstring("/proc/sys/fs/binfmt_misc/rosetta")) + + proc2 := sshMachine{} + proc2Session, err := mb.setName(name).setCmd(proc2.withSSHCommand([]string{"ls -d /proc/sys/fs/binfmt_misc/qemu-x86_64"})).run() + Expect(err).ToNot(HaveOccurred()) + Expect(proc2Session.ExitCode()).To(Equal(2)) + }) + + It("machine init with rosetta=false", func() { + skipIfVmtype(define.QemuVirt, "Test is only for AppleHv") + skipIfVmtype(define.WSLVirt, "Test is only for AppleHv") + skipIfVmtype(define.HyperVVirt, "Test is only for AppleHv") + skipIfVmtype(define.LibKrun, "Test is only for AppleHv") + if runtime.GOARCH != "arm64" { + Skip("Test is only for AppleHv with arm64 architecture") + } + configDir := filepath.Join(testDir, ".config", "containers") + err := os.MkdirAll(configDir, 0755) + Expect(err).ToNot(HaveOccurred()) + + err = os.WriteFile(filepath.Join(configDir, "containers.conf"), rosettaConfig, 0644) + Expect(err).ToNot(HaveOccurred()) + + i := initMachine{} + name := randomString() + session, err := mb.setName(name).setCmd(i.withImage(mb.imagePath)).run() + Expect(err).ToNot(HaveOccurred()) + Expect(session).To(Exit(0)) + + s := startMachine{} + ssession, err := mb.setCmd(s).setTimeout(time.Minute * 10).run() + Expect(err).ToNot(HaveOccurred()) + Expect(ssession).Should(Exit(0)) + + inspect := new(inspectMachine) + inspect = inspect.withFormat("{{.Rosetta}}") + inspectSession, err := mb.setName(name).setCmd(inspect).run() + Expect(err).ToNot(HaveOccurred()) + Expect(inspectSession).To(Exit(0)) + Expect(inspectSession.outputToString()).To(Equal("false")) + + mnt := sshMachine{} + mntSession, err := mb.setName(name).setCmd(mnt.withSSHCommand([]string{"ls -d /mnt/rosetta"})).run() + Expect(err).ToNot(HaveOccurred()) + Expect(mntSession.ExitCode()).To(Equal(2)) + + proc := sshMachine{} + procSession, err := mb.setName(name).setCmd(proc.withSSHCommand([]string{"ls -d /proc/sys/fs/binfmt_misc/rosetta"})).run() + Expect(err).ToNot(HaveOccurred()) + Expect(procSession.ExitCode()).To(Equal(2)) + + proc2 := sshMachine{} + proc2Session, err := mb.setName(name).setCmd(proc2.withSSHCommand([]string{"ls -d /proc/sys/fs/binfmt_misc/qemu-x86_64"})).run() + Expect(err).ToNot(HaveOccurred()) + Expect(proc2Session.outputToString()).To(ContainSubstring("/proc/sys/fs/binfmt_misc/qemu-x86_64")) + }) }) var p4Config = []byte(`{ @@ -455,3 +549,8 @@ var p4Config = []byte(`{ "LastUp": "0001-01-01T00:00:00Z" } `) + +var rosettaConfig = []byte(` +[machine] +rosetta=false +`) diff --git a/pkg/machine/e2e/machine_test.go b/pkg/machine/e2e/machine_test.go index d83d0d50e2..2fa7e1d5a5 100644 --- a/pkg/machine/e2e/machine_test.go +++ b/pkg/machine/e2e/machine_test.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "os/exec" "path/filepath" "runtime" "strings" @@ -69,7 +70,13 @@ var _ = BeforeSuite(func() { if pullError != nil { Fail(fmt.Sprintf("failed to pull wsl disk: %q", pullError)) } - + if testProvider.VMType() == define.AppleHvVirt { + cmd := exec.Command("softwareupdate", "--install-rosetta", "--agree-to-license") + err := cmd.Run() + if err != nil { + Fail(fmt.Sprintf("Command failed with error: %q", err)) + } + } }) var _ = SynchronizedAfterSuite(func() {}, func() {}) diff --git a/pkg/machine/hyperv/stubber.go b/pkg/machine/hyperv/stubber.go index b9caef1cde..11f9e8620a 100644 --- a/pkg/machine/hyperv/stubber.go +++ b/pkg/machine/hyperv/stubber.go @@ -557,3 +557,7 @@ func createNetworkUnit(netPort uint64) (string, error) { netUnit.Add("Install", "WantedBy", "multi-user.target") return netUnit.ToString() } + +func (h HyperVStubber) GetRosetta(mc *vmconfigs.MachineConfig) (bool, error) { + return false, nil +} diff --git a/pkg/machine/ignition/ignition.go b/pkg/machine/ignition/ignition.go index 579837a337..58e7b622f1 100644 --- a/pkg/machine/ignition/ignition.go +++ b/pkg/machine/ignition/ignition.go @@ -10,6 +10,7 @@ import ( "os" "path" "path/filepath" + "runtime" "github.com/containers/podman/v5/pkg/machine/define" "github.com/containers/podman/v5/pkg/systemd/parser" @@ -65,6 +66,7 @@ type DynamicIgnition struct { Cfg Config Rootful bool NetRecover bool + Rosetta bool } func (ign *DynamicIgnition) Write() error { @@ -239,6 +241,19 @@ func (ign *DynamicIgnition) GenerateIgnitionConfig() error { ignSystemd.Units = append(ignSystemd.Units, qemuUnit) } + // Only AppleHv with Apple Silicon can use Rosetta + if ign.VMType == define.AppleHvVirt && runtime.GOARCH == "arm64" { + rosettaUnit := Systemd{ + Units: []Unit{ + { + Enabled: BoolToPtr(true), + Name: "rosetta-activation.service", + }, + }, + } + ignSystemd.Units = append(ignSystemd.Units, rosettaUnit.Units...) + } + // Only after all checks are done // it's ready create the ingConfig ign.Cfg = Config{ diff --git a/pkg/machine/libkrun/stubber.go b/pkg/machine/libkrun/stubber.go index 4dd982ccf6..1749b113c6 100644 --- a/pkg/machine/libkrun/stubber.go +++ b/pkg/machine/libkrun/stubber.go @@ -139,3 +139,7 @@ func (l LibKrunStubber) RequireExclusiveActive() bool { func (l LibKrunStubber) UpdateSSHPort(mc *vmconfigs.MachineConfig, port int) error { return nil } + +func (l LibKrunStubber) GetRosetta(mc *vmconfigs.MachineConfig) (bool, error) { + return false, nil +} diff --git a/pkg/machine/qemu/stubber.go b/pkg/machine/qemu/stubber.go index dbd762a0d5..26da286921 100644 --- a/pkg/machine/qemu/stubber.go +++ b/pkg/machine/qemu/stubber.go @@ -360,3 +360,7 @@ func (q *QEMUStubber) UpdateSSHPort(mc *vmconfigs.MachineConfig, port int) error func (q *QEMUStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error { return diskpull.GetDisk(userInputPath, dirs, mc.ImagePath, q.VMType(), mc.Name) } + +func (q *QEMUStubber) GetRosetta(mc *vmconfigs.MachineConfig) (bool, error) { + return false, nil +} diff --git a/pkg/machine/vmconfigs/config.go b/pkg/machine/vmconfigs/config.go index ca7f4e5b1b..aac6726881 100644 --- a/pkg/machine/vmconfigs/config.go +++ b/pkg/machine/vmconfigs/config.go @@ -51,6 +51,8 @@ type MachineConfig struct { // Starting is defined as "on" but not fully booted Starting bool + + Rosetta bool } type machineImage interface { //nolint:unused @@ -99,6 +101,7 @@ type VMProvider interface { //nolint:interfacebloat UseProviderNetworkSetup() bool RequireExclusiveActive() bool UpdateSSHPort(mc *MachineConfig, port int) error + GetRosetta(mc *MachineConfig) (bool, error) } // HostUser describes the host user diff --git a/pkg/machine/wsl/stubber.go b/pkg/machine/wsl/stubber.go index 5197893c9e..433f05eef1 100644 --- a/pkg/machine/wsl/stubber.go +++ b/pkg/machine/wsl/stubber.go @@ -342,3 +342,7 @@ func (w WSLStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc * // pull if needed and decompress to image location return myDisk.Get() } + +func (w WSLStubber) GetRosetta(mc *vmconfigs.MachineConfig) (bool, error) { + return false, nil +}