mirror of
https://github.com/containers/podman.git
synced 2025-08-06 19:44:14 +08:00

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 <acui@redhat.com>
143 lines
4.3 KiB
Go
143 lines
4.3 KiB
Go
//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
|
|
}
|