diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/container.go | 42 | ||||
-rw-r--r-- | libpod/container_internal.go | 4 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 4 | ||||
-rw-r--r-- | libpod/image/image.go | 107 | ||||
-rw-r--r-- | libpod/image/parts.go | 4 | ||||
-rw-r--r-- | libpod/image/pull.go | 36 | ||||
-rw-r--r-- | libpod/networking_linux.go | 77 | ||||
-rw-r--r-- | libpod/options.go | 3 | ||||
-rw-r--r-- | libpod/pod.go | 10 | ||||
-rw-r--r-- | libpod/runtime.go | 5 | ||||
-rw-r--r-- | libpod/runtime_img.go | 36 | ||||
-rw-r--r-- | libpod/runtime_pod.go | 36 | ||||
-rw-r--r-- | libpod/runtime_pod_linux.go | 2 |
13 files changed, 264 insertions, 102 deletions
diff --git a/libpod/container.go b/libpod/container.go index 9486986ab..f882868ed 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -143,18 +143,11 @@ type containerState struct { // ExecSessions contains active exec sessions for container // Exec session ID is mapped to PID of exec process ExecSessions map[string]*ExecSession `json:"execSessions,omitempty"` - // IPs contains IP addresses assigned to the container - // Only populated if we created a network namespace for the container, - // and the network namespace is currently active - IPs []*cnitypes.IPConfig `json:"ipAddresses,omitempty"` - // Interfaces contains interface information about the container - // Only populated if we created a network namespace for the container, - // and the network namespace is currently active - Interfaces []*cnitypes.Interface `json:"interfaces,omitempty"` - // Routes contains network routes present in the container - // Only populated if we created a network namespace for the container, - // and the network namespace is currently active - Routes []*types.Route `json:"routes,omitempty"` + // NetworkStatus contains the configuration results for all networks + // the pod is attached to. Only populated if we created a network + // namespace for the container, and the network namespace is currently + // active + NetworkStatus []*cnitypes.Result `json:"networkResults,omitempty"` // BindMounts contains files that will be bind-mounted into the // container when it is mounted. // These include /etc/hosts and /etc/resolv.conf @@ -268,6 +261,8 @@ type ContainerConfig struct { // Hosts to add in container // Will be appended to host's host file HostAdd []string `json:"hostsAdd,omitempty"` + // Network names (CNI) to add container to. Empty to use default network. + Networks []string `json:"networks,omitempty"` // Image Config @@ -773,10 +768,12 @@ func (c *Container) IPs() ([]net.IPNet, error) { return nil, errors.Wrapf(ErrInvalidArg, "container %s network namespace is not managed by libpod") } - ips := make([]net.IPNet, 0, len(c.state.IPs)) + ips := make([]net.IPNet, 0) - for _, ip := range c.state.IPs { - ips = append(ips, ip.Address) + for _, r := range c.state.NetworkStatus { + for _, ip := range r.IPs { + ips = append(ips, ip.Address) + } } return ips, nil @@ -799,15 +796,16 @@ func (c *Container) Routes() ([]types.Route, error) { return nil, errors.Wrapf(ErrInvalidArg, "container %s network namespace is not managed by libpod") } - routes := make([]types.Route, 0, len(c.state.Routes)) + routes := make([]types.Route, 0) - for _, route := range c.state.Routes { - newRoute := types.Route{ - Dst: route.Dst, - GW: route.GW, + for _, r := range c.state.NetworkStatus { + for _, route := range r.Routes { + newRoute := types.Route{ + Dst: route.Dst, + GW: route.GW, + } + routes = append(routes, newRoute) } - - routes = append(routes, newRoute) } return routes, nil diff --git a/libpod/container_internal.go b/libpod/container_internal.go index eb9b39a02..905402c47 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -321,9 +321,7 @@ func resetState(state *containerState) error { state.Mounted = false state.State = ContainerStateConfigured state.ExecSessions = make(map[string]*ExecSession) - state.IPs = nil - state.Interfaces = nil - state.Routes = nil + state.NetworkStatus = nil state.BindMounts = make(map[string]string) return nil diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 9ad825458..e7e3b6ce9 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -75,9 +75,7 @@ func (c *Container) cleanupNetwork() error { } c.state.NetNS = nil - c.state.IPs = nil - c.state.Interfaces = nil - c.state.Routes = nil + c.state.NetworkStatus = nil if c.valid { return c.save() diff --git a/libpod/image/image.go b/libpod/image/image.go index 5dd2c57f3..b5c4c537f 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -59,6 +59,9 @@ type Runtime struct { SignaturePolicyPath string } +// ErrRepoTagNotFound is the error returned when the image id given doesn't match a rep tag in store +var ErrRepoTagNotFound = errors.New("unable to match user input to any specific repotag") + // NewImageRuntimeFromStore creates an ImageRuntime based on a provided store func NewImageRuntimeFromStore(store storage.Store) *Runtime { return &Runtime{ @@ -333,9 +336,39 @@ func (i *Image) TopLayer() string { // Remove an image; container removal for the image must be done // outside the context of images +// TODO: the force param does nothing as of now. Need to move container +// handling logic here eventually. func (i *Image) Remove(force bool) error { - _, err := i.imageruntime.store.DeleteImage(i.ID(), true) - return err + parent, err := i.GetParent() + if err != nil { + return err + } + if _, err := i.imageruntime.store.DeleteImage(i.ID(), true); err != nil { + return err + } + for parent != nil { + nextParent, err := parent.GetParent() + if err != nil { + return err + } + children, err := parent.GetChildren() + if err != nil { + return err + } + // Do not remove if image is a base image and is not untagged, or if + // the image has more children. + if (nextParent == nil && len(parent.Names()) > 0) || len(children) > 0 { + return nil + } + id := parent.ID() + if _, err := i.imageruntime.store.DeleteImage(id, true); err != nil { + logrus.Debugf("unable to remove intermediate image %q: %v", id, err) + } else { + fmt.Println(id) + } + parent = nextParent + } + return nil } // Decompose an Image @@ -902,7 +935,7 @@ func (i *Image) MatchRepoTag(input string) (string, error) { } } if maxCount == 0 { - return "", errors.Errorf("unable to match user input to any specific repotag") + return "", ErrRepoTagNotFound } if len(results[maxCount]) > 1 { return "", errors.Errorf("user input matched multiple repotags for the image") @@ -916,6 +949,68 @@ func splitString(input string) string { return split[len(split)-1] } +// IsParent goes through the layers in the store and checks if i.TopLayer is +// the parent of any other layer in store. Double check that image with that +// layer exists as well. +func (i *Image) IsParent() (bool, error) { + children, err := i.GetChildren() + if err != nil { + return false, err + } + return len(children) > 0, nil +} + +// GetParent returns the image ID of the parent. Return nil if a parent is not found. +func (i *Image) GetParent() (*Image, error) { + images, err := i.imageruntime.GetImages() + if err != nil { + return nil, err + } + layer, err := i.imageruntime.store.Layer(i.TopLayer()) + if err != nil { + return nil, err + } + for _, img := range images { + if img.TopLayer() == layer.Parent { + return img, nil + } + } + return nil, nil +} + +// GetChildren returns a list of the imageIDs that depend on the image +func (i *Image) GetChildren() ([]string, error) { + var children []string + images, err := i.imageruntime.GetImages() + if err != nil { + return nil, err + } + layers, err := i.imageruntime.store.Layers() + if err != nil { + return nil, err + } + + for _, layer := range layers { + if layer.Parent == i.TopLayer() { + if imageID := getImageOfTopLayer(images, layer.ID); len(imageID) > 0 { + children = append(children, imageID...) + } + } + } + return children, nil +} + +// getImageOfTopLayer returns the image ID where layer is the top layer of the image +func getImageOfTopLayer(images []*Image, layer string) []string { + var matches []string + for _, img := range images { + if img.TopLayer() == layer { + matches = append(matches, img.ID()) + } + } + return matches +} + // InputIsID returns a bool if the user input for an image // is the image's partial or full id func (i *Image) InputIsID() bool { @@ -960,3 +1055,9 @@ func (i *Image) Comment(ctx context.Context, manifestType string) (string, error } return ociv1Img.History[0].Comment, nil } + +// HasShaInInputName returns a bool as to whether the user provide an image name that includes +// a reference to a specific sha +func (i *Image) HasShaInInputName() bool { + return strings.Contains(i.InputName, "@sha256:") +} diff --git a/libpod/image/parts.go b/libpod/image/parts.go index 979f223fc..07a119c28 100644 --- a/libpod/image/parts.go +++ b/libpod/image/parts.go @@ -2,6 +2,7 @@ package image import ( "fmt" + "strings" "github.com/containers/image/docker/reference" ) @@ -33,6 +34,9 @@ func decompose(input string) (imageParts, error) { } if !isTagged { tag = "latest" + if strings.Contains(input, "@sha256:") { + tag = "none" + } } else { tag = ntag.Tag() } diff --git a/libpod/image/pull.go b/libpod/image/pull.go index 48513509d..a5a398eb1 100644 --- a/libpod/image/pull.go +++ b/libpod/image/pull.go @@ -49,9 +49,10 @@ var ( ) type pullStruct struct { - image string - srcRef types.ImageReference - dstRef types.ImageReference + image string + srcRef types.ImageReference + dstRef types.ImageReference + shaPullName string } func (ir *Runtime) getPullStruct(srcRef types.ImageReference, destName string) (*pullStruct, error) { @@ -247,13 +248,22 @@ func (i *Image) pullImage(ctx context.Context, writer io.Writer, authfile, signa // createNamesToPull looks at a decomposed image and determines the possible // images names to try pulling in combination with the registries.conf file as well func (i *Image) createNamesToPull() ([]*pullStruct, error) { - var pullNames []*pullStruct + var ( + pullNames []*pullStruct + imageName string + ) + decomposedImage, err := decompose(i.InputName) if err != nil { return nil, err } if decomposedImage.hasRegistry { - srcRef, err := alltransports.ParseImageName(decomposedImage.assembleWithTransport()) + if i.HasShaInInputName() { + imageName = fmt.Sprintf("%s%s", decomposedImage.transport, i.InputName) + } else { + imageName = decomposedImage.assembleWithTransport() + } + srcRef, err := alltransports.ParseImageName(imageName) if err != nil { return nil, errors.Wrapf(err, "unable to parse '%s'", i.InputName) } @@ -261,6 +271,9 @@ func (i *Image) createNamesToPull() ([]*pullStruct, error) { image: i.InputName, srcRef: srcRef, } + if i.HasShaInInputName() { + ps.shaPullName = decomposedImage.assemble() + } pullNames = append(pullNames, &ps) } else { @@ -275,7 +288,11 @@ func (i *Image) createNamesToPull() ([]*pullStruct, error) { } for _, registry := range searchRegistries { decomposedImage.registry = registry - srcRef, err := alltransports.ParseImageName(decomposedImage.assembleWithTransport()) + imageName := decomposedImage.assembleWithTransport() + if i.HasShaInInputName() { + imageName = fmt.Sprintf("%s%s/%s", decomposedImage.transport, registry, i.InputName) + } + srcRef, err := alltransports.ParseImageName(imageName) if err != nil { return nil, errors.Wrapf(err, "unable to parse '%s'", i.InputName) } @@ -287,8 +304,13 @@ func (i *Image) createNamesToPull() ([]*pullStruct, error) { } } + // Here we construct the destination reference for _, pStruct := range pullNames { - destRef, err := is.Transport.ParseStoreReference(i.imageruntime.store, pStruct.image) + dstName := pStruct.image + if pStruct.shaPullName != "" { + dstName = pStruct.shaPullName + } + destRef, err := is.Transport.ParseStoreReference(i.imageruntime.store, dstName) if err != nil { return nil, errors.Wrapf(err, "error parsing dest reference name") } diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 59666b534..d9eb87572 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -5,7 +5,6 @@ package libpod import ( "crypto/rand" "fmt" - "net" "os" "path/filepath" "strconv" @@ -24,21 +23,22 @@ import ( ) // Get an OCICNI network config -func getPodNetwork(id, name, nsPath string, ports []ocicni.PortMapping) ocicni.PodNetwork { +func getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.PortMapping) ocicni.PodNetwork { return ocicni.PodNetwork{ Name: name, Namespace: name, // TODO is there something else we should put here? We don't know about Kube namespaces ID: id, NetNS: nsPath, PortMappings: ports, + Networks: networks, } } // Create and configure a new network namespace for a container func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (err error) { - podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.PortMappings) + podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings) - result, err := r.netPlugin.SetUpPod(podNetwork) + results, err := r.netPlugin.SetUpPod(podNetwork) if err != nil { return errors.Wrapf(err, "error configuring network namespace for container %s", ctr.ID()) } @@ -50,28 +50,28 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (err error) { } }() - logrus.Debugf("Response from CNI plugins: %v", result.String()) - - resultStruct, err := cnitypes.GetResult(result) - if err != nil { - return errors.Wrapf(err, "error parsing result from CNI plugins") + ctr.state.NetNS = ctrNS + ctr.state.NetworkStatus = make([]*cnitypes.Result, 0) + for idx, r := range results { + logrus.Debugf("[%d] CNI result: %v", idx, r.String()) + resultCurrent, err := cnitypes.GetResult(r) + if err != nil { + return errors.Wrapf(err, "error parsing CNI plugin result %q: %v", r.String(), err) + } + ctr.state.NetworkStatus = append(ctr.state.NetworkStatus, resultCurrent) } - ctr.state.NetNS = ctrNS - ctr.state.IPs = resultStruct.IPs - ctr.state.Routes = resultStruct.Routes - ctr.state.Interfaces = resultStruct.Interfaces - - // We need to temporarily use iptables to allow the container - // to resolve DNS until this issue is fixed upstream. - // https://github.com/containernetworking/plugins/pull/75 - if resultStruct.IPs != nil { - for _, ip := range resultStruct.IPs { + for _, r := range ctr.state.NetworkStatus { + // We need to temporarily use iptables to allow the container + // to resolve DNS until this issue is fixed upstream. + // https://github.com/containernetworking/plugins/pull/75 + for _, ip := range r.IPs { if ip.Address.IP.To4() != nil { iptablesDNS("-I", ip.Address.IP.String()) } } } + return nil } @@ -148,27 +148,6 @@ func joinNetNS(path string) (ns.NetNS, error) { return ns, nil } -// Get a container's IP address -func (r *Runtime) getContainerIP(ctr *Container) (net.IP, error) { - if ctr.state.NetNS == nil { - return nil, errors.Wrapf(ErrInvalidArg, "container %s has no network namespace, cannot get IP", ctr.ID()) - } - - podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.PortMappings) - - ipStr, err := r.netPlugin.GetPodNetworkStatus(podNetwork) - if err != nil { - return nil, errors.Wrapf(err, "error retrieving network status of container %s", ctr.ID()) - } - - ip := net.ParseIP(ipStr) - if ip == nil { - return nil, errors.Wrapf(ErrInternal, "error parsing IP address %s for container %s", ipStr, ctr.ID()) - } - - return ip, nil -} - // Tear down a network namespace func (r *Runtime) teardownNetNS(ctr *Container) error { if ctr.state.NetNS == nil { @@ -180,15 +159,17 @@ func (r *Runtime) teardownNetNS(ctr *Container) error { // on per IP address, we also need to try to remove the iptables rule // on cleanup. Remove when https://github.com/containernetworking/plugins/pull/75 // is merged. - for _, ip := range ctr.state.IPs { - if ip.Address.IP.To4() != nil { - iptablesDNS("-D", ip.Address.IP.String()) + for _, r := range ctr.state.NetworkStatus { + for _, ip := range r.IPs { + if ip.Address.IP.To4() != nil { + iptablesDNS("-D", ip.Address.IP.String()) + } } } logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID()) - podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.PortMappings) + podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings) // The network may have already been torn down, so don't fail here, just log if err := r.netPlugin.TearDownPod(podNetwork); err != nil { @@ -232,9 +213,11 @@ func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) { } func (c *Container) getContainerNetworkInfo(data *inspect.ContainerInspectData) *inspect.ContainerInspectData { - if c.state.NetNS != nil { + if c.state.NetNS != nil && len(c.state.NetworkStatus) > 0 { + // Report network settings from the first pod network + result := c.state.NetworkStatus[0] // Go through our IP addresses - for _, ctrIP := range c.state.IPs { + for _, ctrIP := range result.IPs { ipWithMask := ctrIP.Address.String() splitIP := strings.Split(ipWithMask, "/") mask, _ := strconv.Atoi(splitIP[1]) @@ -253,7 +236,7 @@ func (c *Container) getContainerNetworkInfo(data *inspect.ContainerInspectData) data.NetworkSettings.SandboxKey = c.state.NetNS.Path() // Set MAC address of interface linked with network namespace path - for _, i := range c.state.Interfaces { + for _, i := range result.Interfaces { if i.Sandbox == data.NetworkSettings.SandboxKey { data.NetworkSettings.MacAddress = i.Mac } diff --git a/libpod/options.go b/libpod/options.go index c02bd4336..718b44930 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -733,7 +733,7 @@ func WithDependencyCtrs(ctrs []*Container) CtrCreateOption { // namespace with a minimal configuration. // An optional array of port mappings can be provided. // Conflicts with WithNetNSFrom(). -func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool) CtrCreateOption { +func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, networks []string) CtrCreateOption { return func(ctr *Container) error { if ctr.valid { return ErrCtrFinalized @@ -746,6 +746,7 @@ func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool) CtrCr ctr.config.PostConfigureNetNS = postConfigureNetNS ctr.config.CreateNetNS = true ctr.config.PortMappings = portMappings + ctr.config.Networks = networks return nil } diff --git a/libpod/pod.go b/libpod/pod.go index a96628853..fb69787ed 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -4,6 +4,7 @@ import ( "context" "path/filepath" "strings" + "time" "github.com/containers/storage" "github.com/docker/docker/pkg/stringid" @@ -36,6 +37,9 @@ type PodConfig struct { // If true, all containers joined to the pod will use the pod cgroup as // their cgroup parent, and cannot set a different cgroup parent UsePodCgroup bool + + // Time pod was created + CreatedTime time.Time `json:"created"` } // podState represents a pod's state @@ -64,6 +68,11 @@ func (p *Pod) Labels() map[string]string { return labels } +// CreatedTime gets the time when the pod was created +func (p *Pod) CreatedTime() time.Time { + return p.config.CreatedTime +} + // CgroupParent returns the pod's CGroup parent func (p *Pod) CgroupParent() string { return p.config.CgroupParent @@ -92,6 +101,7 @@ func newPod(lockDir string, runtime *Runtime) (*Pod, error) { pod.config = new(PodConfig) pod.config.ID = stringid.GenerateNonCryptoID() pod.config.Labels = make(map[string]string) + pod.config.CreatedTime = time.Now() pod.state = new(podState) pod.runtime = runtime diff --git a/libpod/runtime.go b/libpod/runtime.go index a2ebc4de4..a551c9134 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -133,6 +133,9 @@ type RuntimeConfig struct { CNIPluginDir []string `toml:"cni_plugin_dir"` // HooksDir Path to the directory containing hooks configuration files HooksDir string `toml:"hooks_dir"` + // CNIDefaultNetwork is the network name of the default CNI network + // to attach pods to + CNIDefaultNetwork string `toml:"cni_default_network,omitempty"` // HooksDirNotExistFatal switches between fatal errors and non-fatal warnings if the configured HooksDir does not exist. HooksDirNotExistFatal bool `toml:"hooks_dir_not_exist_fatal"` // DefaultMountsFile is the path to the default mounts file for testing purposes only @@ -462,7 +465,7 @@ func makeRuntime(runtime *Runtime) (err error) { } // Set up the CNI net plugin - netPlugin, err := ocicni.InitCNI(runtime.config.CNIConfigDir, runtime.config.CNIPluginDir...) + netPlugin, err := ocicni.InitCNI(runtime.config.CNIDefaultNetwork, runtime.config.CNIConfigDir, runtime.config.CNIPluginDir...) if err != nil { return errors.Wrapf(err, "error configuring CNI network plugin") } diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index 8b0c08cd3..d127d753f 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -75,7 +75,7 @@ type CopyOptions struct { // RemoveImage deletes an image from local storage // Images being used by running containers can only be removed if force=true -func (r *Runtime) RemoveImage(ctx context.Context, image *image.Image, force bool) (string, error) { +func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool) (string, error) { r.lock.Lock() defer r.lock.Unlock() @@ -90,50 +90,58 @@ func (r *Runtime) RemoveImage(ctx context.Context, image *image.Image, force boo } imageCtrs := []*Container{} for _, ctr := range ctrs { - if ctr.config.RootfsImageID == image.ID() { + if ctr.config.RootfsImageID == img.ID() { imageCtrs = append(imageCtrs, ctr) } } - if len(imageCtrs) > 0 && len(image.Names()) <= 1 { + if len(imageCtrs) > 0 && len(img.Names()) <= 1 { if force { for _, ctr := range imageCtrs { if err := r.removeContainer(ctx, ctr, true); err != nil { - return "", errors.Wrapf(err, "error removing image %s: container %s using image could not be removed", image.ID(), ctr.ID()) + return "", errors.Wrapf(err, "error removing image %s: container %s using image could not be removed", img.ID(), ctr.ID()) } } } else { - return "", fmt.Errorf("could not remove image %s as it is being used by %d containers", image.ID(), len(imageCtrs)) + return "", fmt.Errorf("could not remove image %s as it is being used by %d containers", img.ID(), len(imageCtrs)) } } - if len(image.Names()) > 1 && !image.InputIsID() { + hasChildren, err := img.IsParent() + if err != nil { + return "", err + } + + if (len(img.Names()) > 1 && !img.InputIsID()) || hasChildren { // If the image has multiple reponames, we do not technically delete // the image. we figure out which repotag the user is trying to refer // to and untag it. - repoName, err := image.MatchRepoTag(image.InputName) + repoName, err := img.MatchRepoTag(img.InputName) + if hasChildren && err == image.ErrRepoTagNotFound { + return "", errors.Errorf("unable to delete %q (cannot be forced) - image has dependent child images", img.ID()) + } if err != nil { return "", err } - if err := image.UntagImage(repoName); err != nil { + if err := img.UntagImage(repoName); err != nil { return "", err } return fmt.Sprintf("Untagged: %s", repoName), nil - } else if len(image.Names()) > 1 && image.InputIsID() && !force { + } else if len(img.Names()) > 1 && img.InputIsID() && !force { // If the user requests to delete an image by ID and the image has multiple // reponames and no force is applied, we error out. - return "", fmt.Errorf("unable to delete %s (must force) - image is referred to in multiple tags", image.ID()) + return "", fmt.Errorf("unable to delete %s (must force) - image is referred to in multiple tags", img.ID()) } - err = image.Remove(force) + err = img.Remove(force) if err != nil && errors.Cause(err) == storage.ErrImageUsedByContainer { - if errStorage := r.rmStorageContainers(force, image); errStorage == nil { + if errStorage := r.rmStorageContainers(force, img); errStorage == nil { // Containers associated with the image should be deleted now, // let's try removing the image again. - err = image.Remove(force) + err = img.Remove(force) } else { err = errStorage } } - return image.ID(), err + return img.ID(), err } // Remove containers that are in storage rather than Podman. diff --git a/libpod/runtime_pod.go b/libpod/runtime_pod.go index 34925c2d5..f5a2b017b 100644 --- a/libpod/runtime_pod.go +++ b/libpod/runtime_pod.go @@ -2,6 +2,9 @@ package libpod import ( "context" + "time" + + "github.com/pkg/errors" ) // Contains the public Runtime API for pods @@ -93,3 +96,36 @@ func (r *Runtime) Pods(filters ...PodFilter) ([]*Pod, error) { return podsFiltered, nil } + +// GetAllPods retrieves all pods +func (r *Runtime) GetAllPods() ([]*Pod, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + if !r.valid { + return nil, ErrRuntimeStopped + } + + return r.state.AllPods() +} + +// GetLatestPod returns a pod object of the latest created pod. +func (r *Runtime) GetLatestPod() (*Pod, error) { + lastCreatedIndex := -1 + var lastCreatedTime time.Time + pods, err := r.GetAllPods() + if err != nil { + return nil, errors.Wrapf(err, "unable to get all pods") + } + if len(pods) == 0 { + return nil, ErrNoSuchPod + } + for podIndex, pod := range pods { + createdTime := pod.config.CreatedTime + if createdTime.After(lastCreatedTime) { + lastCreatedTime = createdTime + lastCreatedIndex = podIndex + } + } + return pods[lastCreatedIndex], nil +} diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go index 35d095ba3..25340abdb 100644 --- a/libpod/runtime_pod_linux.go +++ b/libpod/runtime_pod_linux.go @@ -74,7 +74,7 @@ func (r *Runtime) NewPod(options ...PodCreateOption) (*Pod, error) { return nil, errors.Wrapf(err, "error adding pod to state") } - return nil, ErrNotImplemented + return pod, nil } func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool) error { |