diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/boltdb_state.go | 12 | ||||
-rw-r--r-- | libpod/boltdb_state_internal.go | 12 | ||||
-rw-r--r-- | libpod/container_config.go | 125 | ||||
-rw-r--r-- | libpod/container_exec.go | 14 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 27 | ||||
-rw-r--r-- | libpod/image/filters.go | 20 | ||||
-rw-r--r-- | libpod/image/image.go | 140 | ||||
-rw-r--r-- | libpod/image/layer_tree.go | 222 | ||||
-rw-r--r-- | libpod/image/prune.go | 11 | ||||
-rw-r--r-- | libpod/image/pull.go | 19 | ||||
-rw-r--r-- | libpod/logs/log.go | 12 | ||||
-rw-r--r-- | libpod/logs/reversereader/reversereader.go | 4 | ||||
-rw-r--r-- | libpod/options.go | 13 |
13 files changed, 459 insertions, 172 deletions
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index e98a6e907..2575f0e86 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -2347,11 +2347,19 @@ func (s *BoltState) AddPod(pod *Pod) error { // Check if we already have something with the given ID and name idExist := idsBkt.Get(podID) if idExist != nil { - return errors.Wrapf(define.ErrPodExists, "ID %s is in use", pod.ID()) + err = define.ErrPodExists + if allPodsBkt.Get(idExist) == nil { + err = define.ErrCtrExists + } + return errors.Wrapf(err, "ID \"%s\" is in use", pod.ID()) } nameExist := namesBkt.Get(podName) if nameExist != nil { - return errors.Wrapf(define.ErrPodExists, "name %s is in use", pod.Name()) + err = define.ErrPodExists + if allPodsBkt.Get(nameExist) == nil { + err = define.ErrCtrExists + } + return errors.Wrapf(err, "name \"%s\" is in use", pod.Name()) } // We are good to add the pod diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index ddbd40da8..9be753d26 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -586,11 +586,19 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { // Check if we already have a container with the given ID and name idExist := idsBucket.Get(ctrID) if idExist != nil { - return errors.Wrapf(define.ErrCtrExists, "ID %s is in use", ctr.ID()) + err = define.ErrCtrExists + if allCtrsBucket.Get(idExist) == nil { + err = define.ErrPodExists + } + return errors.Wrapf(err, "ID \"%s\" is in use", ctr.ID()) } nameExist := namesBucket.Get(ctrName) if nameExist != nil { - return errors.Wrapf(define.ErrCtrExists, "name %s is in use", ctr.Name()) + err = define.ErrCtrExists + if allCtrsBucket.Get(nameExist) == nil { + err = define.ErrPodExists + } + return errors.Wrapf(err, "name \"%s\" is in use", ctr.Name()) } // No overlapping containers diff --git a/libpod/container_config.go b/libpod/container_config.go index 301b867fc..5f89395c1 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -13,25 +13,52 @@ import ( // ContainerConfig contains all information that was used to create the // container. It may not be changed once created. -// It is stored, read-only, on disk +// It is stored, read-only, on disk in Libpod's State. +// Any changes will not be written back to the database, and will cause +// inconsistencies with other Libpod instances. type ContainerConfig struct { + // Spec is OCI runtime spec used to create the container. This is passed + // in when the container is created, but it is not the final spec used + // to run the container - it will be modified by Libpod to add things we + // manage (e.g. bind mounts for /etc/resolv.conf, named volumes, a + // network namespace prepared by CNI or slirp4netns) in the + // generateSpec() function. Spec *spec.Spec `json:"spec"` + // ID is a hex-encoded 256-bit pseudorandom integer used as a unique + // identifier for the container. IDs are globally unique in Libpod - + // once an ID is in use, no other container or pod will be created with + // the same one until the holder of the ID has been removed. + // ID is generated by Libpod, and cannot be chosen or influenced by the + // user (except when restoring a checkpointed container). + // ID is guaranteed to be 64 characters long. ID string `json:"id"` + // Name is a human-readable name for the container. All containers must + // have a non-empty name. Name may be provided when the container is + // created; if no name is chosen, a name will be auto-generated. Name string `json:"name"` - // Full ID of the pood the container belongs to + // Pod is the full ID of the pod the container belongs to. If the + // container does not belong to a pod, this will be empty. + // If this is not empty, a pod with this ID is guaranteed to exist in + // the state for the duration of this container's existence. Pod string `json:"pod,omitempty"` - // Namespace the container is in + // Namespace is the libpod Namespace the container is in. + // Namespaces are used to divide containers in the state. Namespace string `json:"namespace,omitempty"` - // ID of this container's lock + // LockID is the ID of this container's lock. Each container, pod, and + // volume is assigned a unique Lock (from one of several backends) by + // the libpod Runtime. This lock will belong only to this container for + // the duration of the container's lifetime. LockID uint32 `json:"lockID"` - // CreateCommand is the full command plus arguments of the process the - // container has been created with. + // CreateCommand is the full command plus arguments that were used to + // create the container. It is shown in the output of Inspect, and may + // be used to recreate an identical container for automatic updates or + // portable systemd unit files. CreateCommand []string `json:"CreateCommand,omitempty"` // RawImageName is the raw and unprocessed name of the image when creating @@ -40,10 +67,13 @@ type ContainerConfig struct { // name and not some normalized instance of it. RawImageName string `json:"RawImageName,omitempty"` - // UID/GID mappings used by the storage + // IDMappings are UID/GID mappings used by the container's user + // namespace. They are used by the OCI runtime when creating the + // container, and by c/storage to ensure that the container's files have + // the appropriate owner. IDMappings storage.IDMappingOptions `json:"idMappingsOptions,omitempty"` - // IDs of dependency containers. + // Dependencies are the IDs of dependency containers. // These containers must be started before this container is started. Dependencies []string @@ -59,45 +89,92 @@ type ContainerConfig struct { // ContainerRootFSConfig is an embedded sub-config providing config info // about the container's root fs. type ContainerRootFSConfig struct { - RootfsImageID string `json:"rootfsImageID,omitempty"` + // RootfsImageID is the ID of the image used to create the container. + // If the container was created from a Rootfs, this will be empty. + // If non-empty, Podman will create a root filesystem for the container + // based on an image with this ID. + // This conflicts with Rootfs. + RootfsImageID string `json:"rootfsImageID,omitempty"` + // RootfsImageName is the (normalized) name of the image used to create + // the container. If the container was created from a Rootfs, this will + // be empty. RootfsImageName string `json:"rootfsImageName,omitempty"` - // Rootfs to use for the container, this conflicts with RootfsImageID + // Rootfs is a directory to use as the container's root filesystem. + // If RootfsImageID is set, this will be empty. + // If this is set, Podman will not create a root filesystem for the + // container based on an image, and will instead use the given directory + // as the container's root. + // Conflicts with RootfsImageID. Rootfs string `json:"rootfs,omitempty"` - // Src path to be mounted on /dev/shm in container. + // ShmDir is the path to be mounted on /dev/shm in container. + // If not set manually at creation time, Libpod will create a tmpfs + // with the size specified in ShmSize and populate this with the path of + // said tmpfs. ShmDir string `json:"ShmDir,omitempty"` - // Size of the container's SHM. + // ShmSize is the size of the container's SHM. Only used if ShmDir was + // not set manually at time of creation. ShmSize int64 `json:"shmSize"` // Static directory for container content that will persist across // reboot. + // StaticDir is a persistent directory for Libpod files that will + // survive system reboot. It is not part of the container's rootfs and + // is not mounted into the container. It will be removed when the + // container is removed. + // Usually used to store container log files, files that will be bind + // mounted into the container (e.g. the resolv.conf we made for the + // container), and other per-container content. StaticDir string `json:"staticDir"` - // Mounts list contains all additional mounts into the container rootfs. - // These include the SHM mount. + // Mounts contains all additional mounts into the container rootfs. + // It is presently only used for the container's SHM directory. // These must be unmounted before the container's rootfs is unmounted. Mounts []string `json:"mounts,omitempty"` - // NamedVolumes lists the named volumes to mount into the container. + // NamedVolumes lists the Libpod named volumes to mount into the + // container. Each named volume is guaranteed to exist so long as this + // container exists. NamedVolumes []*ContainerNamedVolume `json:"namedVolumes,omitempty"` // OverlayVolumes lists the overlay volumes to mount into the container. OverlayVolumes []*ContainerOverlayVolume `json:"overlayVolumes,omitempty"` + // CreateWorkingDir indicates that Libpod should create the container's + // working directory if it does not exist. Some OCI runtimes do this by + // default, but others do not. + CreateWorkingDir bool `json:"createWorkingDir,omitempty"` } // ContainerSecurityConfig is an embedded sub-config providing security configuration // to the container. type ContainerSecurityConfig struct { - // Whether the container is privileged + // Pirivileged is whether the container is privileged. Privileged + // containers have lessened security and increased access to the system. + // Note that this does NOT directly correspond to Podman's --privileged + // flag - most of the work of that flag is done in creating the OCI spec + // given to Libpod. This only enables a small subset of the overall + // operation, mostly around mounting the container image with reduced + // security. Privileged bool `json:"privileged"` - // SELinux process label for container + // ProcessLabel is the SELinux process label for the container. ProcessLabel string `json:"ProcessLabel,omitempty"` - // SELinux mount label for root filesystem + // MountLabel is the SELinux mount label for the container's root + // filesystem. Only used if the container was created from an image. + // If not explicitly set, an unused random MLS label will be assigned by + // containers/storage (but only if SELinux is enabled). MountLabel string `json:"MountLabel,omitempty"` - // LabelOpts are options passed in by the user to setup SELinux labels + // LabelOpts are options passed in by the user to setup SELinux labels. + // These are used by the containers/storage library. LabelOpts []string `json:"labelopts,omitempty"` - // User and group to use in the container - // Can be specified by name or UID/GID + // User and group to use in the container. Can be specified as only user + // (in which case we will attempt to look up the user in the container + // to determine the appropriate group) or user and group separated by a + // colon. + // Can be specified by name or UID/GID. + // If unset, this will default to UID and GID 0 (root). User string `json:"user,omitempty"` - // Additional groups to add + // Groups are additional groups to add the container's user to. These + // are resolved within the container using the container's /etc/passwd. Groups []string `json:"groups,omitempty"` - // AddCurrentUserPasswdEntry indicates that the current user passwd entry - // should be added to the /etc/passwd within the container + // AddCurrentUserPasswdEntry indicates that Libpod should ensure that + // the container's /etc/passwd contains an entry for the user running + // Libpod - mostly used in rootless containers where the user running + // Libpod wants to retain their UID inside the container. AddCurrentUserPasswdEntry bool `json:"addCurrentUserPasswdEntry,omitempty"` } diff --git a/libpod/container_exec.go b/libpod/container_exec.go index 08e95e6dd..bfeae0a11 100644 --- a/libpod/container_exec.go +++ b/libpod/container_exec.go @@ -415,6 +415,13 @@ func (c *Container) ExecHTTPStartAndAttach(sessionID string, httpCon net.Conn, h execOpts, err := prepareForExec(c, session) if err != nil { + session.State = define.ExecStateStopped + session.ExitCode = define.ExecErrorCodeGeneric + + if err := c.save(); err != nil { + logrus.Errorf("Error saving container %s exec session %s after failure to prepare: %v", err, c.ID(), session.ID()) + } + return err } @@ -427,6 +434,13 @@ func (c *Container) ExecHTTPStartAndAttach(sessionID string, httpCon net.Conn, h pid, attachChan, err := c.ociRuntime.ExecContainerHTTP(c, session.ID(), execOpts, httpCon, httpBuf, streams, cancel) if err != nil { + session.State = define.ExecStateStopped + session.ExitCode = define.TranslateExecErrorToExitCode(define.ExecErrorCodeGeneric, err) + + if err := c.save(); err != nil { + logrus.Errorf("Error saving container %s exec session %s after failure to start: %v", err, c.ID(), session.ID()) + } + return err } diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 4cfe992ea..9fb9738dc 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -159,7 +159,32 @@ func (c *Container) prepare() error { } // Save changes to container state - return c.save() + if err := c.save(); err != nil { + return err + } + + // Ensure container entrypoint is created (if required) + if c.config.CreateWorkingDir { + workdir, err := securejoin.SecureJoin(c.state.Mountpoint, c.WorkingDir()) + if err != nil { + return errors.Wrapf(err, "error creating path to container %s working dir", c.ID()) + } + rootUID := c.RootUID() + rootGID := c.RootGID() + + if err := os.MkdirAll(workdir, 0755); err != nil { + if os.IsExist(err) { + return nil + } + return errors.Wrapf(err, "error creating container %s working dir", c.ID()) + } + + if err := os.Chown(workdir, rootUID, rootGID); err != nil { + return errors.Wrapf(err, "error chowning container %s working directory to container root", c.ID()) + } + } + + return nil } // cleanupNetwork unmounts and cleans up the container's network diff --git a/libpod/image/filters.go b/libpod/image/filters.go index 9738a7d5e..db647954f 100644 --- a/libpod/image/filters.go +++ b/libpod/image/filters.go @@ -29,6 +29,26 @@ func CreatedBeforeFilter(createTime time.Time) ResultFilter { } } +// IntermediateFilter returns filter for intermediate images (i.e., images +// with children and no tags). +func (ir *Runtime) IntermediateFilter(ctx context.Context, images []*Image) (ResultFilter, error) { + tree, err := ir.layerTree() + if err != nil { + return nil, err + } + return func(i *Image) bool { + if len(i.Names()) > 0 { + return true + } + children, err := tree.children(ctx, i, false) + if err != nil { + logrus.Error(err.Error()) + return false + } + return len(children) == 0 + }, nil +} + // CreatedAfterFilter allows you to filter on images created after // the given time.Time func CreatedAfterFilter(createTime time.Time) ResultFilter { diff --git a/libpod/image/image.go b/libpod/image/image.go index 8b2aa318f..6106084d5 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -14,6 +14,7 @@ import ( "syscall" "time" + "github.com/containers/common/pkg/retry" cp "github.com/containers/image/v5/copy" "github.com/containers/image/v5/directory" dockerarchive "github.com/containers/image/v5/docker/archive" @@ -75,6 +76,8 @@ type InfoImage struct { Layers []LayerInfo } +const maxRetry = 3 + // ImageFilter is a function to determine whether a image is included // in command output. Images to be outputted are tested using the function. // A true return will include the image, a false return will exclude it. @@ -158,7 +161,7 @@ func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile if signaturePolicyPath == "" { signaturePolicyPath = ir.SignaturePolicyPath } - imageName, err := ir.pullImageFromHeuristicSource(ctx, name, writer, authfile, signaturePolicyPath, signingoptions, dockeroptions, label) + imageName, err := ir.pullImageFromHeuristicSource(ctx, name, writer, authfile, signaturePolicyPath, signingoptions, dockeroptions, &retry.RetryOptions{MaxRetry: maxRetry}, label) if err != nil { return nil, errors.Wrapf(err, "unable to pull %s", name) } @@ -176,7 +179,7 @@ func (ir *Runtime) LoadFromArchiveReference(ctx context.Context, srcRef types.Im if signaturePolicyPath == "" { signaturePolicyPath = ir.SignaturePolicyPath } - imageNames, err := ir.pullImageFromReference(ctx, srcRef, writer, "", signaturePolicyPath, SigningOptions{}, &DockerRegistryOptions{}) + imageNames, err := ir.pullImageFromReference(ctx, srcRef, writer, "", signaturePolicyPath, SigningOptions{}, &DockerRegistryOptions{}, &retry.RetryOptions{MaxRetry: maxRetry}) if err != nil { return nil, errors.Wrapf(err, "unable to pull %s", transports.ImageName(srcRef)) } @@ -856,26 +859,6 @@ func (i *Image) Dangling() bool { return len(i.Names()) == 0 } -// Intermediate returns true if the image is cache or intermediate image. -// Cache image has parent and child. -func (i *Image) Intermediate(ctx context.Context) (bool, error) { - parent, err := i.IsParent(ctx) - if err != nil { - return false, err - } - if !parent { - return false, nil - } - img, err := i.GetParent(ctx) - if err != nil { - return false, err - } - if img != nil { - return true, nil - } - return false, nil -} - // User returns the image's user func (i *Image) User(ctx context.Context) (string, error) { imgInspect, err := i.inspect(ctx, false) @@ -1214,7 +1197,7 @@ func splitString(input string) string { // the parent of any other layer in store. Double check that image with that // layer exists as well. func (i *Image) IsParent(ctx context.Context) (bool, error) { - children, err := i.getChildren(ctx, 1) + children, err := i.getChildren(ctx, false) if err != nil { if errors.Cause(err) == ErrImageIsBareList { return false, nil @@ -1289,63 +1272,16 @@ func areParentAndChild(parent, child *imgspecv1.Image) bool { // GetParent returns the image ID of the parent. Return nil if a parent is not found. func (i *Image) GetParent(ctx context.Context) (*Image, error) { - var childLayer *storage.Layer - images, err := i.imageruntime.GetImages() - if err != nil { - return nil, err - } - if i.TopLayer() != "" { - if childLayer, err = i.imageruntime.store.Layer(i.TopLayer()); err != nil { - return nil, err - } - } - // fetch the configuration for the child image - child, err := i.ociv1Image(ctx) + tree, err := i.imageruntime.layerTree() if err != nil { - if errors.Cause(err) == ErrImageIsBareList { - return nil, nil - } return nil, err } - for _, img := range images { - if img.ID() == i.ID() { - continue - } - candidateLayer := img.TopLayer() - // as a child, our top layer, if we have one, is either the - // candidate parent's layer, or one that's derived from it, so - // skip over any candidate image where we know that isn't the - // case - if childLayer != nil { - // The child has at least one layer, so a parent would - // have a top layer that's either the same as the child's - // top layer or the top layer's recorded parent layer, - // which could be an empty value. - if candidateLayer != childLayer.Parent && candidateLayer != childLayer.ID { - continue - } - } else { - // The child has no layers, but the candidate does. - if candidateLayer != "" { - continue - } - } - // fetch the configuration for the candidate image - candidate, err := img.ociv1Image(ctx) - if err != nil { - return nil, err - } - // compare them - if areParentAndChild(candidate, child) { - return img, nil - } - } - return nil, nil + return tree.parent(ctx, i) } // GetChildren returns a list of the imageIDs that depend on the image func (i *Image) GetChildren(ctx context.Context) ([]string, error) { - children, err := i.getChildren(ctx, 0) + children, err := i.getChildren(ctx, true) if err != nil { if errors.Cause(err) == ErrImageIsBareList { return nil, nil @@ -1355,62 +1291,15 @@ func (i *Image) GetChildren(ctx context.Context) ([]string, error) { return children, nil } -// getChildren returns a list of at most "max" imageIDs that depend on the image -func (i *Image) getChildren(ctx context.Context, max int) ([]string, error) { - var children []string - - if _, err := i.toImageRef(ctx); err != nil { - return nil, nil - } - - images, err := i.imageruntime.GetImages() - if err != nil { - return nil, err - } - - // fetch the configuration for the parent image - parent, err := i.ociv1Image(ctx) +// getChildren returns a list of imageIDs that depend on the image. If all is +// false, only the first child image is returned. +func (i *Image) getChildren(ctx context.Context, all bool) ([]string, error) { + tree, err := i.imageruntime.layerTree() if err != nil { return nil, err } - parentLayer := i.TopLayer() - for _, img := range images { - if img.ID() == i.ID() { - continue - } - if img.TopLayer() == "" { - if parentLayer != "" { - // this image has no layers, but we do, so - // it can't be derived from this one - continue - } - } else { - candidateLayer, err := img.Layer() - if err != nil { - return nil, err - } - // if this image's top layer is not our top layer, and is not - // based on our top layer, we can skip it - if candidateLayer.Parent != parentLayer && candidateLayer.ID != parentLayer { - continue - } - } - // fetch the configuration for the candidate image - candidate, err := img.ociv1Image(ctx) - if err != nil { - return nil, err - } - // compare them - if areParentAndChild(parent, candidate) { - children = append(children, img.ID()) - } - // if we're not building an exhaustive list, maybe we're done? - if max > 0 && len(children) >= max { - break - } - } - return children, nil + return tree.children(ctx, i, all) } // InputIsID returns a bool if the user input for an image @@ -1667,6 +1556,7 @@ type LayerInfo struct { // 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) { + // TODO: evaluate if we can reuse `layerTree` here. // Memory allocated to store map of layers with key LayerID. // Map will build dependency chain with ParentID and ChildID(s) diff --git a/libpod/image/layer_tree.go b/libpod/image/layer_tree.go new file mode 100644 index 000000000..3699655fd --- /dev/null +++ b/libpod/image/layer_tree.go @@ -0,0 +1,222 @@ +package image + +import ( + "context" + + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" +) + +// layerTree is an internal representation of local layers. +type layerTree struct { + // nodes is the actual layer tree with layer IDs being keys. + nodes map[string]*layerNode + // ociCache is a cache for Image.ID -> OCI Image. Translations are done + // on-demand. + ociCache map[string]*ociv1.Image +} + +// node returns a layerNode for the specified layerID. +func (t *layerTree) node(layerID string) *layerNode { + node, exists := t.nodes[layerID] + if !exists { + node = &layerNode{} + t.nodes[layerID] = node + } + return node +} + +// toOCI returns an OCI image for the specified image. +func (t *layerTree) toOCI(ctx context.Context, i *Image) (*ociv1.Image, error) { + var err error + oci, exists := t.ociCache[i.ID()] + if !exists { + oci, err = i.ociv1Image(ctx) + t.ociCache[i.ID()] = oci + } + return oci, err +} + +// layerNode is a node in a layerTree. It's ID is the key in a layerTree. +type layerNode struct { + children []*layerNode + images []*Image + parent *layerNode +} + +// layerTree extracts a layerTree from the layers in the local storage and +// relates them to the specified images. +func (ir *Runtime) layerTree() (*layerTree, error) { + layers, err := ir.store.Layers() + if err != nil { + return nil, err + } + + images, err := ir.GetImages() + if err != nil { + return nil, err + } + + tree := layerTree{ + nodes: make(map[string]*layerNode), + ociCache: make(map[string]*ociv1.Image), + } + + // First build a tree purely based on layer information. + for _, layer := range layers { + node := tree.node(layer.ID) + if layer.Parent == "" { + continue + } + parent := tree.node(layer.Parent) + node.parent = parent + parent.children = append(parent.children, node) + } + + // Now assign the images to each (top) layer. + for i := range images { + img := images[i] // do not leak loop variable outside the scope + topLayer := img.TopLayer() + if topLayer == "" { + continue + } + node, exists := tree.nodes[topLayer] + if !exists { + return nil, errors.Errorf("top layer %s of image %s not found in layer tree", img.TopLayer(), img.ID()) + } + node.images = append(node.images, img) + } + + return &tree, nil +} + +// children returns the image IDs of children . Child images are images +// with either the same top layer as parent or parent being the true parent +// layer. Furthermore, the history of the parent and child images must match +// with the parent having one history item less. +// If all is true, all images are returned. Otherwise, the first image is +// returned. +func (t *layerTree) children(ctx context.Context, parent *Image, all bool) ([]string, error) { + if parent.TopLayer() == "" { + return nil, nil + } + + var children []string + + parentNode, exists := t.nodes[parent.TopLayer()] + if !exists { + return nil, errors.Errorf("layer not found in layer tree: %q", parent.TopLayer()) + } + + parentID := parent.ID() + parentOCI, err := t.toOCI(ctx, parent) + if err != nil { + return nil, err + } + + // checkParent returns true if child and parent are in such a relation. + checkParent := func(child *Image) (bool, error) { + if parentID == child.ID() { + return false, nil + } + childOCI, err := t.toOCI(ctx, child) + if err != nil { + return false, err + } + // History check. + return areParentAndChild(parentOCI, childOCI), nil + } + + // addChildrenFrom adds child images of parent to children. Returns + // true if any image is a child of parent. + addChildrenFromNode := func(node *layerNode) (bool, error) { + foundChildren := false + for _, childImage := range node.images { + isChild, err := checkParent(childImage) + if err != nil { + return foundChildren, err + } + if isChild { + foundChildren = true + children = append(children, childImage.ID()) + if all { + return foundChildren, nil + } + } + } + return foundChildren, nil + } + + // First check images where parent's top layer is also the parent + // layer. + for _, childNode := range parentNode.children { + found, err := addChildrenFromNode(childNode) + if err != nil { + return nil, err + } + if found && all { + return children, nil + } + } + + // Now check images with the same top layer. + if _, err := addChildrenFromNode(parentNode); err != nil { + return nil, err + } + + return children, nil +} + +// parent returns the parent image or nil if no parent image could be found. +func (t *layerTree) parent(ctx context.Context, child *Image) (*Image, error) { + if child.TopLayer() == "" { + return nil, nil + } + + node, exists := t.nodes[child.TopLayer()] + if !exists { + return nil, errors.Errorf("layer not found in layer tree: %q", child.TopLayer()) + } + + childOCI, err := t.toOCI(ctx, child) + if err != nil { + return nil, err + } + + // Check images from the parent node (i.e., parent layer) and images + // with the same layer (i.e., same top layer). + childID := child.ID() + images := node.images + if node.parent != nil { + images = append(images, node.parent.images...) + } + for _, parent := range images { + if parent.ID() == childID { + continue + } + parentOCI, err := t.toOCI(ctx, parent) + if err != nil { + return nil, err + } + // History check. + if areParentAndChild(parentOCI, childOCI) { + return parent, nil + } + } + + return nil, nil +} + +// hasChildrenAndParent returns true if the specified image has children and a +// parent. +func (t *layerTree) hasChildrenAndParent(ctx context.Context, i *Image) (bool, error) { + children, err := t.children(ctx, i, false) + if err != nil { + return false, err + } + if len(children) == 0 { + return false, nil + } + parent, err := t.parent(ctx, i) + return parent != nil, err +} diff --git a/libpod/image/prune.go b/libpod/image/prune.go index 8c9267650..5a9ca5d8e 100644 --- a/libpod/image/prune.go +++ b/libpod/image/prune.go @@ -66,6 +66,12 @@ func (ir *Runtime) GetPruneImages(ctx context.Context, all bool, filterFuncs []I if err != nil { return nil, err } + + tree, err := ir.layerTree() + if err != nil { + return nil, err + } + for _, i := range allImages { // filter the images based on this. for _, filterFunc := range filterFuncs { @@ -85,8 +91,9 @@ func (ir *Runtime) GetPruneImages(ctx context.Context, all bool, filterFuncs []I } } - //skip the cache or intermediate images - intermediate, err := i.Intermediate(ctx) + // skip the cache (i.e., with parent) and intermediate (i.e., + // with children) images + intermediate, err := tree.hasChildrenAndParent(ctx, i) if err != nil { return nil, err } diff --git a/libpod/image/pull.go b/libpod/image/pull.go index d31f0dbdc..641698d03 100644 --- a/libpod/image/pull.go +++ b/libpod/image/pull.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "github.com/containers/common/pkg/retry" cp "github.com/containers/image/v5/copy" "github.com/containers/image/v5/directory" "github.com/containers/image/v5/docker" @@ -218,7 +219,7 @@ func toLocalImageName(imageName string) string { // pullImageFromHeuristicSource pulls an image based on inputName, which is heuristically parsed and may involve configured registries. // Use pullImageFromReference if the source is known precisely. -func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName string, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, label *string) ([]string, error) { +func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName string, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions, label *string) ([]string, error) { span, _ := opentracing.StartSpanFromContext(ctx, "pullImageFromHeuristicSource") defer span.Finish() @@ -247,11 +248,11 @@ func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName s return nil, errors.Wrapf(err, "error determining pull goal for image %q", inputName) } } - return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, label) + return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, retryOptions, label) } // pullImageFromReference pulls an image from a types.imageReference. -func (ir *Runtime) pullImageFromReference(ctx context.Context, srcRef types.ImageReference, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions) ([]string, error) { +func (ir *Runtime) pullImageFromReference(ctx context.Context, srcRef types.ImageReference, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions) ([]string, error) { span, _ := opentracing.StartSpanFromContext(ctx, "pullImageFromReference") defer span.Finish() @@ -264,7 +265,7 @@ func (ir *Runtime) pullImageFromReference(ctx context.Context, srcRef types.Imag if err != nil { return nil, errors.Wrapf(err, "error determining pull goal for image %q", transports.ImageName(srcRef)) } - return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, nil) + return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, retryOptions, nil) } func cleanErrorMessage(err error) string { @@ -274,7 +275,7 @@ func cleanErrorMessage(err error) string { } // doPullImage is an internal helper interpreting pullGoal. Almost everyone should call one of the callers of doPullImage instead. -func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goal pullGoal, writer io.Writer, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, label *string) ([]string, error) { +func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goal pullGoal, writer io.Writer, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions, label *string) ([]string, error) { span, _ := opentracing.StartSpanFromContext(ctx, "doPullImage") defer span.Finish() @@ -310,9 +311,11 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa return nil, err } } - - _, err = cp.Image(ctx, policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions) - if err != nil { + imageInfo := imageInfo + if err = retry.RetryIfNecessary(ctx, func() error { + _, err = cp.Image(ctx, policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions) + return err + }, retryOptions); err != nil { pullErrors = multierror.Append(pullErrors, err) logrus.Debugf("Error pulling image ref %s: %v", imageInfo.srcRef.StringWithinTransport(), err) if writer != nil { diff --git a/libpod/logs/log.go b/libpod/logs/log.go index c2545e188..a9554088b 100644 --- a/libpod/logs/log.go +++ b/libpod/logs/log.go @@ -101,11 +101,14 @@ func getTailLog(path string, tail int) ([]*LogLine, error) { if err != nil { if errors.Cause(err) == io.EOF { inputs <- []string{leftover} - close(inputs) - break + } else { + logrus.Error(err) } - logrus.Error(err) close(inputs) + if err := f.Close(); err != nil { + logrus.Error(err) + } + break } line := strings.Split(s+leftover, "\n") if len(line) > 1 { @@ -136,9 +139,6 @@ func getTailLog(path string, tail int) ([]*LogLine, error) { } // if we have enough loglines, we can hangup if nllCounter >= tail { - if err := f.Close(); err != nil { - logrus.Error(err) - } break } } diff --git a/libpod/logs/reversereader/reversereader.go b/libpod/logs/reversereader/reversereader.go index 72d9ad975..4fa1a3f88 100644 --- a/libpod/logs/reversereader/reversereader.go +++ b/libpod/logs/reversereader/reversereader.go @@ -60,7 +60,7 @@ func (r *ReverseReader) Read() (string, error) { if int64(n) < r.readSize { b = b[0:n] } - // Set to the next page boundary - r.offset = -r.readSize + // Move the offset one pagesize up + r.offset -= r.readSize return string(b), nil } diff --git a/libpod/options.go b/libpod/options.go index b98ef2221..16b05d9b6 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1451,6 +1451,19 @@ func WithCreateCommand(cmd []string) CtrCreateOption { } } +// WithCreateWorkingDir tells Podman to create the container's working directory +// if it does not exist. +func WithCreateWorkingDir() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.CreateWorkingDir = true + return nil + } +} + // Volume Creation Options // WithVolumeName sets the name of the volume. |