diff options
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} } |