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/systemd/coredump", "/run/host/var/lib/systemd/coredump", "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 {
logrus.Debugf("Inspecting mounts of container %s", container)
info, err := podman.InspectContainer(container)
containerObj, err := podman.InspectContainer(container)
if err != nil {
return fmt.Errorf("failed to inspect entry point of container %s", container)
}
var needsFlatpakSessionHelper bool
mounts := info["Mounts"].([]interface{})
mounts := containerObj.Mounts()
for _, mount := range mounts {
destination := mount.(map[string]interface{})["Destination"].(string)
if destination == "/run/host/monitor" {
if mount == "/run/host/monitor" {
logrus.Debug("Requires org.freedesktop.Flatpak.SessionHelper")
needsFlatpakSessionHelper = true
break
@ -526,30 +525,17 @@ func constructExecArgs(container, preserveFDs string,
func getEntryPointAndPID(container string) (string, int, error) {
logrus.Debugf("Inspecting entry point of container %s", container)
info, err := podman.InspectContainer(container)
containerObj, err := podman.InspectContainer(container)
if err != nil {
return "", 0, fmt.Errorf("failed to inspect entry point of container %s", container)
}
config := info["Config"].(map[string]interface{})
entryPoint := config["Cmd"].([]interface{})[0].(string)
entryPoint := containerObj.EntryPoint()
entryPointPID := containerObj.EntryPointPID()
state := info["State"].(map[string]interface{})
entryPointPID := state["Pid"]
logrus.Debugf("Entry point PID is a %T", entryPointPID)
logrus.Debugf("Entry point of container %s is %s (PID=%d)", container, entryPoint, entryPointPID)
var entryPointPIDInt int
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
return entryPoint, entryPointPID, nil
}
func isCommandPresent(container, command string) (bool, error) {

View File

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

View File

@ -18,27 +18,46 @@ package podman
import (
"encoding/json"
"time"
"github.com/containers/toolbox/pkg/utils"
)
type Container interface {
Created() string
EntryPoint() string
EntryPointPID() int
ID() string
Image() string
Labels() map[string]string
Mounts() []string
Name() string
Names() []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 {
created string
id string
image string
labels map[string]string
names []string
status string
created string
entryPoint string
entryPointPID int
id string
image string
labels map[string]string
mounts []string
names []string
status string
}
type Containers struct {
@ -46,10 +65,105 @@ type Containers struct {
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 {
return container.created
}
func (container *containerPS) EntryPoint() string {
return container.entryPoint
}
func (container *containerPS) EntryPointPID() int {
return container.entryPointPID
}
func (container *containerPS) ID() string {
return container.id
}
@ -62,6 +176,10 @@ func (container *containerPS) Labels() map[string]string {
return container.labels
}
func (container *containerPS) Mounts() []string {
return container.mounts
}
func (container *containerPS) Name() string {
return container.names[0]
}
@ -76,11 +194,14 @@ func (container *containerPS) Status() string {
func (container *containerPS) UnmarshalJSON(data []byte) error {
var raw struct {
Command []string
Created interface{}
ID string
Image string
Labels map[string]string
Mounts []string
Names interface{}
PID int
State interface{}
Status string
}
@ -89,6 +210,12 @@ func (container *containerPS) UnmarshalJSON(data []byte) error {
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
// "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
@ -106,6 +233,7 @@ func (container *containerPS) UnmarshalJSON(data []byte) error {
container.id = raw.ID
container.image = raw.Image
container.labels = raw.Labels
container.mounts = raw.Mounts
// In Podman V1 the field 'Names' held a single string but since Podman V2 the
// 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
func InspectContainer(container string) (map[string]interface{}, error) {
func InspectContainer(container string) (Container, error) {
var stdout bytes.Buffer
logLevelString := LogLevel.String()
@ -313,13 +313,12 @@ func InspectContainer(container string) (map[string]interface{}, error) {
}
output := stdout.Bytes()
var info []map[string]interface{}
if err := json.Unmarshal(output, &info); err != nil {
var containers []containerInspect
if err := json.Unmarshal(output, &containers); err != nil {
return nil, err
}
return info[0], nil
return &containers[0], nil
}
// 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) {
info, err := InspectContainer(container)
containerObj, err := InspectContainer(container)
if err != nil {
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" {
return false, fmt.Errorf("%s is not a Toolbx container", container)
}