mirror of
https://github.com/containers/podman.git
synced 2026-02-05 15:45:08 +01:00
use bootc for os apply
Instead of using rpm-ostree, we now use bootc for os apply. the implementation is a little murky right now and will require some cleanup to implement bootc's transports. for now, we only support oci images from registries. once we have an upgrade command, the transports can be added and the docs for apply can be ammended to be more clear. Fixes: RUN-3836 Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
@@ -10,22 +10,22 @@ podman\-machine\-os\-apply - Apply an OCI image to a Podman Machine's OS
|
||||
|
||||
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.
|
||||
The Podman virtual machine has 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, Linux, and Windows Hyper-V use a customized rpm-ostree based distribution (Fedora CoreOS). Machines based on Microsoft WSL use a
|
||||
customized Fedora distribution and cannot be updated with this command.
|
||||
customized distribution and cannot be updated with this command.
|
||||
|
||||
Note: WSL-based machines are upgradable by using the `podman machine ssh <machine_name>` command followed by `sudo dnf update`. This can, however, result in unexpected results in
|
||||
Podman client and server version differences.
|
||||
|
||||
The applying of the OCI image is done by a command called `bootc`.
|
||||
|
||||
Podman machine images are stored as OCI images at `quay.io/podman/machine-os`. When applying an image using this
|
||||
command, the fully qualified OCI reference name must be used including tag where the tag is the
|
||||
version of Podman that is inside the VM. By default, Podman will attempt to pull only the statement
|
||||
version as itself.
|
||||
|
||||
For more information, see the [rpm-ostree documentation](https://coreos.github.io/rpm-ostree/container/).
|
||||
|
||||
The default machine name is `podman-machine-default`. If a machine name is not specified as an argument,
|
||||
then the OS changes will be applied to `podman-machine-default`.
|
||||
|
||||
@@ -47,15 +47,15 @@ bootable OCI image.
|
||||
Note: This may result in having a newer Podman version inside the machine
|
||||
than the client. Unexpected results may occur.
|
||||
|
||||
Update the default Podman machine to the most recent Podman 5.4 bootable
|
||||
Update the default Podman machine to the most recent Podman 6.1 bootable
|
||||
OCI image.
|
||||
```
|
||||
$ podman machine os apply quay.io/podman/machine-os:5.4
|
||||
$ podman machine os apply quay.io/podman/machine-os:6.1
|
||||
```
|
||||
|
||||
Update the specified Podman machine to latest Podman 5.3 bootable OCI image.
|
||||
Update the specified Podman machine to latest Podman 6.1 bootable OCI image.
|
||||
```
|
||||
$ podman machine os apply quay.io/podman/machine-os:5.3 mymachine
|
||||
$ podman machine os apply quay.io/podman/machine-os:6.1 mymachine
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
@@ -24,7 +24,7 @@ func (m *MachineOS) Apply(image string, _ ApplyOptions) error {
|
||||
var off bool
|
||||
args := []string{"podman", "machine", "os", "apply", image}
|
||||
|
||||
if err := machine.LocalhostSSH(m.VM.SSH.RemoteUsername, m.VM.SSH.IdentityPath, m.VMName, m.VM.SSH.Port, args); err != nil {
|
||||
if err := machine.LocalhostSSHShellForceTerm(m.VM.SSH.RemoteUsername, m.VM.SSH.IdentityPath, m.VMName, m.VM.SSH.Port, args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -3,138 +3,21 @@
|
||||
package os
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.podman.io/image/v5/transports/alltransports"
|
||||
)
|
||||
|
||||
// OSTree deals with operations on ostree based os's
|
||||
type OSTree struct{}
|
||||
|
||||
// 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.
|
||||
// Apply takes an input that describes an image based on transports
|
||||
// defined by bootc. Omission of a transport assumes the image
|
||||
// is pulled from an OCI registry. We simply pass the user
|
||||
// input to bootc without any manipulation.
|
||||
func (dist *OSTree) Apply(image string, _ 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, 0o755); 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, 0o755); 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...)
|
||||
cli := []string{"bootc", "switch", image}
|
||||
cmd := exec.Command("sudo", cli...)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -20,8 +20,13 @@ func LocalhostSSH(username, identityPath, name string, sshPort int, inputArgs []
|
||||
return localhostBuiltinSSH(username, identityPath, name, sshPort, inputArgs, true, os.Stdin)
|
||||
}
|
||||
|
||||
// LocalhostSSHShellForceTerm runs the native ssh shell client and forces a terminal (-t)
|
||||
func LocalhostSSHShellForceTerm(username, identityPath, name string, sshPort int, inputArgs []string) error {
|
||||
return localhostNativeSSH(username, identityPath, name, sshPort, inputArgs, os.Stdin, true)
|
||||
}
|
||||
|
||||
func LocalhostSSHShell(username, identityPath, name string, sshPort int, inputArgs []string) error {
|
||||
return localhostNativeSSH(username, identityPath, name, sshPort, inputArgs, os.Stdin)
|
||||
return localhostNativeSSH(username, identityPath, name, sshPort, inputArgs, os.Stdin, false)
|
||||
}
|
||||
|
||||
func LocalhostSSHSilent(username, identityPath, name string, sshPort int, inputArgs []string) error {
|
||||
@@ -117,13 +122,18 @@ func createLocalhostConfig(user string, identityPath string) (*ssh.ClientConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func localhostNativeSSH(username, identityPath, name string, sshPort int, inputArgs []string, stdin io.Reader) error {
|
||||
func localhostNativeSSH(username, identityPath, name string, sshPort int, inputArgs []string, stdin io.Reader, forceTerm bool) error {
|
||||
sshDestination := username + "@localhost"
|
||||
port := strconv.Itoa(sshPort)
|
||||
interactive := true
|
||||
|
||||
args := append([]string{"-i", identityPath, "-p", port, sshDestination}, LocalhostSSHArgs()...) // WARNING: This MUST NOT be generalized to allow communication over untrusted networks.
|
||||
if len(inputArgs) > 0 {
|
||||
// on the other condition, the term is forced
|
||||
// anyway
|
||||
if forceTerm {
|
||||
args = append(args, "-t")
|
||||
}
|
||||
interactive = false
|
||||
args = append(args, inputArgs...)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user