cmd/run, pkg/podman: Make podman.InspectContainer() return a Container

Unmarshal the JSON from 'podman inspect --format json --type container'
directly inside podman.InspectContainer() to confine the details within
the podman package.

The JSON samples for the unit tests were taken using the default Toolbx
container on versions of Fedora that shipped a specific Podman and
Toolbx version.  This accounts for differences in the JSON caused by
different major versions of Podman and the way different Toolbx versions
set up the containers.

One exception was Fedora 28, which had Podman 1.1.2 and Toolbx 0.0.9,
which was the last Toolbx version before 'toolbox init-container' became
the entry point for all Toolbx containers [1].  However, the default
Toolbx image is no longer available from registry.fedoraproject.org.
Hence, the image for Fedora 29 was used.

The minimum required Podman version is 1.6.4 [2], and the Go
implementation has been encouraging users to create containers with
Toolbx version 0.0.17 or newer [3].  The versions used to collect the
JSON samples for the unit tests were chosen accordingly.  They don't
exhaustively cover all possible supported and unsupported version
combinations, but hopefully enough to be useful.

[1] Commit 8b84b5e460
    https://github.com/containers/toolbox/commit/8b84b5e4604921fa
    https://github.com/debarshiray/toolbox/pull/160

[2] Commit 8e80dd5db1
    https://github.com/containers/toolbox/commit/8e80dd5db1e6f40b
    https://github.com/containers/toolbox/pull/1253

[3] Commit 238f2451e7
    https://github.com/containers/toolbox/commit/238f2451e7d7d54a
    https://github.com/containers/toolbox/pull/318

https://github.com/containers/toolbox/pull/1490
This commit is contained in:
Debarshi Ray 2024-05-09 23:52:40 +02:00
parent e611969726
commit ec7eb59bb0
6 changed files with 2390 additions and 35 deletions

View File

@ -13,3 +13,4 @@
{"/var/lib/flatpak", "/run/host/var/lib/flatpak", "ro"}, {"/var/lib/flatpak", "/run/host/var/lib/flatpak", "ro"},
{"/var/lib/systemd/coredump", "/run/host/var/lib/systemd/coredump", "ro"}, {"/var/lib/systemd/coredump", "/run/host/var/lib/systemd/coredump", "ro"},
{"/var/log/journal", "/run/host/var/log/journal", "ro"}, {"/var/log/journal", "/run/host/var/log/journal", "ro"},
" \"ro\"," +

View File

