summaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
Diffstat (limited to 'libpod')
-rw-r--r--libpod/container.go42
-rw-r--r--libpod/container_internal.go4
-rw-r--r--libpod/container_internal_linux.go4
-rw-r--r--libpod/image/image.go107
-rw-r--r--libpod/image/parts.go4
-rw-r--r--libpod/image/pull.go36
-rw-r--r--libpod/networking_linux.go77
-rw-r--r--libpod/options.go3
-rw-r--r--libpod/pod.go10
-rw-r--r--libpod/runtime.go5
-rw-r--r--libpod/runtime_img.go36
-rw-r--r--libpod/runtime_pod.go36
-rw-r--r--libpod/runtime_pod_linux.go2
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 {