mirror of
https://github.com/containers/podman.git
synced 2026-02-05 06:45:31 +01:00
Implements automatic OS upgrade functionality for Podman machines that requires no user input beyond running the command. The upgrade logic automatically determines the appropriate upgrade path using a three-way comparison between client version, machine version, and OCI registry: * When the client version is older than the machine version, no action is taken and an error is returned. * When the client version matches the machine version, the OCI registry is queried to check for in-band updates by comparing image digests. This handles minor, patch level, and updates oci image use cases. * When the client version is newer than the machine version, the machine is upgraded to match the client's major.minor version. * No manual image selection or version specification required. The command supports dry-run mode and JSON (only) output format for automation. Signed-off-by: Brent Baude <bbaude@redhat.com>
153 lines
4.4 KiB
Go
153 lines
4.4 KiB
Go
package os
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/blang/semver/v4"
|
|
"github.com/opencontainers/go-digest"
|
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/sirupsen/logrus"
|
|
"go.podman.io/image/v5/docker/reference"
|
|
"go.podman.io/image/v5/manifest"
|
|
)
|
|
|
|
// BootcHost represents the top-level bootc status structure
|
|
type BootcHost struct {
|
|
APIVersion string `json:"apiVersion"`
|
|
Kind string `json:"kind"`
|
|
Metadata bootcMetadata `json:"metadata"`
|
|
Spec bootcSpec `json:"spec"`
|
|
Status bootcStatus `json:"status"`
|
|
}
|
|
|
|
// bootcMetadata contains metadata about the host
|
|
type bootcMetadata struct {
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// bootcSpec contains the specification for the bootc host
|
|
type bootcSpec struct {
|
|
BootOrder string `json:"bootOrder"`
|
|
Image imageRef `json:"image"`
|
|
}
|
|
|
|
// imageRef represents a container image reference
|
|
type imageRef struct {
|
|
Image string `json:"image"`
|
|
Transport string `json:"transport"`
|
|
}
|
|
|
|
// bootcStatus contains the current status of the bootc host
|
|
type bootcStatus struct {
|
|
Booted *bootEntry `json:"booted"`
|
|
Rollback *bootEntry `json:"rollback"`
|
|
Staged *bootEntry `json:"staged"`
|
|
RollbackQueued bool `json:"rollbackQueued"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
// bootEntry represents a boot entry (booted, rollback, or staged)
|
|
type bootEntry struct {
|
|
CachedUpdate *imageStatus `json:"cachedUpdate"`
|
|
Image imageStatus `json:"image"`
|
|
Incompatible bool `json:"incompatible"`
|
|
Ostree ostreeInfo `json:"ostree"`
|
|
Pinned bool `json:"pinned"`
|
|
SoftRebootCapable bool `json:"softRebootCapable"`
|
|
Store string `json:"store"`
|
|
}
|
|
|
|
// imageStatus contains detailed information about a container image
|
|
type imageStatus struct {
|
|
Architecture string `json:"architecture"`
|
|
Image imageRefWithSig `json:"image"`
|
|
ImageDigest string `json:"imageDigest"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Version string `json:"version"`
|
|
}
|
|
|
|
// imageRefWithSig is an image reference that may include a signature
|
|
type imageRefWithSig struct {
|
|
Image string `json:"image"`
|
|
Transport string `json:"transport"`
|
|
Signature *signature `json:"signature,omitempty"`
|
|
}
|
|
|
|
// signature contains signature information for an image
|
|
type signature struct {
|
|
OstreeRemote string `json:"ostreeRemote"`
|
|
}
|
|
|
|
// ostreeInfo contains OSTree-specific information
|
|
type ostreeInfo struct {
|
|
Checksum string `json:"checksum"`
|
|
DeploySerial int `json:"deploySerial"`
|
|
Stateroot string `json:"stateroot"`
|
|
}
|
|
|
|
// GetBootedImageRef returns the booted image reference (status -> booted -> image -> image)
|
|
// Returns an empty string if booted is nil
|
|
func (b *BootcHost) GetBootedImageRef() string {
|
|
if b.Status.Booted == nil {
|
|
return ""
|
|
}
|
|
return b.Status.Booted.Image.Image.Image
|
|
}
|
|
|
|
// GetBootedOSTreeChecksum returns the booted OSTree checksum
|
|
// Returns an empty string if booted is nil
|
|
func (b *BootcHost) GetBootedOSTreeChecksum() string {
|
|
if b.Status.Booted == nil {
|
|
return ""
|
|
}
|
|
return b.Status.Booted.Ostree.Checksum
|
|
}
|
|
|
|
func (b *BootcHost) getLocalOSOCIInfo() (reference.Named, *semver.Version, error) {
|
|
ref, err := reference.ParseNormalizedNamed(b.GetBootedImageRef())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
named, ok := ref.(reference.NamedTagged)
|
|
if !ok {
|
|
return nil, nil, errors.New("failed to parse ostree origin")
|
|
}
|
|
tagAsVersion, err := semver.ParseTolerant(named.Tag())
|
|
return named, &tagAsVersion, err
|
|
}
|
|
|
|
func (b *BootcHost) getLocalOsImageDigest() (digest.Digest, error) {
|
|
args := []string{"container", "image", "metadata", "--repo", "/ostree/repo", "docker://" + b.GetBootedImageRef()}
|
|
logrus.Debugf("executing : ostree %s", strings.Join(args, " "))
|
|
cmd := exec.Command("ostree", args...)
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
localOs, err := manifest.FromBlob(out, v1.MediaTypeImageManifest)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return localOs.ConfigInfo().Digest, nil
|
|
}
|
|
|
|
// newBootcHost creates a new BootcHost by executing 'sudo bootc status --format json'
|
|
// and unmarshaling the output
|
|
func newBootcHost() (*BootcHost, error) {
|
|
cmd := exec.Command("sudo", "bootc", "status", "--format", "json")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var host BootcHost
|
|
if err := json.Unmarshal(output, &host); err != nil {
|
|
return nil, err
|
|
}
|
|
return &host, nil
|
|
}
|