summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Dockerfile.CentOS2
-rw-r--r--Dockerfile.Fedora2
-rw-r--r--cmd/podman/cliconfig/config.go5
-rw-r--r--cmd/podman/common.go2
-rw-r--r--cmd/podman/container.go19
-rw-r--r--cmd/podman/image.go19
-rw-r--r--cmd/podman/inspect.go38
-rw-r--r--cmd/podman/main.go2
-rw-r--r--cmd/podman/tree.go190
-rwxr-xr-xcontrib/cirrus/setup_environment.sh1
-rw-r--r--contrib/gate/Dockerfile2
-rw-r--r--docs/podman-attach.1.md7
-rw-r--r--docs/podman-build.1.md14
-rw-r--r--docs/podman-create.1.md4
-rw-r--r--docs/podman-image-tree.1.md88
-rw-r--r--docs/podman-image.1.md1
-rw-r--r--docs/podman-inspect.1.md11
-rw-r--r--docs/podman-run.1.md2
-rw-r--r--docs/podman-start.1.md3
-rw-r--r--docs/tutorials/podman_tutorial.md2
-rw-r--r--install.md2
-rw-r--r--libpod/container_internal.go12
-rw-r--r--libpod/events.go4
-rw-r--r--libpod/image/image.go67
-rw-r--r--libpod/networking_linux.go30
-rw-r--r--libpod/runtime.go156
-rw-r--r--test/e2e/commit_test.go2
-rw-r--r--test/e2e/info_test.go2
-rw-r--r--test/e2e/tree_test.go64
-rw-r--r--troubleshooting.md2
-rw-r--r--utils/utils.go2
31 files changed, 622 insertions, 135 deletions
diff --git a/Dockerfile.CentOS b/Dockerfile.CentOS
index be4ae3eaf..605dc9df4 100644
--- a/Dockerfile.CentOS
+++ b/Dockerfile.CentOS
@@ -15,7 +15,7 @@ RUN yum -y install btrfs-progs-devel \
libassuan-devel \
libseccomp-devel \
libselinux-devel \
- skopeo-containers \
+ containers-common \
runc \
make \
ostree-devel \
diff --git a/Dockerfile.Fedora b/Dockerfile.Fedora
index aeee9c3cf..e38e2e056 100644
--- a/Dockerfile.Fedora
+++ b/Dockerfile.Fedora
@@ -16,7 +16,7 @@ RUN dnf -y install btrfs-progs-devel \
libassuan-devel \
libseccomp-devel \
libselinux-devel \
- skopeo-containers \
+ containers-common \
runc \
make \
ostree-devel \
diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go
index ec08eedb5..cb9d9a338 100644
--- a/cmd/podman/cliconfig/config.go
+++ b/cmd/podman/cliconfig/config.go
@@ -66,6 +66,11 @@ type TagValues struct {
PodmanCommand
}
+type TreeValues struct {
+ PodmanCommand
+ WhatRequires bool
+}
+
type WaitValues struct {
PodmanCommand
Interval uint
diff --git a/cmd/podman/common.go b/cmd/podman/common.go
index 1d96f40c8..43dfdb837 100644
--- a/cmd/podman/common.go
+++ b/cmd/podman/common.go
@@ -226,7 +226,7 @@ func getCreateFlags(c *cliconfig.PodmanCommand) {
)
createFlags.String(
"detach-keys", "",
- "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`",
+ "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`",
)
createFlags.StringSlice(
"device", []string{},
diff --git a/cmd/podman/container.go b/cmd/podman/container.go
index 8ad8d7a44..ce6ad8883 100644
--- a/cmd/podman/container.go
+++ b/cmd/podman/container.go
@@ -19,6 +19,20 @@ var (
},
}
+ contInspectSubCommand cliconfig.InspectValues
+ _contInspectSubCommand = &cobra.Command{
+ Use: strings.Replace(_inspectCommand.Use, "| IMAGE", "", 1),
+ Short: "Display the configuration of a container",
+ Long: `Displays the low-level information on a container identified by name or ID.`,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ contInspectSubCommand.InputArgs = args
+ contInspectSubCommand.GlobalFlags = MainGlobalOpts
+ return inspectCmd(&contInspectSubCommand)
+ },
+ Example: `podman container inspect myCtr
+ podman container inspect -l --format '{{.Id}} {{.Config.Labels}}'`,
+ }
+
listSubCommand cliconfig.PsValues
_listSubCommand = &cobra.Command{
Use: strings.Replace(_psCommand.Use, "ps", "list", 1),
@@ -37,12 +51,15 @@ var (
// Commands that are universally implemented.
containerCommands = []*cobra.Command{
_containerExistsCommand,
- _inspectCommand,
+ _contInspectSubCommand,
_listSubCommand,
}
)
func init() {
+ contInspectSubCommand.Command = _contInspectSubCommand
+ inspectInit(&contInspectSubCommand)
+
listSubCommand.Command = _listSubCommand
psInit(&listSubCommand)
diff --git a/cmd/podman/image.go b/cmd/podman/image.go
index 52bac6ecb..66c141686 100644
--- a/cmd/podman/image.go
+++ b/cmd/podman/image.go
@@ -31,6 +31,19 @@ var (
Example: strings.Replace(_imagesCommand.Example, "podman images", "podman image list", -1),
}
+ inspectSubCommand cliconfig.InspectValues
+ _inspectSubCommand = &cobra.Command{
+ Use: strings.Replace(_inspectCommand.Use, "CONTAINER | ", "", 1),
+ Short: "Display the configuration of an image",
+ Long: `Displays the low-level information on an image identified by name or ID.`,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ inspectSubCommand.InputArgs = args
+ inspectSubCommand.GlobalFlags = MainGlobalOpts
+ return inspectCmd(&inspectSubCommand)
+ },
+ Example: `podman image inspect alpine`,
+ }
+
rmSubCommand cliconfig.RmiValues
_rmSubCommand = &cobra.Command{
Use: strings.Replace(_rmiCommand.Use, "rmi", "rm", 1),
@@ -52,7 +65,7 @@ var imageSubCommands = []*cobra.Command{
_imagesSubCommand,
_imageExistsCommand,
_importCommand,
- _inspectCommand,
+ _inspectSubCommand,
_loadCommand,
_pruneImagesCommand,
_pullCommand,
@@ -60,6 +73,7 @@ var imageSubCommands = []*cobra.Command{
_rmSubCommand,
_saveCommand,
_tagCommand,
+ _treeCommand,
}
func init() {
@@ -69,6 +83,9 @@ func init() {
imagesSubCommand.Command = _imagesSubCommand
imagesInit(&imagesSubCommand)
+ inspectSubCommand.Command = _inspectSubCommand
+ inspectInit(&inspectSubCommand)
+
imageCommand.SetUsageTemplate(UsageTemplate())
imageCommand.AddCommand(imageSubCommands...)
imageCommand.AddCommand(getImageSubCommands()...)
diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go
index e14f25c24..3d6fd07e0 100644
--- a/cmd/podman/inspect.go
+++ b/cmd/podman/inspect.go
@@ -27,7 +27,7 @@ var (
inspectDescription = `This displays the low-level information on containers and images identified by name or ID.
If given a name that matches both a container and an image, this command inspects the container. By default, this will render all results in a JSON array.`
- _inspectCommand = &cobra.Command{
+ _inspectCommand = cobra.Command{
Use: "inspect [flags] CONTAINER | IMAGE",
Short: "Display the configuration of a container or image",
Long: inspectDescription,
@@ -42,16 +42,34 @@ var (
}
)
+func inspectInit(command *cliconfig.InspectValues) {
+ command.SetHelpTemplate(HelpTemplate())
+ command.SetUsageTemplate(UsageTemplate())
+ flags := command.Flags()
+ flags.StringVarP(&command.Format, "format", "f", "", "Change the output format to a Go template")
+
+ // -t flag applicable only to 'podman inspect', not 'image/container inspect'
+ ambiguous := strings.Contains(command.Use, "|")
+ if ambiguous {
+ flags.StringVarP(&command.TypeObject, "type", "t", inspectAll, "Return JSON for specified type, (image or container)")
+ }
+
+ if strings.Contains(command.Use, "CONTAINER") {
+ containers_only := " (containers only)"
+ if !ambiguous {
+ containers_only = ""
+ command.TypeObject = inspectTypeContainer
+ }
+ flags.BoolVarP(&command.Latest, "latest", "l", false, "Act on the latest container podman is aware of"+containers_only)
+ flags.BoolVarP(&command.Size, "size", "s", false, "Display total file size"+containers_only)
+ markFlagHiddenForRemoteClient("latest", flags)
+ } else {
+ command.TypeObject = inspectTypeImage
+ }
+}
func init() {
- inspectCommand.Command = _inspectCommand
- inspectCommand.SetHelpTemplate(HelpTemplate())
- inspectCommand.SetUsageTemplate(UsageTemplate())
- flags := inspectCommand.Flags()
- flags.StringVarP(&inspectCommand.TypeObject, "type", "t", inspectAll, "Return JSON for specified type, (e.g image, container or task)")
- flags.StringVarP(&inspectCommand.Format, "format", "f", "", "Change the output format to a Go template")
- flags.BoolVarP(&inspectCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of if the type is a container")
- flags.BoolVarP(&inspectCommand.Size, "size", "s", false, "Display total file size if the type is container")
- markFlagHiddenForRemoteClient("latest", flags)
+ inspectCommand.Command = &_inspectCommand
+ inspectInit(&inspectCommand)
}
func inspectCmd(c *cliconfig.InspectValues) error {
diff --git a/cmd/podman/main.go b/cmd/podman/main.go
index 5e8b9745a..1717e0624 100644
--- a/cmd/podman/main.go
+++ b/cmd/podman/main.go
@@ -42,7 +42,7 @@ var mainCommands = []*cobra.Command{
&_imagesCommand,
_importCommand,
_infoCommand,
- _inspectCommand,
+ &_inspectCommand,
_killCommand,
_loadCommand,
podCommand.Command,
diff --git a/cmd/podman/tree.go b/cmd/podman/tree.go
new file mode 100644
index 000000000..ebda18cdb
--- /dev/null
+++ b/cmd/podman/tree.go
@@ -0,0 +1,190 @@
+package main
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/cliconfig"
+ "github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/libpod/image"
+ units "github.com/docker/go-units"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+const (
+ middleItem = "├── "
+ continueItem = "│ "
+ lastItem = "└── "
+)
+
+var (
+ treeCommand cliconfig.TreeValues
+
+ treeDescription = "Prints layer hierarchy of an image in a tree format"
+ _treeCommand = &cobra.Command{
+ Use: "tree",
+ Short: treeDescription,
+ Long: treeDescription,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ treeCommand.InputArgs = args
+ treeCommand.GlobalFlags = MainGlobalOpts
+ return treeCmd(&treeCommand)
+ },
+ Example: "podman image tree alpine:latest",
+ }
+)
+
+func init() {
+ treeCommand.Command = _treeCommand
+ treeCommand.SetUsageTemplate(UsageTemplate())
+ treeCommand.Flags().BoolVar(&treeCommand.WhatRequires, "whatrequires", false, "Show all child images and layers of the specified image")
+}
+
+// infoImage keep information of Image along with all associated layers
+type infoImage struct {
+ // id of image
+ id string
+ // tags of image
+ tags []string
+ // layers stores all layers of image.
+ layers []image.LayerInfo
+}
+
+func treeCmd(c *cliconfig.TreeValues) error {
+ args := c.InputArgs
+ if len(args) == 0 {
+ return errors.Errorf("an image name must be specified")
+ }
+ if len(args) > 1 {
+ return errors.Errorf("you must provide at most 1 argument")
+ }
+
+ runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand)
+ if err != nil {
+ return errors.Wrapf(err, "error creating libpod runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ img, err := runtime.ImageRuntime().NewFromLocal(args[0])
+ if err != nil {
+ return err
+ }
+
+ // Fetch map of image-layers, which is used for printing output.
+ layerInfoMap, err := image.GetLayersMapWithImageInfo(runtime.ImageRuntime())
+ if err != nil {
+ return errors.Wrapf(err, "error while retriving layers of image %q", img.InputName)
+ }
+
+ // Create an imageInfo and fill the image and layer info
+ imageInfo := &infoImage{
+ id: img.ID(),
+ tags: img.Names(),
+ }
+
+ size, err := img.Size(context.Background())
+ if err != nil {
+ return errors.Wrapf(err, "error while retriving image size")
+ }
+ fmt.Printf("Image ID: %s\n", imageInfo.id[:12])
+ fmt.Printf("Tags:\t %s\n", imageInfo.tags)
+ fmt.Printf("Size:\t %v\n", units.HumanSizeWithPrecision(float64(*size), 4))
+ fmt.Printf(fmt.Sprintf("Image Layers\n"))
+
+ if !c.WhatRequires {
+ // fill imageInfo with layers associated with image.
+ // the layers will be filled such that
+ // (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End)
+ err := buildImageHierarchyMap(imageInfo, layerInfoMap, img.TopLayer())
+ if err != nil {
+ return err
+ }
+ // Build output from imageInfo into buffer
+ printImageHierarchy(imageInfo)
+
+ } else {
+ // fill imageInfo with layers associated with image.
+ // the layers will be filled such that
+ // (Start)TopLayer->...intermediate Child Layer(s)-> Child TopLayer(End)
+ // (Forks)... intermediate Child Layer(s) -> Child Top Layer(End)
+ err := printImageChildren(layerInfoMap, img.TopLayer(), "", true)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// Stores hierarchy of images such that all parent layers using which image is built are stored in imageInfo
+// Layers are added such that (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End)
+func buildImageHierarchyMap(imageInfo *infoImage, layerMap map[string]*image.LayerInfo, layerID string) error {
+ if layerID == "" {
+ return nil
+ }
+ ll, ok := layerMap[layerID]
+ if !ok {
+ return fmt.Errorf("lookup error: layerid %s not found", layerID)
+ }
+ if err := buildImageHierarchyMap(imageInfo, layerMap, ll.ParentID); err != nil {
+ return err
+ }
+
+ imageInfo.layers = append(imageInfo.layers, *ll)
+ return nil
+}
+
+// Stores all children layers which are created using given Image.
+// Layers are stored as follows
+// (Start)TopLayer->...intermediate Child Layer(s)-> Child TopLayer(End)
+// (Forks)... intermediate Child Layer(s) -> Child Top Layer(End)
+func printImageChildren(layerMap map[string]*image.LayerInfo, layerID string, prefix string, last bool) error {
+ if layerID == "" {
+ return nil
+ }
+ ll, ok := layerMap[layerID]
+ if !ok {
+ return fmt.Errorf("lookup error: layerid %s, not found", layerID)
+ }
+ fmt.Printf(prefix)
+
+ //initialize intend with middleItem to reduce middleItem checks.
+ intend := middleItem
+ if !last {
+ // add continueItem i.e. '|' for next iteration prefix
+ prefix = prefix + continueItem
+ } else if len(ll.ChildID) > 1 || len(ll.ChildID) == 0 {
+ // The above condition ensure, alignment happens for node, which has more then 1 childern.
+ // If node is last in printing hierarchy, it should not be printed as middleItem i.e. ├──
+ intend = lastItem
+ prefix = prefix + " "
+ }
+
+ var tags string
+ if len(ll.RepoTags) > 0 {
+ tags = fmt.Sprintf(" Top Layer of: %s", ll.RepoTags)
+ }
+ fmt.Printf("%sID: %s Size: %7v%s\n", intend, ll.ID[:12], units.HumanSizeWithPrecision(float64(ll.Size), 4), tags)
+ for count, childID := range ll.ChildID {
+ if err := printImageChildren(layerMap, childID, prefix, (count == len(ll.ChildID)-1)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// prints the layers info of image
+func printImageHierarchy(imageInfo *infoImage) {
+ for count, l := range imageInfo.layers {
+ var tags string
+ intend := middleItem
+ if len(l.RepoTags) > 0 {
+ tags = fmt.Sprintf(" Top Layer of: %s", l.RepoTags)
+ }
+ if count == len(imageInfo.layers)-1 {
+ intend = lastItem
+ }
+ fmt.Printf("%s ID: %s Size: %7v%s\n", intend, l.ID[:12], units.HumanSizeWithPrecision(float64(l.Size), 4), tags)
+ }
+}
diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh
index d8d97904b..ead2f7343 100755
--- a/contrib/cirrus/setup_environment.sh
+++ b/contrib/cirrus/setup_environment.sh
@@ -66,6 +66,7 @@ then
RUNC="https://kojipkgs.fedoraproject.org/packages/runc/1.0.0/55.dev.git578fe65.fc${OS_RELEASE_VER}/x86_64/runc-1.0.0-55.dev.git578fe65.fc${OS_RELEASE_VER}.x86_64.rpm"
echo ">>>>> OVERRIDING RUNC WITH $RUNC <<<<<"
dnf -y install "$RUNC"
+ dnf -y upgrade slirp4netns
;& # Continue to the next item
centos-7) ;&
rhel-7)
diff --git a/contrib/gate/Dockerfile b/contrib/gate/Dockerfile
index 16d5eda67..e44c2fd4f 100644
--- a/contrib/gate/Dockerfile
+++ b/contrib/gate/Dockerfile
@@ -31,7 +31,7 @@ RUN dnf -y install \
python3-pytoml \
python3-pyyaml \
python3-varlink \
- skopeo-containers \
+ containers-common \
slirp4netns \
rsync \
which \
diff --git a/docs/podman-attach.1.md b/docs/podman-attach.1.md
index ceb945a0e..11cecc16c 100644
--- a/docs/podman-attach.1.md
+++ b/docs/podman-attach.1.md
@@ -11,13 +11,12 @@ The attach command allows you to attach to a running container using the contain
or name, either to view its ongoing output or to control it interactively.
You can detach from the container (and leave it running) using a configurable key sequence. The default
-sequence is CTRL-p CTRL-q. You configure the key sequence using the --detach-keys option
+sequence is `ctrl-p,ctrl-q`. You configure the key sequence using the --detach-keys option
## OPTIONS
-**--detach-keys**
+**--detach-keys**=""
-Override the key sequence for detaching a container. Format is a single character [a-Z] or
-ctrl-[value] where [value] is one of: a-z, @, ^, [, , or _.
+Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`.
**--latest, -l**
diff --git a/docs/podman-build.1.md b/docs/podman-build.1.md
index fdae48b93..3d51a5319 100644
--- a/docs/podman-build.1.md
+++ b/docs/podman-build.1.md
@@ -209,7 +209,7 @@ Write the image ID to the file.
Sets the configuration for IPC namespaces when handling `RUN` instructions.
The configured value can be "" (the empty string) or "container" to indicate
that a new IPC namespace should be created, or it can be "host" to indicate
-that the IPC namespace in which `buildah` itself is being run should be reused,
+that the IPC namespace in which `podman` itself is being run should be reused,
or it can be the path to an IPC namespace which is already in use by
another process.
@@ -269,7 +269,7 @@ unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap.
Sets the configuration for network namespaces when handling `RUN` instructions.
The configured value can be "" (the empty string) or "container" to indicate
that a new network namespace should be created, or it can be "host" to indicate
-that the network namespace in which `buildah` itself is being run should be
+that the network namespace in which `podman` itself is being run should be
reused, or it can be the path to a network namespace which is already in use by
another process.
@@ -282,7 +282,7 @@ Do not use existing cached images for the container build. Build from the start
Sets the configuration for PID namespaces when handling `RUN` instructions.
The configured value can be "" (the empty string) or "container" to indicate
that a new PID namespace should be created, or it can be "host" to indicate
-that the PID namespace in which `buildah` itself is being run should be reused,
+that the PID namespace in which `podman` itself is being run should be reused,
or it can be the path to a PID namespace which is already in use by another
process.
@@ -398,7 +398,7 @@ include:
Sets the configuration for user namespaces when handling `RUN` instructions.
The configured value can be "" (the empty string) or "container" to indicate
that a new user namespace should be created, it can be "host" to indicate that
-the user namespace in which `buildah` itself is being run should be reused, or
+the user namespace in which `podman` itself is being run should be reused, or
it can be the path to an user namespace which is already in use by another
process.
@@ -452,7 +452,7 @@ in the `/etc/subuid` file which correspond to the specified user.
Commands run when handling `RUN` instructions will default to being run in
their own user namespaces, configured using the UID and GID maps.
If --userns-gid-map-group is specified, but --userns-uid-map-user is not
-specified, `buildah` will assume that the specified group name is also a
+specified, `podman` will assume that the specified group name is also a
suitable user name to use as the default setting for this option.
**--userns-gid-map-group** *group*
@@ -463,7 +463,7 @@ in the `/etc/subgid` file which correspond to the specified group.
Commands run when handling `RUN` instructions will default to being run in
their own user namespaces, configured using the UID and GID maps.
If --userns-uid-map-user is specified, but --userns-gid-map-group is not
-specified, `buildah` will assume that the specified user name is also a
+specified, `podman` will assume that the specified user name is also a
suitable group name to use as the default setting for this option.
**--uts** *how*
@@ -471,7 +471,7 @@ suitable group name to use as the default setting for this option.
Sets the configuration for UTS namespaces when the handling `RUN` instructions.
The configured value can be "" (the empty string) or "container" to indicate
that a new UTS namespace should be created, or it can be "host" to indicate
-that the UTS namespace in which `buildah` itself is being run should be reused,
+that the UTS namespace in which `podman` itself is being run should be reused,
or it can be the path to a UTS namespace which is already in use by another
process.
diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md
index b4f097348..2e176db76 100644
--- a/docs/podman-create.1.md
+++ b/docs/podman-create.1.md
@@ -167,13 +167,13 @@ the other shell to view a list of the running containers. You can reattach to a
detached container with **podman attach**.
When attached in the tty mode, you can detach from the container (and leave it
-running) using a configurable key sequence. The default sequence is `CTRL-p CTRL-q`.
+running) using a configurable key sequence. The default sequence is `ctrl-p,ctrl-q`.
You configure the key sequence using the **--detach-keys** option or a configuration file.
See **config-json(5)** for documentation on using a configuration file.
**--detach-keys**=""
-Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
+Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`.
**--device**=[]
diff --git a/docs/podman-image-tree.1.md b/docs/podman-image-tree.1.md
new file mode 100644
index 000000000..014499d6a
--- /dev/null
+++ b/docs/podman-image-tree.1.md
@@ -0,0 +1,88 @@
+% podman-image-tree(1)
+
+## NAME
+podman\-image\-tree - Prints layer hierarchy of an image in a tree format
+
+## SYNOPSIS
+**podman image tree** [*image*:*tag*]**|**[*image-id*]
+[**--help**|**-h**]
+
+## DESCRIPTION
+Prints layer hierarchy of an image in a tree format.
+If you do not provide a *tag*, podman will default to `latest` for the *image*.
+Layers are indicated with image tags as `Top Layer of`, when the tag is known locally.
+## OPTIONS
+
+**--help**, **-h**
+
+Print usage statement
+
+**--whatrequires**
+
+Show all child images and layers of the specified image
+
+## EXAMPLES
+
+```
+$ podman pull docker.io/library/wordpress
+$ podman pull docker.io/library/php:7.2-apache
+
+$ podman image tree docker.io/library/wordpress
+Image ID: 6e880d17852f
+Tags: [docker.io/library/wordpress:latest]
+Size: 429.9MB
+Image Layers
+├── ID: 3c816b4ead84 Size: 58.47MB
+├── ID: e39dad2af72e Size: 3.584kB
+├── ID: b2d6a702383c Size: 213.6MB
+├── ID: 94609408badd Size: 3.584kB
+├── ID: f4dddbf86725 Size: 43.04MB
+├── ID: 8f695df43a4c Size: 11.78kB
+├── ID: c29d67bf8461 Size: 9.728kB
+├── ID: 23f4315918f8 Size: 7.68kB
+├── ID: d082f93a18b3 Size: 13.51MB
+├── ID: 7ea8bedcac69 Size: 4.096kB
+├── ID: dc3bbf7b3dc0 Size: 57.53MB
+├── ID: fdbbc6404531 Size: 11.78kB
+├── ID: 8d24785437c6 Size: 4.608kB
+├── ID: 80715f9e8880 Size: 4.608kB Top Layer of: [docker.io/library/php:7.2-apache]
+├── ID: c93cbcd6437e Size: 3.573MB
+├── ID: dece674f3cd1 Size: 4.608kB
+├── ID: 834f4497afda Size: 7.168kB
+├── ID: bfe2ce1263f8 Size: 40.06MB
+└── ID: 748e99b214cf Size: 11.78kB Top Layer of: [docker.io/library/wordpress:latest]
+
+$ podman pull docker.io/circleci/ruby:latest
+$ podman pull docker.io/library/ruby:latest
+
+$ podman image tree ae96a4ad4f3f --whatrequires
+Image ID: ae96a4ad4f3f
+Tags: [docker.io/library/ruby:latest]
+Size: 894.2MB
+Image Layers
+└── ID: 9c92106221c7 Size: 2.56kB Top Layer of: [docker.io/library/ruby:latest]
+ ├── ID: 1b90f2b80ba0 Size: 3.584kB
+ │ ├── ID: 42b7d43ae61c Size: 169.5MB
+ │ ├── ID: 26dc8ba99ec3 Size: 2.048kB
+ │ ├── ID: b4f822db8d95 Size: 3.957MB
+ │ ├── ID: 044e9616ef8a Size: 164.7MB
+ │ ├── ID: bf94b940200d Size: 11.75MB
+ │ ├── ID: 4938e71bfb3b Size: 8.532MB
+ │ └── ID: f513034bf553 Size: 1.141MB
+ ├── ID: 1e55901c3ea9 Size: 3.584kB
+ ├── ID: b62835a63f51 Size: 169.5MB
+ ├── ID: 9f4e8857f3fd Size: 2.048kB
+ ├── ID: c3b392020e8f Size: 3.957MB
+ ├── ID: 880163026a0a Size: 164.8MB
+ ├── ID: 8c78b2b14643 Size: 11.75MB
+ ├── ID: 830370cfa182 Size: 8.532MB
+ └── ID: 567fd7b7bd38 Size: 1.141MB Top Layer of: [docker.io/circleci/ruby:latest]
+
+```
+
+
+## SEE ALSO
+podman(1), crio(8)
+
+## HISTORY
+Feb 2019, Originally compiled by Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp>
diff --git a/docs/podman-image.1.md b/docs/podman-image.1.md
index b4ae752f6..54960045f 100644
--- a/docs/podman-image.1.md
+++ b/docs/podman-image.1.md
@@ -28,6 +28,7 @@ The image command allows you to manage images
| sign | [podman-image-sign(1)](podman-image-sign.1.md) | Sign an image. |
| tag | [podman-tag(1)](podman-tag.1.md) | Add an additional name to a local image. |
| trust | [podman-image-trust(1)](podman-image-trust.1.md)| Manage container image trust policy. |
+| tree | [podman-image-tree(1)](podman-image-tree.1.md) | Prints layer hierarchy of an image in a tree format |
## SEE ALSO
podman
diff --git a/docs/podman-inspect.1.md b/docs/podman-inspect.1.md
index 5748f29f4..712891ad6 100644
--- a/docs/podman-inspect.1.md
+++ b/docs/podman-inspect.1.md
@@ -6,6 +6,10 @@ podman\-inspect - Display a container or image's configuration
## SYNOPSIS
**podman inspect** [*options*] *name* ...
+**podman image inspect** [*options*] *image*
+
+**podman container inspect** [*options*] *container*
+
## DESCRIPTION
This displays the low-level information on containers and images identified by name or ID. By default, this will render
all results in a JSON array. If the container and image have the same name, this will return container JSON for
@@ -16,6 +20,7 @@ unspecified type. If a format is specified, the given template will be executed
**--type, t="TYPE"**
Return JSON for the specified type. Type can be 'container', 'image' or 'all' (default: all)
+(Only meaningful when invoked as *podman inspect*)
**--format, -f="FORMAT"**
@@ -27,7 +32,7 @@ The keys of the returned JSON can be used as the values for the --format flag (s
Instead of providing the container name or ID, use the last created container. If you use methods other than Podman
to run containers such as CRI-O, the last started container could be from either of those methods.
-The latest option is not supported on the remote client.
+The latest option is not supported on the remote client or when invoked as *podman image inspect*.
**--size, -s**
@@ -94,12 +99,12 @@ overlay
```
```
-# podman inspect --format "size: {{.Size}}" alpine
+# podman image inspect --format "size: {{.Size}}" alpine
size: 4405240
```
```
-podman inspect --latest --format {{.EffectiveCaps}}
+podman container inspect --latest --format {{.EffectiveCaps}}
[CAP_CHOWN CAP_DAC_OVERRIDE CAP_FSETID CAP_FOWNER CAP_MKNOD CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SETFCAP CAP_SETPCAP CAP_NET_BIND_SERVICE CAP_SYS_CHROOT CAP_KILL CAP_AUDIT_WRITE]
```
diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md
index 1eb866773..fe98e43ca 100644
--- a/docs/podman-run.1.md
+++ b/docs/podman-run.1.md
@@ -173,7 +173,7 @@ the other shell to view a list of the running containers. You can reattach to a
detached container with **podman attach**.
When attached in the tty mode, you can detach from the container (and leave it
-running) using a configurable key sequence. The default sequence is `CTRL-p CTRL-q`.
+running) using a configurable key sequence. The default sequence is `ctrl-p,ctrl-q`.
You configure the key sequence using the **--detach-keys** option or a configuration file.
See **config-json(5)** for documentation on using a configuration file.
diff --git a/docs/podman-start.1.md b/docs/podman-start.1.md
index b0167003e..aa5362046 100644
--- a/docs/podman-start.1.md
+++ b/docs/podman-start.1.md
@@ -21,8 +21,7 @@ starting multiple containers.
**--detach-keys**
-Override the key sequence for detaching a container. Format is a single character [a-Z] or
-ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _.
+Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`.
**--interactive, -i**
diff --git a/docs/tutorials/podman_tutorial.md b/docs/tutorials/podman_tutorial.md
index 5017e61cd..bfff90016 100644
--- a/docs/tutorials/podman_tutorial.md
+++ b/docs/tutorials/podman_tutorial.md
@@ -34,7 +34,7 @@ acquire the source, and build it.
```console
sudo dnf install -y git runc libassuan-devel golang golang-github-cpuguy83-go-md2man glibc-static \
gpgme-devel glib2-devel device-mapper-devel libseccomp-devel \
- atomic-registries iptables skopeo-containers containernetworking-cni \
+ atomic-registries iptables containers-common containernetworking-cni \
conmon ostree-devel
```
### Building and installing podman
diff --git a/install.md b/install.md
index eba1ce8ea..071eeff67 100644
--- a/install.md
+++ b/install.md
@@ -107,7 +107,7 @@ yum install -y \
ostree-devel \
pkgconfig \
runc \
- skopeo-containers
+ containers-common
```
Debian, Ubuntu, and related distributions:
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index bea7acd69..872802016 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -337,11 +337,13 @@ func (c *Container) setupStorage(ctx context.Context) error {
}
// Set the default Entrypoint and Command
- if c.config.Entrypoint == nil {
- c.config.Entrypoint = containerInfo.Config.Config.Entrypoint
- }
- if c.config.Command == nil {
- c.config.Command = containerInfo.Config.Config.Cmd
+ if containerInfo.Config != nil {
+ if c.config.Entrypoint == nil {
+ c.config.Entrypoint = containerInfo.Config.Config.Entrypoint
+ }
+ if c.config.Command == nil {
+ c.config.Command = containerInfo.Config.Config.Cmd
+ }
}
artifacts := filepath.Join(c.config.StaticDir, artifactsDir)
diff --git a/libpod/events.go b/libpod/events.go
index 879aeb6c5..f09529a05 100644
--- a/libpod/events.go
+++ b/libpod/events.go
@@ -1,6 +1,8 @@
package libpod
import (
+ "os"
+
"github.com/containers/libpod/libpod/events"
"github.com/hpcloud/tail"
"github.com/pkg/errors"
@@ -85,7 +87,7 @@ func (r *Runtime) Events(fromStart, stream bool, options []events.EventFilter, e
func (r *Runtime) getTail(fromStart, stream bool) (*tail.Tail, error) {
reopen := true
- seek := tail.SeekInfo{Offset: 0, Whence: 2}
+ seek := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END}
if fromStart || !stream {
seek.Whence = 0
reopen = false
diff --git a/libpod/image/image.go b/libpod/image/image.go
index 72f07dad1..c5939e055 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -1212,3 +1212,70 @@ func (i *Image) newImageEvent(status events.Status) {
logrus.Infof("unable to write event to %s", i.imageruntime.EventsLogFilePath)
}
}
+
+// LayerInfo keeps information of single layer
+type LayerInfo struct {
+ // Layer ID
+ ID string
+ // Parent ID of current layer.
+ ParentID string
+ // ChildID of current layer.
+ // there can be multiple children in case of fork
+ ChildID []string
+ // RepoTag will have image repo names, if layer is top layer of image
+ RepoTags []string
+ // Size stores Uncompressed size of layer.
+ Size int64
+}
+
+// GetLayersMapWithImageInfo returns map of image-layers, with associated information like RepoTags, parent and list of child layers.
+func GetLayersMapWithImageInfo(imageruntime *Runtime) (map[string]*LayerInfo, error) {
+
+ // Memory allocated to store map of layers with key LayerID.
+ // Map will build dependency chain with ParentID and ChildID(s)
+ layerInfoMap := make(map[string]*LayerInfo)
+
+ // scan all layers & fill size and parent id for each layer in layerInfoMap
+ layers, err := imageruntime.store.Layers()
+ if err != nil {
+ return nil, err
+ }
+ for _, layer := range layers {
+ _, ok := layerInfoMap[layer.ID]
+ if !ok {
+ layerInfoMap[layer.ID] = &LayerInfo{
+ ID: layer.ID,
+ Size: layer.UncompressedSize,
+ ParentID: layer.Parent,
+ }
+ } else {
+ return nil, fmt.Errorf("detected multiple layers with the same ID %q", layer.ID)
+ }
+ }
+
+ // scan all layers & add all childs for each layers to layerInfo
+ for _, layer := range layers {
+ _, ok := layerInfoMap[layer.ID]
+ if ok {
+ if layer.Parent != "" {
+ layerInfoMap[layer.Parent].ChildID = append(layerInfoMap[layer.Parent].ChildID, layer.ID)
+ }
+ } else {
+ return nil, fmt.Errorf("lookup error: layer-id %s, not found", layer.ID)
+ }
+ }
+
+ // Add the Repo Tags to Top layer of each image.
+ imgs, err := imageruntime.store.Images()
+ if err != nil {
+ return nil, err
+ }
+ for _, img := range imgs {
+ e, ok := layerInfoMap[img.TopLayer]
+ if !ok {
+ return nil, fmt.Errorf("top-layer for image %s not found local store", img.ID)
+ }
+ e.RepoTags = append(e.RepoTags, img.Names...)
+ }
+ return layerInfoMap, nil
+}
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index 80d7d8213..d8b0cffcb 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -134,6 +134,15 @@ type slirp4netnsCmd struct {
Args slirp4netnsCmdArg `json:"arguments"`
}
+func checkSlirpFlags(path string) (bool, bool, error) {
+ cmd := exec.Command(path, "--help")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return false, false, err
+ }
+ return strings.Contains(string(out), "--disable-host-loopback"), strings.Contains(string(out), "--mtu"), nil
+}
+
// Configure the network namespace for a rootless container
func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
defer ctr.rootlessSlirpSyncR.Close()
@@ -159,13 +168,24 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
havePortMapping := len(ctr.Config().PortMappings) > 0
apiSocket := filepath.Join(r.ociRuntime.tmpDir, fmt.Sprintf("%s.net", ctr.config.ID))
- var cmd *exec.Cmd
+
+ cmdArgs := []string{}
if havePortMapping {
- // if we need ports to be mapped from the host, create a API socket to use for communicating with slirp4netns.
- cmd = exec.Command(path, "-c", "-e", "3", "-r", "4", "--api-socket", apiSocket, fmt.Sprintf("%d", ctr.state.PID), "tap0")
- } else {
- cmd = exec.Command(path, "-c", "-e", "3", "-r", "4", fmt.Sprintf("%d", ctr.state.PID), "tap0")
+ cmdArgs = append(cmdArgs, "--api-socket", apiSocket, fmt.Sprintf("%d", ctr.state.PID))
}
+ dhp, mtu, err := checkSlirpFlags(path)
+ if err != nil {
+ return errors.Wrapf(err, "error checking slirp4netns binary %s", path)
+ }
+ if dhp {
+ cmdArgs = append(cmdArgs, "--disable-host-loopback")
+ }
+ if mtu {
+ cmdArgs = append(cmdArgs, "--mtu", "65520")
+ }
+ cmdArgs = append(cmdArgs, "-c", "-e", "3", "-r", "4", fmt.Sprintf("%d", ctr.state.PID), "tap0")
+
+ cmd := exec.Command(path, cmdArgs...)
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
diff --git a/libpod/runtime.go b/libpod/runtime.go
index fa208a2ca..9836b7aab 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -241,6 +241,12 @@ type runtimeConfiguredFrom struct {
libpodStaticDirSet bool
libpodTmpDirSet bool
volPathSet bool
+ conmonPath bool
+ conmonEnvVars bool
+ ociRuntimes bool
+ runtimePath bool
+ cniPluginDir bool
+ noPivotRoot bool
}
var (
@@ -324,6 +330,22 @@ func SetXdgRuntimeDir(val string) error {
// NewRuntime creates a new container runtime
// Options can be passed to override the default configuration for the runtime
func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) {
+ return newRuntimeFromConfig("", options...)
+}
+
+// NewRuntimeFromConfig creates a new container runtime using the given
+// configuration file for its default configuration. Passed RuntimeOption
+// functions can be used to mutate this configuration further.
+// An error will be returned if the configuration file at the given path does
+// not exist or cannot be loaded
+func NewRuntimeFromConfig(userConfigPath string, options ...RuntimeOption) (runtime *Runtime, err error) {
+ if userConfigPath == "" {
+ return nil, errors.New("invalid configuration file specified")
+ }
+ return newRuntimeFromConfig(userConfigPath, options...)
+}
+
+func newRuntimeFromConfig(userConfigPath string, options ...RuntimeOption) (runtime *Runtime, err error) {
runtime = new(Runtime)
runtime.config = new(RuntimeConfig)
runtime.configuredFrom = new(runtimeConfiguredFrom)
@@ -358,11 +380,6 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) {
rootlessConfigPath = filepath.Join(home, ".config/containers/libpod.conf")
- configPath = rootlessConfigPath
- if _, err := os.Stat(configPath); err != nil {
- foundConfig = false
- }
-
runtimeDir, err := util.GetRootlessRuntimeDir()
if err != nil {
return nil, err
@@ -374,6 +391,20 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) {
return nil, errors.Wrapf(err, "cannot set XDG_RUNTIME_DIR")
}
+ }
+
+ if userConfigPath != "" {
+ configPath = userConfigPath
+ if _, err := os.Stat(configPath); err != nil {
+ // If the user specified a config file, we must fail immediately
+ // when it doesn't exist
+ return nil, errors.Wrapf(err, "cannot stat %s", configPath)
+ }
+ } else if rootless.IsRootless() {
+ configPath = rootlessConfigPath
+ if _, err := os.Stat(configPath); err != nil {
+ foundConfig = false
+ }
} else if _, err := os.Stat(OverrideConfigPath); err == nil {
// Use the override configuration path
configPath = OverrideConfigPath
@@ -409,6 +440,24 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) {
if tmpConfig.VolumePath != "" {
runtime.configuredFrom.volPathSet = true
}
+ if tmpConfig.ConmonPath != nil {
+ runtime.configuredFrom.conmonPath = true
+ }
+ if tmpConfig.ConmonEnvVars != nil {
+ runtime.configuredFrom.conmonEnvVars = true
+ }
+ if tmpConfig.OCIRuntimes != nil {
+ runtime.configuredFrom.ociRuntimes = true
+ }
+ if tmpConfig.RuntimePath != nil {
+ runtime.configuredFrom.runtimePath = true
+ }
+ if tmpConfig.CNIPluginDir != nil {
+ runtime.configuredFrom.cniPluginDir = true
+ }
+ if tmpConfig.NoPivotRoot {
+ runtime.configuredFrom.noPivotRoot = true
+ }
if _, err := toml.Decode(string(contents), runtime.config); err != nil {
return nil, errors.Wrapf(err, "error decoding configuration file %s", configPath)
@@ -428,12 +477,24 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) {
}
// Cherry pick the settings we want from the global configuration
- runtime.config.ConmonPath = tmpConfig.ConmonPath
- runtime.config.ConmonEnvVars = tmpConfig.ConmonEnvVars
- runtime.config.OCIRuntimes = tmpConfig.OCIRuntimes
- runtime.config.RuntimePath = tmpConfig.RuntimePath
- runtime.config.CNIPluginDir = tmpConfig.CNIPluginDir
- runtime.config.NoPivotRoot = tmpConfig.NoPivotRoot
+ if !runtime.configuredFrom.conmonPath {
+ runtime.config.ConmonPath = tmpConfig.ConmonPath
+ }
+ if !runtime.configuredFrom.conmonEnvVars {
+ runtime.config.ConmonEnvVars = tmpConfig.ConmonEnvVars
+ }
+ if !runtime.configuredFrom.ociRuntimes {
+ runtime.config.OCIRuntimes = tmpConfig.OCIRuntimes
+ }
+ if !runtime.configuredFrom.runtimePath {
+ runtime.config.RuntimePath = tmpConfig.RuntimePath
+ }
+ if !runtime.configuredFrom.cniPluginDir {
+ runtime.config.CNIPluginDir = tmpConfig.CNIPluginDir
+ }
+ if !runtime.configuredFrom.noPivotRoot {
+ runtime.config.NoPivotRoot = tmpConfig.NoPivotRoot
+ }
break
}
}
@@ -465,80 +526,9 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) {
return runtime, nil
}
-// NewRuntimeFromConfig creates a new container runtime using the given
-// configuration file for its default configuration. Passed RuntimeOption
-// functions can be used to mutate this configuration further.
-// An error will be returned if the configuration file at the given path does
-// not exist or cannot be loaded
-func NewRuntimeFromConfig(configPath string, options ...RuntimeOption) (runtime *Runtime, err error) {
- runtime = new(Runtime)
- runtime.config = new(RuntimeConfig)
- runtime.configuredFrom = new(runtimeConfiguredFrom)
-
- // Set three fields not in the TOML config
- runtime.config.StateType = defaultRuntimeConfig.StateType
- runtime.config.OCIRuntime = defaultRuntimeConfig.OCIRuntime
-
- storageConf, err := util.GetDefaultStoreOptions()
- if err != nil {
- return nil, errors.Wrapf(err, "error retrieving storage config")
- }
- runtime.config.StorageConfig = storageConf
- runtime.config.StaticDir = filepath.Join(storageConf.GraphRoot, "libpod")
- runtime.config.VolumePath = filepath.Join(storageConf.GraphRoot, "volumes")
-
- tmpDir, err := getDefaultTmpDir()
- if err != nil {
- return nil, err
- }
- runtime.config.TmpDir = tmpDir
- if rootless.IsRootless() {
- runtimeDir, err := util.GetRootlessRuntimeDir()
- if err != nil {
- return nil, err
- }
- // containers/image uses XDG_RUNTIME_DIR to locate the auth file.
- // So make sure the env variable is set.
- if err := SetXdgRuntimeDir(runtimeDir); err != nil {
- return nil, errors.Wrapf(err, "cannot set XDG_RUNTIME_DIR")
- }
- }
-
- // Check to see if the given configuration file exists
- if _, err := os.Stat(configPath); err != nil {
- return nil, errors.Wrapf(err, "error checking existence of configuration file %s", configPath)
- }
-
- // Read contents of the config file
- contents, err := ioutil.ReadFile(configPath)
- if err != nil {
- return nil, errors.Wrapf(err, "error reading configuration file %s", configPath)
- }
-
- // Decode configuration file
- if _, err := toml.Decode(string(contents), runtime.config); err != nil {
- return nil, errors.Wrapf(err, "error decoding configuration from file %s", configPath)
- }
-
- // Overwrite the config with user-given configuration options
- for _, opt := range options {
- if err := opt(runtime); err != nil {
- return nil, errors.Wrapf(err, "error configuring runtime")
- }
- }
-
- if err := makeRuntime(runtime); err != nil {
- return nil, err
- }
-
- return runtime, nil
-}
-
// Make a new runtime based on the given configuration
// Sets up containers/storage, state store, OCI runtime
func makeRuntime(runtime *Runtime) (err error) {
- runtime.config.EventsLogFilePath = filepath.Join(runtime.config.TmpDir, "events", "events.log")
-
// Backward compatibility for `runtime_path`
if runtime.config.RuntimePath != nil {
// Don't print twice in rootless mode.
@@ -697,6 +687,8 @@ func makeRuntime(runtime *Runtime) (err error) {
runtime.config.VolumePath = dbConfig.VolumePath
}
+ runtime.config.EventsLogFilePath = filepath.Join(runtime.config.TmpDir, "events", "events.log")
+
logrus.Debugf("Using graph driver %s", runtime.config.StorageConfig.GraphDriverName)
logrus.Debugf("Using graph root %s", runtime.config.StorageConfig.GraphRoot)
logrus.Debugf("Using run root %s", runtime.config.StorageConfig.RunRoot)
diff --git a/test/e2e/commit_test.go b/test/e2e/commit_test.go
index dff156441..bf9c88de5 100644
--- a/test/e2e/commit_test.go
+++ b/test/e2e/commit_test.go
@@ -58,7 +58,7 @@ var _ = Describe("Podman commit", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- check := podmanTest.Podman([]string{"container", "inspect", "foobar.com/test1-image:latest"})
+ check := podmanTest.Podman([]string{"image", "inspect", "foobar.com/test1-image:latest"})
check.WaitWithDefaultTimeout()
data := check.InspectImageJSON()
Expect(StringInSlice("foobar.com/test1-image:latest", data[0].RepoTags)).To(BeTrue())
diff --git a/test/e2e/info_test.go b/test/e2e/info_test.go
index 046297bc0..c960fb311 100644
--- a/test/e2e/info_test.go
+++ b/test/e2e/info_test.go
@@ -41,7 +41,7 @@ var _ = Describe("Podman Info", func() {
})
It("podman system info json output", func() {
session := podmanTest.Podman([]string{"system", "info", "--format=json"})
- session.Wait()
+ session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
})
diff --git a/test/e2e/tree_test.go b/test/e2e/tree_test.go
new file mode 100644
index 000000000..9740adada
--- /dev/null
+++ b/test/e2e/tree_test.go
@@ -0,0 +1,64 @@
+package integration
+
+import (
+ "fmt"
+ "os"
+
+ . "github.com/containers/libpod/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman image tree", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest *PodmanTestIntegration
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanTestCreate(tempdir)
+ podmanTest.RestoreAllArtifacts()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds())
+ GinkgoWriter.Write([]byte(timedResult))
+ })
+
+ It("podman image tree", func() {
+ if podmanTest.RemoteTest {
+ Skip("Does not work on remote client")
+ }
+ session := podmanTest.Podman([]string{"pull", "docker.io/library/busybox:latest"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ dockerfile := `FROM docker.io/library/busybox:latest
+RUN mkdir hello
+RUN touch test.txt
+ENV foo=bar
+`
+ podmanTest.BuildImage(dockerfile, "test:latest", "true")
+
+ session = podmanTest.Podman([]string{"image", "tree", "test:latest"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"image", "tree", "--whatrequires", "docker.io/library/busybox:latest"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"rmi", "test:latest"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"rmi", "docker.io/library/busybox:latest"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+})
diff --git a/troubleshooting.md b/troubleshooting.md
index 74b2e76df..882afef0c 100644
--- a/troubleshooting.md
+++ b/troubleshooting.md
@@ -39,7 +39,7 @@ error pulling image "fedora": unable to pull fedora: error getting default regis
#### Solution
- * Verify that the `/etc/containers/registries.conf` file exists. If not, verify that the skopeo-containers package is installed.
+ * Verify that the `/etc/containers/registries.conf` file exists. If not, verify that the containers-common package is installed.
* Verify that the entries in the `[registries.search]` section of the /etc/containers/registries.conf file are valid and reachable.
* i.e. `registries = ['registry.fedoraproject.org', 'quay.io', 'registry.access.redhat.com']`
diff --git a/utils/utils.go b/utils/utils.go
index 33b0eb1c5..c195daa5d 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -93,7 +93,7 @@ var ErrDetach = errors.New("detached from container")
// CopyDetachable is similar to io.Copy but support a detach key sequence to break out.
func CopyDetachable(dst io.Writer, src io.Reader, keys []byte) (written int64, err error) {
if len(keys) == 0 {
- // Default keys : ctrl-p ctrl-q
+ // Default keys : ctrl-p,ctrl-q
keys = []byte{16, 17}
}