From 9abe8c78539dc83e9fcc0cf88f7703f83d4f7160 Mon Sep 17 00:00:00 2001 From: Ashley Cui Date: Thu, 9 Feb 2023 16:12:19 -0500 Subject: [PATCH] Introduce podman machine os apply Podman machine os apply takes a takes a OCI image with container native ostree functionality and rebases the machine os on that image. Currently, this requires the guest os inside the vm to use rpm-ostree. When specifying an image, any container transport may be specified. If a container transport is not specified, OS apply will attempt to search the local containers-storage for the image, and if it is not found, it will then attempt to use the Docker transport to pull from a remote registry. The architecture of OS apply is as follows: podman machine os apply ssh's into the machine and calls podman machine os apply. on the secondary call to podman machine os apply, apply recognizes that it is inside the machine and does image operations, and finally calls rpm-ostree rebase. Tests are written but commented out, due to the chicken-and-egg problem. Signed-off-by: Ashley Cui --- Makefile | 9 -- cmd/podman/machine/os.go | 7 +- cmd/podman/machine/os/apply.go | 42 ++++-- cmd/podman/machine/os/manager.go | 91 +++++++++++ cmd/podman/machine/os/os_unsupported.go | 7 + cmd/podman/main.go | 1 + cmd/podman/main_experimental.go | 8 - .../markdown/podman-machine-inspect.1.md | 2 +- .../markdown/podman-machine-os-apply.1.md | 41 +++++ docs/source/markdown/podman-machine-os.1.md | 22 +++ docs/source/markdown/podman-machine.1.md | 23 +-- pkg/machine/e2e/config_os_apply_test.go | 27 ++++ pkg/machine/e2e/os_test.go | 59 ++++++++ pkg/machine/os/config.go | 15 ++ pkg/machine/os/machine_os.go | 40 +++++ pkg/machine/os/ostree.go | 142 ++++++++++++++++++ 16 files changed, 493 insertions(+), 43 deletions(-) create mode 100644 cmd/podman/machine/os/manager.go create mode 100644 cmd/podman/machine/os/os_unsupported.go delete mode 100644 cmd/podman/main_experimental.go create mode 100644 docs/source/markdown/podman-machine-os-apply.1.md create mode 100644 docs/source/markdown/podman-machine-os.1.md create mode 100644 pkg/machine/e2e/config_os_apply_test.go create mode 100644 pkg/machine/e2e/os_test.go create mode 100644 pkg/machine/os/config.go create mode 100644 pkg/machine/os/machine_os.go create mode 100644 pkg/machine/os/ostree.go diff --git a/Makefile b/Makefile index 89a7a2c00d..f2ae6d752b 100644 --- a/Makefile +++ b/Makefile @@ -396,15 +396,6 @@ bin/rootlessport: $(SOURCES) go.mod go.sum .PHONY: rootlessport rootlessport: bin/rootlessport -.PHONY: podman-remote-experimental -podman-remote-experimental: $(SRCBINDIR)/experimental/podman$(BINSFX) -$(SRCBINDIR)/experimental/podman$(BINSFX): $(SOURCES) go.mod go.sum | $(SRCBINDIR) - $(GOCMD) build \ - $(BUILDFLAGS) \ - $(GO_LDFLAGS) '$(LDFLAGS_PODMAN)' \ - -tags "${REMOTETAGS} experimental" \ - -o $@ ./cmd/podman - ### ### Secondary binary-build targets ### diff --git a/cmd/podman/machine/os.go b/cmd/podman/machine/os.go index 78d1c92072..54a9f9cbb6 100644 --- a/cmd/podman/machine/os.go +++ b/cmd/podman/machine/os.go @@ -1,6 +1,5 @@ -//go:build (amd64 || arm64) && experimental +//go:build amd64 || arm64 // +build amd64 arm64 -// +build experimental package machine @@ -13,8 +12,8 @@ import ( var ( OSCmd = &cobra.Command{ Use: "os", - Short: "Manage a virtual machine's os", - Long: "Manage a virtual machine's operating system", + Short: "Manage a Podman virtual machine's OS", + Long: "Manage a Podman virtual machine's operating system", PersistentPreRunE: validate.NoOp, RunE: validate.SubCommandExists, } diff --git a/cmd/podman/machine/os/apply.go b/cmd/podman/machine/os/apply.go index 35d65b0efc..18c9aa509f 100644 --- a/cmd/podman/machine/os/apply.go +++ b/cmd/podman/machine/os/apply.go @@ -1,38 +1,60 @@ -//go:build (amd64 || arm64) && experimental +//go:build amd64 || arm64 // +build amd64 arm64 -// +build experimental -package machineos +package os import ( - "fmt" - + "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/machine" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/validate" + "github.com/containers/podman/v4/pkg/machine/os" "github.com/spf13/cobra" ) var ( applyCmd = &cobra.Command{ - Use: "apply", - Short: "Apply OCI image to existing VM", - Long: "Apply custom layers from a containerized Fedora CoreOS image on top of an existing VM", + Use: "apply [options] IMAGE [NAME]", + Short: "Apply an OCI image to a Podman Machine's OS", + Long: "Apply custom layers from a containerized Fedora CoreOS OCI image on top of an existing VM", PersistentPreRunE: validate.NoOp, + Args: cobra.RangeArgs(1, 2), RunE: apply, + ValidArgsFunction: common.AutocompleteImages, Example: `podman machine os apply myimage`, } ) +var restart bool + func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Command: applyCmd, Parent: machine.OSCmd, }) + flags := applyCmd.Flags() + restartFlagName := "restart" + flags.BoolVar(&restart, restartFlagName, false, "Restart VM to apply changes") } func apply(cmd *cobra.Command, args []string) error { - fmt.Println("Applying..") - return nil + vmName := "" + if len(args) == 2 { + vmName = args[1] + } + managerOpts := ManagerOpts{ + VMName: vmName, + CLIArgs: args, + Restart: restart, + } + osManager, err := NewOSManager(managerOpts) + if err != nil { + return err + } + + applyOpts := os.ApplyOptions{ + Image: args[0], + } + return osManager.Apply(args[0], applyOpts) } diff --git a/cmd/podman/machine/os/manager.go b/cmd/podman/machine/os/manager.go new file mode 100644 index 0000000000..b6f9bd4cad --- /dev/null +++ b/cmd/podman/machine/os/manager.go @@ -0,0 +1,91 @@ +//go:build amd64 || arm64 +// +build amd64 arm64 + +package os + +import ( + "bufio" + "errors" + "os" + "strings" + + machineconfig "github.com/containers/common/pkg/machine" + "github.com/containers/podman/v4/cmd/podman/machine" + pkgMachine "github.com/containers/podman/v4/pkg/machine" + pkgOS "github.com/containers/podman/v4/pkg/machine/os" +) + +type ManagerOpts struct { + VMName string + CLIArgs []string + Restart bool +} + +// NewOSManager creates a new OSManager depending on the mode of the call +func NewOSManager(opts ManagerOpts) (pkgOS.Manager, error) { + // If a VM name is specified, then we know that we are not inside a + // Podman VM, but rather outside of it. + if machineconfig.IsPodmanMachine() && opts.VMName == "" { + return guestOSManager() + } + return machineOSManager(opts) +} + +// guestOSManager returns an OSmanager for inside-VM operations +func guestOSManager() (pkgOS.Manager, error) { + dist := GetDistribution() + switch { + case dist.Name == "fedora" && dist.Variant == "coreos": + return &pkgOS.OSTree{}, nil + default: + return nil, errors.New("unsupported OS") + } +} + +// machineOSManager returns an os manager that manages outside the VM. +func machineOSManager(opts ManagerOpts) (pkgOS.Manager, error) { + vmName := opts.VMName + if opts.VMName == "" { + vmName = pkgMachine.DefaultMachineName + } + provider := machine.GetSystemDefaultProvider() + vm, err := provider.LoadVMByName(vmName) + if err != nil { + return nil, err + } + return &pkgOS.MachineOS{ + VM: vm, + Args: opts.CLIArgs, + VMName: vmName, + Restart: opts.Restart, + }, nil +} + +type Distribution struct { + Name string + Variant string +} + +// GetDistribution checks the OS distribution +func GetDistribution() Distribution { + dist := Distribution{ + Name: "unknown", + Variant: "unknown", + } + f, err := os.Open("/etc/os-release") + if err != nil { + return dist + } + defer f.Close() + + l := bufio.NewScanner(f) + for l.Scan() { + if strings.HasPrefix(l.Text(), "ID=") { + dist.Name = strings.TrimPrefix(l.Text(), "ID=") + } + if strings.HasPrefix(l.Text(), "VARIANT_ID=") { + dist.Variant = strings.Trim(strings.TrimPrefix(l.Text(), "VARIANT_ID="), "\"") + } + } + return dist +} diff --git a/cmd/podman/machine/os/os_unsupported.go b/cmd/podman/machine/os/os_unsupported.go new file mode 100644 index 0000000000..47ca2fce6b --- /dev/null +++ b/cmd/podman/machine/os/os_unsupported.go @@ -0,0 +1,7 @@ +//go:build !amd64 && !arm64 +// +build !amd64,!arm64 + +package os + +// init do not register _podman machine os_ command on unsupported platforms +func init() {} diff --git a/cmd/podman/main.go b/cmd/podman/main.go index dc21807b4a..a820967f93 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -11,6 +11,7 @@ import ( _ "github.com/containers/podman/v4/cmd/podman/images" _ "github.com/containers/podman/v4/cmd/podman/kube" _ "github.com/containers/podman/v4/cmd/podman/machine" + _ "github.com/containers/podman/v4/cmd/podman/machine/os" _ "github.com/containers/podman/v4/cmd/podman/manifest" _ "github.com/containers/podman/v4/cmd/podman/networks" _ "github.com/containers/podman/v4/cmd/podman/pods" diff --git a/cmd/podman/main_experimental.go b/cmd/podman/main_experimental.go deleted file mode 100644 index 08ad953567..0000000000 --- a/cmd/podman/main_experimental.go +++ /dev/null @@ -1,8 +0,0 @@ -//go:build experimental -// +build experimental - -package main - -import ( - _ "github.com/containers/podman/v4/cmd/podman/machine/os" -) diff --git a/docs/source/markdown/podman-machine-inspect.1.md b/docs/source/markdown/podman-machine-inspect.1.md index 79611f65d7..ec810e0e6c 100644 --- a/docs/source/markdown/podman-machine-inspect.1.md +++ b/docs/source/markdown/podman-machine-inspect.1.md @@ -4,7 +4,7 @@ podman\-machine\-inspect - Inspect one or more virtual machines ## SYNOPSIS -**podman machine inspect** [*options] *name* ... +**podman machine inspect** [*options*] *name* ... ## DESCRIPTION diff --git a/docs/source/markdown/podman-machine-os-apply.1.md b/docs/source/markdown/podman-machine-os-apply.1.md new file mode 100644 index 0000000000..29b2182645 --- /dev/null +++ b/docs/source/markdown/podman-machine-os-apply.1.md @@ -0,0 +1,41 @@ +% podman-machine-os-apply 1 + +## NAME +podman\-machine\-os\-apply - Apply an OCI image to a Podman Machine's OS + +## SYNOPSIS +**podman machine os apply** [*options*] *image* [vm] + +## DESCRIPTION + +Apply machine OS changes from an OCI image. + +VM's that use OS's that use rpm-ostreee have the capability to rebase itself from the content of an OCI image. +`podman machine image apply` takes an OCI image with container native ostree functionality and rebases itself on that image. + +By default, Podman machines on Mac and Linux use an rpm-ostree based distrubition (Fedora CoreOS). + +For more information, please see the [rpm-ostree docs](https://coreos.github.io/rpm-ostree/container/). + +## OPTIONS + +#### **--help** + +Print usage statement. + +#### **--restart** + +Restart VM after applying changes. + +## EXAMPLES + +``` +$ podman machine os apply quay.io/podman_next +$ podman machine os apply quay.io/podman_next podman-machine-defualt +``` + +## SEE ALSO +**[podman(1)](podman.1.md)**, **[podman-machine(1)](podman-machine.1.md)**, **[podman-machine-os(1)](podman-machine-os.1.md)** + +## HISTORY +February 2023, Originally compiled by Ashley Cui diff --git a/docs/source/markdown/podman-machine-os.1.md b/docs/source/markdown/podman-machine-os.1.md new file mode 100644 index 0000000000..5c69092c5f --- /dev/null +++ b/docs/source/markdown/podman-machine-os.1.md @@ -0,0 +1,22 @@ +% podman-machine-os 1 + +## NAME +podman\-machine\-os - Manage a Podman virtual machine's OS + +## SYNOPSIS +**podman machine os** *subcommand* + +## DESCRIPTION +`podman machine os` is a set of subcommands that manage a Podman virtual machine's operating system. + +## SUBCOMMANDS + +| Command | Man Page | Description | +|---------|--------------------------------------------------------------|----------------------------------------------| +| apply | [podman-machine-os-apply(1)](podman-machine-os-apply.1.md) | Apply an OCI image to a Podman Machine's OS | + +## SEE ALSO +**[podman(1)](podman.1.md)**, **[podman-machine(1)](podman-machine.1.md)**, **[podman-machine-os-apply(1)](podman-machine-os-apply.1.md)** + +## HISTORY +February 2023, Originally compiled by Ashley Cui diff --git a/docs/source/markdown/podman-machine.1.md b/docs/source/markdown/podman-machine.1.md index 37b864c366..e83f140aaa 100644 --- a/docs/source/markdown/podman-machine.1.md +++ b/docs/source/markdown/podman-machine.1.md @@ -22,20 +22,21 @@ environment variable while the machines are running can lead to unexpected behav ## SUBCOMMANDS -| Command | Man Page | Description | -|---------|------------------------------------------------------|-----------------------------------| -| info | [podman-machine-info(1)](podman-machine-info.1.md) | Display machine host info | -| init | [podman-machine-init(1)](podman-machine-init.1.md) | Initialize a new virtual machine | +| Command | Man Page | Description | +|---------|-----------------------------------------------------------|--------------------------------------| +| info | [podman-machine-info(1)](podman-machine-info.1.md) | Display machine host info | +| init | [podman-machine-init(1)](podman-machine-init.1.md) | Initialize a new virtual machine | | inspect | [podman-machine-inspect(1)](podman-machine-inspect.1.md) | Inspect one or more virtual machines | -| list | [podman-machine-list(1)](podman-machine-list.1.md) | List virtual machines | -| rm | [podman-machine-rm(1)](podman-machine-rm.1.md) | Remove a virtual machine | -| set | [podman-machine-set(1)](podman-machine-set.1.md) | Sets a virtual machine setting | -| ssh | [podman-machine-ssh(1)](podman-machine-ssh.1.md) | SSH into a virtual machine | -| start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine | -| stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine | +| list | [podman-machine-list(1)](podman-machine-list.1.md) | List virtual machines | +| os | [podman-machine-os(1)](podman-machine-os.1.md) | Manage a Podman virtual machine's OS | +| rm | [podman-machine-rm(1)](podman-machine-rm.1.md) | Remove a virtual machine | +| set | [podman-machine-set(1)](podman-machine-set.1.md) | Sets a virtual machine setting | +| ssh | [podman-machine-ssh(1)](podman-machine-ssh.1.md) | SSH into a virtual machine | +| start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine | +| stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine | ## SEE ALSO -**[podman(1)](podman.1.md)**, **[podman-machine-info(1)](podman-machine-info.1.md)**, **[podman-machine-init(1)](podman-machine-init.1.md)**, **[podman-machine-list(1)](podman-machine-list.1.md)**, **[podman-machine-rm(1)](podman-machine-rm.1.md)**, **[podman-machine-ssh(1)](podman-machine-ssh.1.md)**, **[podman-machine-start(1)](podman-machine-start.1.md)**, **[podman-machine-stop(1)](podman-machine-stop.1.md)**, **[podman-machine-inspect(1)](podman-machine-inspect.1.md)** +**[podman(1)](podman.1.md)**, **[podman-machine-info(1)](podman-machine-info.1.md)**, **[podman-machine-init(1)](podman-machine-init.1.md)**, **[podman-machine-list(1)](podman-machine-list.1.md)**, **[podman-machine-os(1)](podman-machine-os.1.md)**, **[podman-machine-rm(1)](podman-machine-rm.1.md)**, **[podman-machine-ssh(1)](podman-machine-ssh.1.md)**, **[podman-machine-start(1)](podman-machine-start.1.md)**, **[podman-machine-stop(1)](podman-machine-stop.1.md)**, **[podman-machine-inspect(1)](podman-machine-inspect.1.md)** ## HISTORY March 2021, Originally compiled by Ashley Cui diff --git a/pkg/machine/e2e/config_os_apply_test.go b/pkg/machine/e2e/config_os_apply_test.go new file mode 100644 index 0000000000..c85475f5c5 --- /dev/null +++ b/pkg/machine/e2e/config_os_apply_test.go @@ -0,0 +1,27 @@ +package e2e_test + +// type applyMachineOS struct { +// restart bool + +// cmd []string +// } + +// func (a *applyMachineOS) buildCmd(m *machineTestBuilder) []string { +// cmd := []string{"machine", "os", "apply"} +// if a.restart { +// cmd = append(cmd, "--restart") +// } + +// a.cmd = cmd +// return cmd +// } + +// func (a *applyMachineOS) withRestart() *applyMachineOS { +// a.restart = true +// return a +// } + +// func (a *applyMachineOS) args(cmd []string) *applyMachineOS { +// a.cmd = cmd +// return a +// } diff --git a/pkg/machine/e2e/os_test.go b/pkg/machine/e2e/os_test.go new file mode 100644 index 0000000000..032d3e4c11 --- /dev/null +++ b/pkg/machine/e2e/os_test.go @@ -0,0 +1,59 @@ +package e2e_test + +// import ( +// . "github.com/onsi/ginkgo" +// . "github.com/onsi/gomega" +// . "github.com/onsi/gomega/gexec" +// ) + +// var _ = Describe("podman machine os apply", func() { +// var ( +// mb *machineTestBuilder +// testDir string +// ) + +// BeforeEach(func() { +// testDir, mb = setup() +// }) +// AfterEach(func() { +// teardown(originalHomeDir, testDir, mb) +// }) + +// It("apply machine", func() { +// i := new(initMachine) +// foo1, err := mb.setName("foo1").setCmd(i.withImagePath(mb.imagePath)).run() +// Expect(err).ToNot(HaveOccurred()) +// Expect(foo1).To(Exit(0)) + +// apply := new(applyMachineOS) +// applySession, err := mb.setName("foo1").setCmd(apply.args([]string{"quay.io/baude/podman_next"})).run() +// Expect(err).ToNot(HaveOccurred()) +// Expect(applySession).To(Exit(0)) +// }) + +// It("apply machine from containers-storage", func() { +// i := new(initMachine) +// foo1, err := mb.setName("foo1").setCmd(i.withImagePath(mb.imagePath)).run() +// Expect(err).ToNot(HaveOccurred()) +// Expect(foo1).To(Exit(0)) + +// ssh := sshMachine{} +// sshSession, err := mb.setName("foo1").setCmd(ssh.withSSHComand([]string{"podman", "pull", "quay.io/baude/podman_next"})).run() +// Expect(err).ToNot(HaveOccurred()) +// Expect(sshSession).To(Exit(0)) + +// apply := new(applyMachineOS) +// applySession, err := mb.setName("foo1").setCmd(apply.args([]string{"quay.io/baude/podman_next"})).run() +// Expect(err).ToNot(HaveOccurred()) +// Expect(applySession).To(Exit(0)) +// Expect(applySession.outputToString()).To(ContainSubstring("Pulling from: containers-storage")) +// }) + +// It("apply machine not exist", func() { +// apply := new(applyMachineOS) +// applySession, err := mb.setName("foo1").setCmd(apply.args([]string{"quay.io/baude/podman_next", "notamachine"})).run() +// Expect(err).ToNot(HaveOccurred()) +// Expect(applySession).To(Exit(125)) +// Expect(applySession.errorToString()).To(ContainSubstring("not exist")) +// }) +// }) diff --git a/pkg/machine/os/config.go b/pkg/machine/os/config.go new file mode 100644 index 0000000000..b7158d0323 --- /dev/null +++ b/pkg/machine/os/config.go @@ -0,0 +1,15 @@ +//go:build amd64 || arm64 +// +build amd64 arm64 + +package os + +// Manager is the interface for operations on a Podman machine's OS +type Manager interface { + // Apply machine OS changes from an OCI image. + Apply(image string, opts ApplyOptions) error +} + +// ApplyOptions are the options for applying an image into a Podman machine VM +type ApplyOptions struct { + Image string +} diff --git a/pkg/machine/os/machine_os.go b/pkg/machine/os/machine_os.go new file mode 100644 index 0000000000..d8cd93519d --- /dev/null +++ b/pkg/machine/os/machine_os.go @@ -0,0 +1,40 @@ +//go:build amd64 || arm64 +// +build amd64 arm64 + +package os + +import ( + "fmt" + + "github.com/containers/podman/v4/pkg/machine" +) + +// MachineOS manages machine OS's from outside the machine. +type MachineOS struct { + Args []string + VM machine.VM + VMName string + Restart bool +} + +// Apply applies the image by sshing into the machine and running apply from inside the VM. +func (m *MachineOS) Apply(image string, opts ApplyOptions) error { + sshOpts := machine.SSHOptions{ + Args: []string{"podman", "machine", "os", "apply", image}, + } + + if err := m.VM.SSH(m.VMName, sshOpts); err != nil { + return err + } + + if m.Restart { + if err := m.VM.Stop(m.VMName, machine.StopOptions{}); err != nil { + return err + } + if err := m.VM.Start(m.VMName, machine.StartOptions{NoInfo: true}); err != nil { + return err + } + fmt.Printf("Machine %q restarted successfully\n", m.VMName) + } + return nil +} diff --git a/pkg/machine/os/ostree.go b/pkg/machine/os/ostree.go new file mode 100644 index 0000000000..0eaf446ba0 --- /dev/null +++ b/pkg/machine/os/ostree.go @@ -0,0 +1,142 @@ +//go:build amd64 || arm64 +// +build amd64 arm64 + +package os + +import ( + "errors" + "fmt" + "os" + "os/exec" + "regexp" + "strings" + + "github.com/containers/image/v5/transports/alltransports" + "github.com/sirupsen/logrus" +) + +// OSTree deals with operations on ostree based os's +type OSTree struct { //nolint:revive +} + +// Apply takes an OCI image and does an rpm-ostree rebase on the image +// If no containers-transport is specified, +// apply will first check if the image exists locally, then default to pulling. +// Exec-ing out to rpm-ostree rebase requires sudo, so this means apply cannot +// be called within podman's user namespace if run as rootless. +// This means that we need to export images in containers-storage to oci-dirs +// We also need to do this via an exec, because if we tried to use the ABI functions, +// we would enter the user namespace, the rebase command would fail. +// The pull portion of this function essentially is a work-around for two things: +// 1. rpm-ostree requires you to specify the containers-transport when pulling. +// The pull in podman allows the behavior of os apply to match other podman commands, +// where you only pull if the image does not exist in storage already. +// 2. This works around the root/rootless issue. +// Podman machines are by default set up using a rootless connection. +// rpm-ostree needs to be run as root. If a user wants to use an image in containers-storage, +// rpm-ostree will look at the root storage, and not the user storage, which is unexpected behavior. +// Exporting to an oci-dir works around this, without nagging the user to configure the machine in rootful mode. +func (dist *OSTree) Apply(image string, opts ApplyOptions) error { + imageWithTransport := image + + transport := alltransports.TransportFromImageName(image) + + switch { + // no transport was specified + case transport == nil: + exists, err := execPodmanImageExists(image) + if err != nil { + return err + } + + if exists { + fmt.Println("Pulling from", "containers-storage"+":", imageWithTransport) + dir, err := os.MkdirTemp("", pathSafeString(imageWithTransport)) + if err != nil { + return err + } + if err := os.Chmod(dir, 0755); err != nil { + return err + } + + defer func() { + if err := os.RemoveAll(dir); err != nil { + logrus.Errorf("failed to remove temporary pull file: %v", err) + } + }() + + if err := execPodmanSave(dir, image); err != nil { + return err + } + + imageWithTransport = "oci:" + dir + } else { + // if image doesn't exist locally, assume that we want to pull and use docker transport + imageWithTransport = "docker://" + image + } + // containers-transport specified + case transport.Name() == "containers-storage": + fmt.Println("Pulling from", image) + dir, err := os.MkdirTemp("", pathSafeString(strings.TrimPrefix(image, "containers-storage"+":"))) + if err != nil { + return err + } + + if err := os.Chmod(dir, 0755); err != nil { + return err + } + + defer func() { + if err := os.RemoveAll(dir); err != nil { + logrus.Errorf("failed to remove temporary pull file: %v", err) + } + }() + + if err := execPodmanSave(dir, image); err != nil { + return err + } + imageWithTransport = "oci:" + dir + } + + ostreeCli := []string{"rpm-ostree", "--bypass-driver", "rebase", fmt.Sprintf("ostree-unverified-image:%s", imageWithTransport)} + cmd := exec.Command("sudo", ostreeCli...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +// pathSafeString creates a path-safe name for our tmpdirs +func pathSafeString(str string) string { + alphanumOnly := regexp.MustCompile(`[^a-zA-Z0-9]+`) + + return alphanumOnly.ReplaceAllString(str, "") +} + +// execPodmanSave execs out to podman save +func execPodmanSave(dir, image string) error { + saveArgs := []string{"image", "save", "--format", "oci-dir", "-o", dir, image} + + saveCmd := exec.Command("podman", saveArgs...) + saveCmd.Stdout = os.Stdout + saveCmd.Stderr = os.Stderr + return saveCmd.Run() +} + +// execPodmanSave execs out to podman image exists +func execPodmanImageExists(image string) (bool, error) { + existsArgs := []string{"image", "exists", image} + + existsCmd := exec.Command("podman", existsArgs...) + + if err := existsCmd.Run(); err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + switch exitCode := exitError.ExitCode(); exitCode { + case 1: + return false, nil + default: + return false, errors.New("unable to access local image store") + } + } + } + return true, nil +}