Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/list: Make listing of containers/images more robust #503

Merged
merged 4 commits into from
Jul 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 153 additions & 59 deletions src/cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package cmd

import (
"encoding/json"
"errors"
"fmt"
"os"
Expand All @@ -28,6 +29,20 @@ import (
"github.com/spf13/cobra"
)

type toolboxImage struct {
ID string
Names []string
Created string
}

type toolboxContainer struct {
ID string
Names []string
Status string
Created string
Image string
}

var (
listFlags struct {
onlyContainers bool
Expand Down Expand Up @@ -82,8 +97,8 @@ func list(cmd *cobra.Command, args []string) error {
lsImages = false
}

var images []map[string]interface{}
var containers []map[string]interface{}
var images []toolboxImage
var containers []toolboxContainer
var err error

if lsImages {
Expand All @@ -104,7 +119,7 @@ func list(cmd *cobra.Command, args []string) error {
return nil
}

func listContainers() ([]map[string]interface{}, error) {
func listContainers() ([]toolboxContainer, error) {
logrus.Debug("Fetching containers with label=com.redhat.component=fedora-toolbox")
args := []string{"--all", "--filter", "label=com.redhat.component=fedora-toolbox"}
containers_old, err := podman.GetContainers(args...)
Expand All @@ -128,7 +143,27 @@ func listContainers() ([]map[string]interface{}, error) {
containers = utils.SortJSON(containers, "Names", false)
}

return containers, nil
// This section is a temporary solution that is here to prevent a major
// redesign of the way how toolbox containers are fetched.
// Remove this in Toolbox v0.2.0
var toolboxContainers []toolboxContainer
for _, container := range containers {
var c toolboxContainer
containerJSON, err := json.Marshal(container)
if err != nil {
logrus.Errorf("failed to marshal container: %v", err)
continue
}

err = c.UnmarshalJSON(containerJSON)
if err != nil {
logrus.Errorf("failed to unmarshal container: %v", err)
continue
}
toolboxContainers = append(toolboxContainers, c)
}

return toolboxContainers, nil
}

func listHelp(cmd *cobra.Command, args []string) {
Expand All @@ -152,7 +187,7 @@ func listHelp(cmd *cobra.Command, args []string) {
}
}

func listImages() ([]map[string]interface{}, error) {
func listImages() ([]toolboxImage, error) {
logrus.Debug("Fetching images with label=com.redhat.component=fedora-toolbox")
args := []string{"--filter", "label=com.redhat.component=fedora-toolbox"}
images_old, err := podman.GetImages(args...)
Expand All @@ -179,34 +214,39 @@ func listImages() ([]map[string]interface{}, error) {
images = utils.SortJSON(images, "names", true)
}

return images, nil
// This section is a temporary solution that is here to prevent a major
// redesign of the way how toolbox images are fetched.
// Remove this in Toolbox v0.2.0
var toolboxImages []toolboxImage
for _, image := range images {
var i toolboxImage
imageJSON, err := json.Marshal(image)
HarryMichal marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
logrus.Errorf("failed to marshal toolbox image: %v", err)
continue
}

err = i.UnmarshalJSON(imageJSON)
if err != nil {
logrus.Errorf("failed to unmarshal toolbox image: %v", err)
continue
}
toolboxImages = append(toolboxImages, i)
}

return toolboxImages, nil
}

func listOutput(images, containers []map[string]interface{}) {
func listOutput(images []toolboxImage, containers []toolboxContainer) {
if len(images) != 0 {
writer := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(writer, "%s\t%s\t%s\n", "IMAGE ID", "IMAGE NAME", "CREATED")

var idKey, nameKey, createdKey string
if podman.CheckVersion("2.0.0") {
idKey = "Id"
nameKey = "Names"
createdKey = "Created"
} else if podman.CheckVersion("1.8.3") {
idKey = "ID"
nameKey = "Names"
createdKey = "Created"
} else {
idKey = "id"
nameKey = "names"
createdKey = "created"
}

for _, image := range images {
id := utils.ShortID(image[idKey].(string))
name := image[nameKey].([]interface{})[0].(string)
created := image[createdKey].(string)
fmt.Fprintf(writer, "%s\t%s\t%s\n", id, name, created)
fmt.Fprintf(writer, "%s\t%s\t%s\n",
utils.ShortID(image.ID),
image.Names[0],
image.Created)
}

writer.Flush()
Expand All @@ -232,35 +272,10 @@ func listOutput(images, containers []map[string]interface{}) {
"IMAGE NAME",
resetColor)

var idKey, createdKey, statusKey string
if podman.CheckVersion("2.0.0") {
idKey = "Id"
createdKey = "CreatedAt"
statusKey = "State"
} else {
idKey = "ID"
createdKey = "Created"
statusKey = "Status"
}

for _, container := range containers {
id := utils.ShortID(container[idKey].(string))

var nameString string
switch name := container["Names"].(type) {
case string:
nameString = name
case []interface{}:
nameString = name[0].(string)
}

created := container[createdKey].(string)
status := container[statusKey].(string)
imageName := container["Image"].(string)

isRunning := false
if podman.CheckVersion("2.0.0") {
isRunning = status == "running"
isRunning = container.Status == "running"
}

var color string
Expand All @@ -270,17 +285,96 @@ func listOutput(images, containers []map[string]interface{}) {
color = defaultColor
}

fmt.Fprintf(writer,
"%s%s\t%s\t%s\t%s\t%s%s\n",
fmt.Fprintf(writer, "%s%s\t%s\t%s\t%s\t%s%s\n",
color,
id,
nameString,
created,
status,
imageName,
utils.ShortID(container.ID),
container.Names[0],
container.Created,
container.Status,
container.Image,
resetColor)
}

writer.Flush()
}
}

func (i *toolboxImage) UnmarshalJSON(data []byte) error {
var raw struct {
ID string
Names []string
Created interface{}
}

if err := json.Unmarshal(data, &raw); err != nil {
return err
}

i.ID = raw.ID
i.Names = raw.Names
// Until Podman 2.0.x the field 'Created' held a human-readable string in
// format "5 minutes ago". Since Podman 2.1 the field holds an integer with
// Unix time. Go interprets numbers in JSON as float64.
switch value := raw.Created.(type) {
case string:
i.Created = value
case float64:
i.Created = utils.HumanDuration(int64(value))
}

return nil
}

func (c *toolboxContainer) UnmarshalJSON(data []byte) error {
var raw struct {
ID string
Names interface{}
Status string
State interface{}
Created interface{}
Image string
}

if err := json.Unmarshal(data, &raw); err != nil {
return err
}

c.ID = raw.ID
// In Podman V1 the field 'Names' held a single string but since Podman V2 the
// field holds an array of strings
switch value := raw.Names.(type) {
case string:
c.Names = append(c.Names, value)
case []interface{}:
for _, v := range value {
c.Names = append(c.Names, v.(string))
}
}

// In Podman V1 the field holding a string about the container's state was
// called 'Status' and field 'State' held a number representing the state. In
// Podman V2 the string was moved to 'State' and field 'Status' was dropped.
switch value := raw.State.(type) {
case string:
c.Status = value
case float64:
c.Status = raw.Status
}

// 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
// previous value was moved to field 'CreatedAt'. Since we're already using
// the 'github.com/docker/go-units' library, we'll stop using the provided
// human-readable string and assemble it ourselves. Go interprets numbers in
// JSON as float64.
switch value := raw.Created.(type) {
case string:
c.Created = value
case float64:
c.Created = utils.HumanDuration(int64(value))
}
c.Image = raw.Image

return nil
}
8 changes: 1 addition & 7 deletions src/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,7 @@ func runCommand(container string,
} else if containersCount == 1 && defaultContainer {
fmt.Fprintf(os.Stderr, "Error: container %s not found\n", container)

switch value := containers[0]["Names"].(type) {
case string:
container = value
case []interface{}:
container = value[0].(string)
}

container = containers[0].Names[0]
fmt.Fprintf(os.Stderr, "Entering container %s instead.\n", container)
fmt.Fprintf(os.Stderr, "Use the 'create' command to create a different toolbox.\n")
fmt.Fprintf(os.Stderr, "Run '%s --help' for usage.\n", executableBase)
Expand Down
1 change: 1 addition & 0 deletions src/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/HarryMichal/go-version v1.0.0
github.com/acobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249
github.com/briandowns/spinner v1.10.0
github.com/docker/go-units v0.4.0
github.com/godbus/dbus/v5 v5.0.3
github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v0.0.5
Expand Down
2 changes: 2 additions & 0 deletions src/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
Expand Down
10 changes: 10 additions & 0 deletions src/pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ import (
"strconv"
"strings"
"syscall"
"time"

"github.com/acobaugh/osrelease"
"github.com/containers/toolbox/pkg/shell"
"github.com/docker/go-units"
"github.com/godbus/dbus/v5"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
Expand Down Expand Up @@ -333,6 +335,14 @@ func GetMountOptions(target string) (string, error) {
return mountOptions, nil
}

// HumanDuration accepts a Unix time value and converts it into a human readable
// string.
//
// Examples: "5 minutes ago", "2 hours ago", "3 days ago"
func HumanDuration(duration int64) string {
return units.HumanDuration(time.Since(time.Unix(duration, 0))) + " ago"
}

// ImageReferenceCanBeID checks if 'image' might be the ID of an image
func ImageReferenceCanBeID(image string) (bool, error) {
matched, err := regexp.MatchString("^[a-f0-9]\\{6,64\\}$", image)
Expand Down