mirror of
https://github.com/containers/podman.git
synced 2026-02-05 06:45:31 +01:00
add bootc transports to os-apply
now that we use `bootc switch` for changing out-of-band updates, we can consider also using some of their supported transports. * containers-storage * oci * oci-archive * registry RUN-3963 Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
@@ -12,14 +12,18 @@ import (
|
||||
)
|
||||
|
||||
var applyCmd = &cobra.Command{
|
||||
Use: "apply [options] IMAGE [NAME]",
|
||||
Use: "apply [options] URI [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`,
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||
images, _ := common.AutocompleteImages(cmd, args, toComplete)
|
||||
// We also accept an URI so ignore ShellCompDirectiveNoFileComp and use the default one instead to get file paths completed by the shell.
|
||||
return images, cobra.ShellCompDirectiveDefault
|
||||
},
|
||||
Example: `podman machine os apply myimage`,
|
||||
}
|
||||
|
||||
var restart bool
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
podman\-machine\-os\-apply - Apply an OCI image to a Podman Machine's OS
|
||||
|
||||
## SYNOPSIS
|
||||
**podman machine os apply** [*options*] *image* [vm]
|
||||
**podman machine os apply** [*options*] *uri* [vm]
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -19,7 +19,16 @@ 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`.
|
||||
The applying of the OCI image is done by a command called `bootc` and specifically `bootc switch`. By default, this command
|
||||
takes an OCI registry image reference like `quay.io/custom/machine-os:latest`. However, `bootc` also
|
||||
understands references with different transports. At present, Podman will support the following transports:
|
||||
|
||||
* containers-storage
|
||||
* oci
|
||||
* oci-archive
|
||||
* registry
|
||||
|
||||
Examples for these transports in URI form are provided below.
|
||||
|
||||
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
|
||||
@@ -47,15 +56,37 @@ 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 6.1 bootable
|
||||
OCI image.
|
||||
Apply a new custom operating system from an OCI bootable image on quay.
|
||||
```
|
||||
$ podman machine os apply quay.io/podman/machine-os:6.1
|
||||
$ podman machine os apply quay.io/custom/machine-os:latest
|
||||
```
|
||||
|
||||
Update the specified Podman machine to latest Podman 6.1 bootable OCI image.
|
||||
Apply a new custom operating system to a specific machine from an OCI bootable image on quay.
|
||||
```
|
||||
$ podman machine os apply quay.io/podman/machine-os:6.1 mymachine
|
||||
$ podman machine os apply quay.io/custom/machine-os:latest mymachine
|
||||
```
|
||||
|
||||
Apply a new custom operating system that was pulled or built on your existing machine by the unprivileged user.
|
||||
Note the use of brackets around the path because this command is run by the root user.
|
||||
```
|
||||
$ podman machine os apply containers-storage:[/home/core/.local/share/containers/storage]localhost/mycustomimage:latest
|
||||
```
|
||||
|
||||
Apply a new custom operating system that was pulled or built on your existing machine by the privileged user. Note that
|
||||
because a privileged user built or pulled the image, `bootc` will resolve that users storage and the path
|
||||
is not needed.
|
||||
```
|
||||
$ podman machine os apply containers-storage:localhost/mycustomimage:latest
|
||||
```
|
||||
|
||||
Apply a new custom operating system from an OCI archive in tar form.
|
||||
```
|
||||
$ podman machine os apply oci-archive:/tmp/oci-image.tar
|
||||
```
|
||||
|
||||
Apply a new custom operating system from an OCI formatted directory.
|
||||
```
|
||||
$ podman machine os apply oci:/tmp/oci-image/
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/blang/semver/v4"
|
||||
"github.com/opencontainers/go-digest"
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
"go.podman.io/image/v5/docker/reference"
|
||||
"go.podman.io/image/v5/image"
|
||||
"go.podman.io/image/v5/manifest"
|
||||
"go.podman.io/image/v5/transports/alltransports"
|
||||
"go.podman.io/image/v5/types"
|
||||
)
|
||||
|
||||
@@ -27,7 +29,17 @@ type OSTree struct{}
|
||||
// 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 {
|
||||
cli := []string{"bootc", "switch", image}
|
||||
t, pathOrImageRef, err := parseApplyInput(image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli := []string{"bootc", "switch"}
|
||||
// registry is the default transport and therefore not
|
||||
// necessary
|
||||
if t != "registry" {
|
||||
cli = append(cli, "--transport", t)
|
||||
}
|
||||
cli = append(cli, pathOrImageRef)
|
||||
cmd := exec.Command("sudo", cli...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
@@ -185,3 +197,76 @@ func printJSON(out UpgradeOutput) error {
|
||||
fmt.Println(string(b))
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseApplyInput takes well known OCI references and splits them up. this
|
||||
// function should only be used to deal with bootc transports. podman takes
|
||||
// the entire reference as an oci reference but bootc requires we use
|
||||
// --transport if the default transport is not used. also, bootc's default
|
||||
// transport is "registry" which mimics docker in the way it is handled.
|
||||
func parseApplyInput(arg string) (string, string, error) {
|
||||
var (
|
||||
containersStorageTransport = "containers-storage"
|
||||
dockerTransport = "docker"
|
||||
ociArchiveTransport = "oci-archive"
|
||||
ociTransport = "oci"
|
||||
registryTransport = "registry"
|
||||
)
|
||||
|
||||
imgRef, err := alltransports.ParseImageName(arg)
|
||||
if err == nil {
|
||||
transportName := imgRef.Transport().Name()
|
||||
if imgRef.DockerReference() != nil {
|
||||
// bootc docs do not show the docker transport, but the
|
||||
// code apparently does handle it and because docker is
|
||||
// a well-known transport, we do as well, but we change
|
||||
// it to registry for convenience.
|
||||
if transportName == dockerTransport {
|
||||
transportName = registryTransport
|
||||
}
|
||||
// docker://quay.io/fedora/fedora-bootc:40
|
||||
return transportName, imgRef.DockerReference().String(), nil
|
||||
}
|
||||
|
||||
imagePath := imgRef.StringWithinTransport()
|
||||
if transportName == ociTransport { //nolint:staticcheck
|
||||
// oci:/tmp/oci-image
|
||||
imagePath, _, _ = strings.Cut(imagePath, ":")
|
||||
} else if transportName == ociArchiveTransport {
|
||||
// oci-archive:/tmp/myimage.tar
|
||||
imagePath = strings.TrimSuffix(imagePath, ":")
|
||||
}
|
||||
return transportName, imagePath, nil
|
||||
}
|
||||
|
||||
// quay.io/fedora/fedora-bootc:40
|
||||
if _, err := reference.ParseNamed(arg); err == nil {
|
||||
return registryTransport, arg, nil
|
||||
}
|
||||
|
||||
// registry://quay.io/fedora/fedora-bootc:40
|
||||
afterReg, hasRegistry := strings.CutPrefix(arg, registryTransport+"://")
|
||||
if hasRegistry {
|
||||
return registryTransport, afterReg, nil
|
||||
}
|
||||
|
||||
// oci-archive:/var/tmp/fedora-bootc.tar
|
||||
// oci-archive:/mnt/usb/images/myapp.tar
|
||||
afterOCIArchive, hasOCIArchive := strings.CutPrefix(arg, ociArchiveTransport+":")
|
||||
if hasOCIArchive {
|
||||
return ociArchiveTransport, afterOCIArchive, nil
|
||||
}
|
||||
|
||||
// oci:/tmp/oci-image
|
||||
// oci:/var/mnt/usb/bootc-image
|
||||
afterOCI, hasOCI := strings.CutPrefix(arg, ociTransport+":")
|
||||
if hasOCI {
|
||||
return ociTransport, afterOCI, nil
|
||||
}
|
||||
// containers-storage:/home/user:localhost/fedora-bootc:latest
|
||||
afterCS, hasCS := strings.CutPrefix(arg, containersStorageTransport+":")
|
||||
if hasCS {
|
||||
return containersStorageTransport, afterCS, nil
|
||||
}
|
||||
|
||||
return "", "", fmt.Errorf("unknown transport %q given", arg)
|
||||
}
|
||||
|
||||
@@ -89,3 +89,151 @@ func Test_compareMajorMinor(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseApplyInput(t *testing.T) {
|
||||
type args struct {
|
||||
arg string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
want1 string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "bare registry reference with tag",
|
||||
args: args{arg: "quay.io/fedora/fedora-bootc:40"},
|
||||
want: "registry",
|
||||
want1: "quay.io/fedora/fedora-bootc:40",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "docker transport with tag",
|
||||
args: args{arg: "docker://quay.io/fedora/fedora-bootc:40"},
|
||||
want: "registry",
|
||||
want1: "quay.io/fedora/fedora-bootc:40",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "docker transport with digest",
|
||||
args: args{arg: "docker://quay.io/podman/stable@sha256:9cca0703342e24806a9f64e08c053dca7f2cd90f10529af8ea872afb0a0c77d4"},
|
||||
want: "registry",
|
||||
want1: "quay.io/podman/stable@sha256:9cca0703342e24806a9f64e08c053dca7f2cd90f10529af8ea872afb0a0c77d4",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "registry transport with tag",
|
||||
args: args{arg: "registry://quay.io/fedora/fedora-bootc:40"},
|
||||
want: "registry",
|
||||
want1: "quay.io/fedora/fedora-bootc:40",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "registry transport with digest",
|
||||
args: args{arg: "registry://quay.io/podman/stable@sha256:9cca0703342e24806a9f64e08c053dca7f2cd90f10529af8ea872afb0a0c77d4"},
|
||||
want: "registry",
|
||||
want1: "quay.io/podman/stable@sha256:9cca0703342e24806a9f64e08c053dca7f2cd90f10529af8ea872afb0a0c77d4",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "registry transport with port",
|
||||
args: args{arg: "registry://localhost:5000/myapp:latest"},
|
||||
want: "registry",
|
||||
want1: "localhost:5000/myapp:latest",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "oci transport var path",
|
||||
args: args{arg: "oci:/var/mnt/usb/bootc-image"},
|
||||
want: "oci",
|
||||
want1: "/var/mnt/usb/bootc-image",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "oci transport opt path",
|
||||
args: args{arg: "oci:/opt/images/mylayout"},
|
||||
want: "oci",
|
||||
want1: "/opt/images/mylayout",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "oci transport tmp path",
|
||||
args: args{arg: "oci:/tmp/oci-image"},
|
||||
want: "oci",
|
||||
want1: "/tmp/oci-image",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "oci transport with reference",
|
||||
args: args{arg: "oci:/path/to/oci-dir:myref"},
|
||||
want: "oci",
|
||||
want1: "/path/to/oci-dir:myref",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "oci-archive transport tmp tar",
|
||||
args: args{arg: "oci-archive:/tmp/myimage.tar"},
|
||||
want: "oci-archive",
|
||||
want1: "/tmp/myimage.tar",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "oci-archive transport mnt path",
|
||||
args: args{arg: "oci-archive:/mnt/usb/images/myapp.tar"},
|
||||
want: "oci-archive",
|
||||
want1: "/mnt/usb/images/myapp.tar",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "oci-archive transport with reference",
|
||||
args: args{arg: "oci-archive:/home/user/archives/image.tar:myref"},
|
||||
want: "oci-archive",
|
||||
want1: "/home/user/archives/image.tar:myref",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "containers-storage transport with tag",
|
||||
args: args{arg: "containers-storage:quay.io/podman/machine-os:6.0"},
|
||||
want: "containers-storage",
|
||||
want1: "quay.io/podman/machine-os:6.0",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "containers-storage transport with graph root",
|
||||
args: args{arg: "containers-storage:[/home/core/.local/share/containers/storage]quay.io/podman/machine-os:6.0"},
|
||||
want: "containers-storage",
|
||||
want1: "[/home/core/.local/share/containers/storage]quay.io/podman/machine-os:6.0",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "good reference bad transport",
|
||||
args: args{"foobar://quay.io/podman/machine-os:6.0"},
|
||||
want: "",
|
||||
want1: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "similar name but incorrect transport",
|
||||
args: args{"oci-dir:/foo/bar/bar.tar"},
|
||||
want: "",
|
||||
want1: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, got1, err := parseApplyInput(tt.args.arg)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parseApplyInput() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("parseApplyInput() got = '%v', want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("parseApplyInput() got1 = '%v', want %v", got1, tt.want1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user