@ -433,17 +433,16 @@ func runHelp(cmd *cobra.Command, args []string) {
func callFlatpakSessionHelper(container string) error { func callFlatpakSessionHelper(container string) error {
logrus.Debugf("Inspecting mounts of container %s", container) logrus.Debugf("Inspecting mounts of container %s", container)
info, err := podman.InspectContainer(container) containerObj, err := podman.InspectContainer(container)
if err != nil { if err != nil {
return fmt.Errorf("failed to inspect entry point of container %s", container) return fmt.Errorf("failed to inspect entry point of container %s", container)
} }
var needsFlatpakSessionHelper bool var needsFlatpakSessionHelper bool
mounts := info["Mounts"].([]interface{}) mounts := containerObj.Mounts()
for _, mount := range mounts { for _, mount := range mounts {
destination := mount.(map[string]interface{})["Destination"].(string) if mount == "/run/host/monitor" {
if destination == "/run/host/monitor" {
logrus.Debug("Requires org.freedesktop.Flatpak.SessionHelper") logrus.Debug("Requires org.freedesktop.Flatpak.SessionHelper")
needsFlatpakSessionHelper = true needsFlatpakSessionHelper = true
break break
@ -526,30 +525,17 @@ func constructExecArgs(container, preserveFDs string,
func getEntryPointAndPID(container string) (string, int, error) { func getEntryPointAndPID(container string) (string, int, error) {
logrus.Debugf("Inspecting entry point of container %s", container) logrus.Debugf("Inspecting entry point of container %s", container)
info, err := podman.InspectContainer(container) containerObj, err := podman.InspectContainer(container)
if err != nil { if err != nil {
return "", 0, fmt.Errorf("failed to inspect entry point of container %s", container) return "", 0, fmt.Errorf("failed to inspect entry point of container %s", container)
} }
config := info["Config"].(map[string]interface{}) entryPoint := containerObj.EntryPoint()
entryPoint := config["Cmd"].([]interface{})[0].(string) entryPointPID := containerObj.EntryPointPID()
state := info["State"].(map[string]interface{}) logrus.Debugf("Entry point of container %s is %s (PID=%d)", container, entryPoint, entryPointPID)
entryPointPID := state["Pid"]
logrus.Debugf("Entry point PID is a %T", entryPointPID)
var entryPointPIDInt int return entryPoint, entryPointPID, nil
switch entryPointPID := entryPointPID.(type) {
case float64:
entryPointPIDInt = int(entryPointPID)
default:
return "", 0, fmt.Errorf("failed to inspect entry point PID of container %s", container)
}
logrus.Debugf("Entry point of container %s is %s (PID=%d)", container, entryPoint, entryPointPIDInt)
return entryPoint, entryPointPIDInt, nil
} }
func isCommandPresent(container, command string) (bool, error) { func isCommandPresent(container, command string) (bool, error) {

View File

@ -23,6 +23,7 @@ sources = files(
'pkg/podman/container.go', 'pkg/podman/container.go',
'pkg/podman/errors.go', 'pkg/podman/errors.go',
'pkg/podman/podman.go', 'pkg/podman/podman.go',
'pkg/podman/containerInspect_test.go',
'pkg/shell/shell.go', 'pkg/shell/shell.go',
'pkg/shell/shell_test.go', 'pkg/shell/shell_test.go',
'pkg/skopeo/skopeo.go', 'pkg/skopeo/skopeo.go',

View File

@ -18,27 +18,46 @@ package podman
import ( import (
"encoding/json" "encoding/json"
"time"
"github.com/containers/toolbox/pkg/utils" "github.com/containers/toolbox/pkg/utils"
) )
type Container interface { type Container interface {
Created() string Created() string
EntryPoint() string
EntryPointPID() int
ID() string ID() string
Image() string Image() string
Labels() map[string]string Labels() map[string]string
Mounts() []string
Name() string Name() string
Names() []string Names() []string
Status() string Status() string
} }
type containerInspect struct {
created string
entryPoint string
entryPointPID int
id string
image string
labels map[string]string
mounts []string
name string
status string
}
type containerPS struct { type containerPS struct {
created string created string
id string entryPoint string
image string entryPointPID int
labels map[string]string id string
names []string image string
status string labels map[string]string
mounts []string
names []string
status string
} }
type Containers struct { type Containers struct {
@ -46,10 +65,105 @@ type Containers struct {
i int i int
} }
func (container *containerInspect) Created() string {
return container.created
}
func (container *containerInspect) EntryPoint() string {
return container.entryPoint
}
func (container *containerInspect) EntryPointPID() int {
return container.entryPointPID
}
func (container *containerInspect) ID() string {
return container.id
}
func (container *containerInspect) Image() string {
return container.image
}
func (container *containerInspect) Labels() map[string]string {
return container.labels
}
func (container *containerInspect) Mounts() []string {
return container.mounts
}
func (container *containerInspect) Name() string {
return container.name
}
func (container *containerInspect) Names() []string {
return []string{container.name}
}
func (container *containerInspect) Status() string {
return container.status
}
func (container *containerInspect) UnmarshalJSON(data []byte) error {
var raw struct {
Config struct {
Cmd []string
Labels map[string]string
}
Created time.Time
ID string
ImageName string
Mounts []struct {
Destination string
}
Name string
State struct {
PID int
Status string
}
}
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
if len(raw.Config.Cmd) > 0 {
container.entryPoint = raw.Config.Cmd[0]
}
container.entryPointPID = raw.State.PID
created := raw.Created.Unix()
container.created = utils.HumanDuration(created)
container.id = raw.ID
container.image = raw.ImageName
container.labels = raw.Config.Labels
for _, mount := range raw.Mounts {
if mount.Destination != "" {
container.mounts = append(container.mounts, mount.Destination)
}
}
container.name = raw.Name
container.status = raw.State.Status
return nil
}
func (container *containerPS) Created() string { func (container *containerPS) Created() string {
return container.created return container.created
} }
func (container *containerPS) EntryPoint() string {
return container.entryPoint
}
func (container *containerPS) EntryPointPID() int {
return container.entryPointPID
}
func (container *containerPS) ID() string { func (container *containerPS) ID() string {
return container.id return container.id
} }
@ -62,6 +176,10 @@ func (container *containerPS) Labels() map[string]string {
return container.labels return container.labels
} }
func (container *containerPS) Mounts() []string {
return container.mounts
}
func (container *containerPS) Name() string { func (container *containerPS) Name() string {
return container.names[0] return container.names[0]
} }
@ -76,11 +194,14 @@ func (container *containerPS) Status() string {
func (container *containerPS) UnmarshalJSON(data []byte) error { func (container *containerPS) UnmarshalJSON(data []byte) error {
var raw struct { var raw struct {
Command []string
Created interface{} Created interface{}
ID string ID string
Image string Image string
Labels map[string]string Labels map[string]string
Mounts []string
Names interface{} Names interface{}
PID int
State interface{} State interface{}
Status string Status string
} }
@ -89,6 +210,12 @@ func (container *containerPS) UnmarshalJSON(data []byte) error {
return err return err
} }
if len(raw.Command) > 0 {
container.entryPoint = raw.Command[0]
}
container.entryPointPID = raw.PID
// In Podman V1 the field 'Created' held a human-readable string in format // In Podman V1 the field 'Created' held a human-readable string in format
// "5 minutes ago". Since Podman V2 the field holds an integer with Unix time. // "5 minutes ago". Since Podman V2 the field holds an integer with Unix time.
// After a discussion in https://github.com/containers/podman/issues/6594 the // After a discussion in https://github.com/containers/podman/issues/6594 the
@ -106,6 +233,7 @@ func (container *containerPS) UnmarshalJSON(data []byte) error {
container.id = raw.ID container.id = raw.ID
container.image = raw.Image container.image = raw.Image
container.labels = raw.Labels container.labels = raw.Labels
container.mounts = raw.Mounts
// In Podman V1 the field 'Names' held a single string but since Podman V2 the // In Podman V1 the field 'Names' held a single string but since Podman V2 the
// field holds an array of strings // field holds an array of strings

File diff suppressed because it is too large Load Diff

View File

@ -302,7 +302,7 @@ func ImageExists(image string) (bool, error) {
} }
// InspectContainer is a wrapper around 'podman inspect --type container' command // InspectContainer is a wrapper around 'podman inspect --type container' command
func InspectContainer(container string) (map[string]interface{}, error) { func InspectContainer(container string) (Container, error) {
var stdout bytes.Buffer var stdout bytes.Buffer
logLevelString := LogLevel.String() logLevelString := LogLevel.String()
@ -313,13 +313,12 @@ func InspectContainer(container string) (map[string]interface{}, error) {
} }
output := stdout.Bytes() output := stdout.Bytes()
var info []map[string]interface{} var containers []containerInspect
if err := json.Unmarshal(output, &containers); err != nil {
if err := json.Unmarshal(output, &info); err != nil {
return nil, err return nil, err
} }
return info[0], nil return &containers[0], nil
} }
// InspectImage is a wrapper around 'podman inspect --type image' command // InspectImage is a wrapper around 'podman inspect --type image' command
@ -344,12 +343,12 @@ func InspectImage(image string) (map[string]interface{}, error) {
} }
func IsToolboxContainer(container string) (bool, error) { func IsToolboxContainer(container string) (bool, error) {
info, err := InspectContainer(container) containerObj, err := InspectContainer(container)
if err != nil { if err != nil {
return false, fmt.Errorf("failed to inspect container %s", container) return false, fmt.Errorf("failed to inspect container %s", container)
} }
labels, _ := info["Config"].(map[string]interface{})["Labels"].(map[string]interface{}) labels := containerObj.Labels()
if labels["com.github.containers.toolbox"] != "true" && labels["com.github.debarshiray.toolbox"] != "true" { if labels["com.github.containers.toolbox"] != "true" && labels["com.github.debarshiray.toolbox"] != "true" {
return false, fmt.Errorf("%s is not a Toolbx container", container) return false, fmt.Errorf("%s is not a Toolbx container", container)
} }