1
0
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:
Brent Baude
2026-01-30 13:09:40 -06:00
parent 2467b71c4a
commit f4138d3599
4 changed files with 279 additions and 11 deletions

View File

@@ -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)
}

View File

@@ -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)
}
})
}
}