mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-10-22 05:47:35 +00:00
Merge pull request #27875 from baude/bootcosapply
use bootc for os apply
This commit is contained in:
commit
fd90d334a3
4 changed files with 27 additions and 134 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue