summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Dockerfile4
-rw-r--r--Dockerfile.CentOS4
-rw-r--r--Dockerfile.Fedora5
-rw-r--r--Makefile14
-rw-r--r--cmd/podman/create.go52
-rw-r--r--cmd/podman/images.go28
-rw-r--r--cmd/podman/main.go13
-rw-r--r--cmd/podman/pod.go25
-rw-r--r--cmd/podman/pod_create.go109
-rw-r--r--cmd/podman/pod_ps.go600
-rw-r--r--cmd/podman/pod_rm.go103
-rw-r--r--cmd/podman/ps.go2
-rw-r--r--cmd/podman/run.go14
-rw-r--r--cmd/podman/sigproxy.go2
-rw-r--r--cmd/podman/start.go2
-rw-r--r--completions/bash/podman145
-rw-r--r--contrib/python/examples/eg_attach.py13
-rw-r--r--contrib/python/podman/libs/_containers_attach.py98
-rw-r--r--contrib/python/podman/libs/_containers_start.py82
-rw-r--r--contrib/python/podman/libs/containers.py9
-rw-r--r--contrib/python/podman/libs/images.py8
-rw-r--r--contrib/python/test/test_containers.py11
-rw-r--r--contrib/python/test/test_images.py1
-rw-r--r--docs/podman-pod-create.1.md60
-rw-r--r--docs/podman-pod-ps.1.md165
-rw-r--r--docs/podman-pod-rm.1.md42
-rw-r--r--docs/podman-pod.1.md21
-rw-r--r--docs/podman.1.md4
-rwxr-xr-xhack/apparmor_tag.sh7
-rwxr-xr-xhack/dind33
-rwxr-xr-xhack/ostree_tag.sh2
-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
-rw-r--r--pkg/apparmor/aaparser.go90
-rw-r--r--pkg/apparmor/aaparser_test.go75
-rw-r--r--pkg/apparmor/apparmor.go54
-rw-r--r--pkg/apparmor/apparmor_linux.go110
-rw-r--r--pkg/apparmor/apparmor_unsupported.go15
-rw-r--r--pkg/spec/createconfig.go24
-rw-r--r--pkg/spec/spec.go7
-rw-r--r--pkg/varlinkapi/containers.go14
-rw-r--r--test/e2e/libpod_suite_test.go46
-rw-r--r--test/e2e/pod_create_test.go84
-rw-r--r--test/e2e/pod_ps_test.go227
-rw-r--r--test/e2e/pod_rm_test.go168
-rw-r--r--test/e2e/pull_test.go2
-rw-r--r--test/e2e/rmi_test.go96
-rw-r--r--vendor.conf2
-rw-r--r--vendor/github.com/containernetworking/cni/README.md15
-rw-r--r--vendor/github.com/containernetworking/cni/libcni/api.go244
-rw-r--r--vendor/github.com/containernetworking/cni/libcni/conf.go3
-rw-r--r--vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go42
-rw-r--r--vendor/github.com/containernetworking/cni/pkg/invoke/exec.go104
-rw-r--r--vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go4
-rw-r--r--vendor/github.com/containernetworking/cni/pkg/types/current/types.go6
-rw-r--r--vendor/github.com/containernetworking/cni/pkg/types/types.go12
-rw-r--r--vendor/github.com/containernetworking/cni/pkg/version/plugin.go59
-rw-r--r--vendor/github.com/containernetworking/cni/pkg/version/version.go4
-rw-r--r--vendor/github.com/containers/storage/pkg/archive/example_changes.go97
-rw-r--r--vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go410
-rw-r--r--vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go11
-rw-r--r--vendor/github.com/cri-o/ocicni/pkg/ocicni/types_unix.go10
-rw-r--r--vendor/github.com/cri-o/ocicni/pkg/ocicni/types_windows.go10
-rw-r--r--vendor/github.com/cri-o/ocicni/pkg/ocicni/util.go34
-rw-r--r--vendor/github.com/cri-o/ocicni/pkg/ocicni/util_linux.go71
-rw-r--r--vendor/github.com/cri-o/ocicni/pkg/ocicni/util_unsupported.go19
-rw-r--r--vendor/github.com/sirupsen/logrus/hooks/syslog/README.md39
-rw-r--r--vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go54
79 files changed, 3701 insertions, 605 deletions
diff --git a/Dockerfile b/Dockerfile
index 53db6a3bc..2a65d95a7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -115,3 +115,7 @@ COPY test/policy.json /etc/containers/policy.json
COPY test/redhat_sigstore.yaml /etc/containers/registries.d/registry.access.redhat.com.yaml
WORKDIR /go/src/github.com/projectatomic/libpod
+
+# Wrap all commands in the "docker-in-docker" script to allow nested containers,
+# and allow testing of apparmor.
+ENTRYPOINT ["./hack/dind"]
diff --git a/Dockerfile.CentOS b/Dockerfile.CentOS
index ed79042e7..f17468b61 100644
--- a/Dockerfile.CentOS
+++ b/Dockerfile.CentOS
@@ -80,3 +80,7 @@ COPY test/policy.json /etc/containers/policy.json
COPY test/redhat_sigstore.yaml /etc/containers/registries.d/registry.access.redhat.com.yaml
WORKDIR /go/src/github.com/projectatomic/libpod
+
+# Wrap all commands in the "docker-in-docker" script to allow nested containers,
+# and allow testing of apparmor.
+ENTRYPOINT ["./hack/dind"]
diff --git a/Dockerfile.Fedora b/Dockerfile.Fedora
index 6415db32b..bc8466848 100644
--- a/Dockerfile.Fedora
+++ b/Dockerfile.Fedora
@@ -83,4 +83,9 @@ COPY test/redhat_sigstore.yaml /etc/containers/registries.d/registry.access.redh
# Install varlink stuff
RUN pip3 install varlink
+
WORKDIR /go/src/github.com/projectatomic/libpod
+
+# Wrap all commands in the "docker-in-docker" script to allow nested containers,
+# and allow testing of apparmor.
+ENTRYPOINT ["./hack/dind"]
diff --git a/Makefile b/Makefile
index ee0c37ee9..4a1a34316 100644
--- a/Makefile
+++ b/Makefile
@@ -17,7 +17,7 @@ ETCDIR ?= ${DESTDIR}/etc
ETCDIR_LIBPOD ?= ${ETCDIR}/crio
TMPFILESDIR ?= ${PREFIX}/lib/tmpfiles.d
SYSTEMDDIR ?= ${PREFIX}/lib/systemd/system
-BUILDTAGS ?= seccomp $(shell hack/btrfs_tag.sh) $(shell hack/libdm_tag.sh) $(shell hack/btrfs_installed_tag.sh) $(shell hack/ostree_tag.sh) $(shell hack/selinux_tag.sh) varlink
+BUILDTAGS ?= seccomp $(shell hack/btrfs_tag.sh) $(shell hack/libdm_tag.sh) $(shell hack/btrfs_installed_tag.sh) $(shell hack/ostree_tag.sh) $(shell hack/selinux_tag.sh) $(shell hack/apparmor_tag.sh) varlink
BUILDTAGS_CROSS ?= containers_image_openpgp containers_image_ostree_stub exclude_graphdriver_btrfs exclude_graphdriver_devicemapper exclude_graphdriver_overlay
ifneq (,$(findstring varlink,$(BUILDTAGS)))
PODMAN_VARLINK_DEPENDENCIES = cmd/podman/varlink/ioprojectatomicpodman.go
@@ -38,6 +38,8 @@ BUILD_INFO ?= $(shell date +%s)
LDFLAGS_PODMAN ?= $(LDFLAGS) -X main.gitCommit=$(GIT_COMMIT) -X main.buildInfo=$(BUILD_INFO)
ISODATE ?= $(shell date --iso-8601)
LIBSECCOMP_COMMIT := release-2.3
+# Wrapper to setup mounts required by AppArmor
+ENTRYPOINT := ./hack/dind
# If GOPATH not specified, use one in the local directory
ifeq ($(GOPATH),)
@@ -137,13 +139,13 @@ libpodimage:
docker build -t ${LIBPOD_IMAGE} .
dbuild: libpodimage
- docker run --name=${LIBPOD_INSTANCE} --privileged ${LIBPOD_IMAGE} -v ${PWD}:/go/src/${PROJECT} --rm make binaries
+ docker run --name=${LIBPOD_INSTANCE} --privileged ${LIBPOD_IMAGE} -v ${PWD}:/go/src/${PROJECT} --rm ${ENTRYPOINT} make binaries
test: libpodimage
- docker run -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e TRAVIS -t --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${LIBPOD_IMAGE} make clean all localunit localintegration
+ docker run -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e TRAVIS -t --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${LIBPOD_IMAGE} ${ENTRYPOINT} make clean all localunit localintegration
integration: libpodimage
- docker run -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e TRAVIS -t --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${LIBPOD_IMAGE} make clean all localintegration
+ docker run -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e TRAVIS -t --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${LIBPOD_IMAGE} ${ENTRYPOINT} make clean all localintegration
integration.fedora:
DIST=Fedora sh .papr_prepare.sh
@@ -152,10 +154,10 @@ integration.centos:
DIST=CentOS sh .papr_prepare.sh
shell: libpodimage
- docker run -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e TRAVIS -it --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${LIBPOD_IMAGE} sh
+ docker run --tmpfs -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e TRAVIS -it --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${LIBPOD_IMAGE} ${ENTRYPOINT} sh
testunit: libpodimage
- docker run -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e TRAVIS -t --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${LIBPOD_IMAGE} make localunit
+ docker run -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e TRAVIS -t --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${LIBPOD_IMAGE} ${ENTRYPOINT} make localunit
localunit: varlink_generate
$(GO) test -tags "$(BUILDTAGS)" -cover $(PACKAGES)
diff --git a/cmd/podman/create.go b/cmd/podman/create.go
index d61f85442..6a70e3f43 100644
--- a/cmd/podman/create.go
+++ b/cmd/podman/create.go
@@ -19,9 +19,11 @@ import (
"github.com/projectatomic/libpod/libpod"
"github.com/projectatomic/libpod/libpod/image"
ann "github.com/projectatomic/libpod/pkg/annotations"
+ "github.com/projectatomic/libpod/pkg/apparmor"
"github.com/projectatomic/libpod/pkg/inspect"
cc "github.com/projectatomic/libpod/pkg/spec"
"github.com/projectatomic/libpod/pkg/util"
+ libpodVersion "github.com/projectatomic/libpod/version"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
@@ -194,6 +196,56 @@ func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error {
}
}
+ if config.ApparmorProfile == "" {
+ // Unless specified otherwise, make sure that the default AppArmor
+ // profile is installed. To avoid redundantly loading the profile
+ // on each invocation, check if it's loaded before installing it.
+ // Suffix the profile with the current libpod version to allow
+ // loading the new, potentially updated profile after an update.
+ profile := fmt.Sprintf("%s-%s", apparmor.DefaultLibpodProfile, libpodVersion.Version)
+
+ loadProfile := func() error {
+ isLoaded, err := apparmor.IsLoaded(profile)
+ if err != nil {
+ return err
+ }
+ if !isLoaded {
+ err = apparmor.InstallDefault(profile)
+ if err != nil {
+ return err
+ }
+
+ }
+ return nil
+ }
+
+ if err := loadProfile(); err != nil {
+ switch err {
+ case apparmor.ErrApparmorUnsupported:
+ // do not set the profile when AppArmor isn't supported
+ logrus.Debugf("AppArmor is not supported: setting empty profile")
+ default:
+ return err
+ }
+ } else {
+ logrus.Infof("Sucessfully loaded AppAmor profile '%s'", profile)
+ config.ApparmorProfile = profile
+ }
+ } else {
+ isLoaded, err := apparmor.IsLoaded(config.ApparmorProfile)
+ if err != nil {
+ switch err {
+ case apparmor.ErrApparmorUnsupported:
+ return fmt.Errorf("profile specified but AppArmor is not supported")
+ default:
+ return fmt.Errorf("error checking if AppArmor profile is loaded: %v", err)
+ }
+ }
+ if !isLoaded {
+ return fmt.Errorf("specified AppArmor profile '%s' is not loaded", config.ApparmorProfile)
+ }
+ }
+
if config.SeccompProfilePath == "" {
if _, err := os.Stat(libpod.SeccompOverridePath); err == nil {
config.SeccompProfilePath = libpod.SeccompOverridePath
diff --git a/cmd/podman/images.go b/cmd/podman/images.go
index fdf2eb02c..092326b1f 100644
--- a/cmd/podman/images.go
+++ b/cmd/podman/images.go
@@ -7,7 +7,8 @@ import (
"strings"
"time"
- "github.com/containers/storage"
+ "github.com/sirupsen/logrus"
+
"github.com/docker/go-units"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
@@ -259,12 +260,16 @@ func sortImagesOutput(sortBy string, imagesOutput imagesSorted) imagesSorted {
}
// getImagesTemplateOutput returns the images information to be printed in human readable format
-func getImagesTemplateOutput(ctx context.Context, runtime *libpod.Runtime, images []*image.Image, opts imagesOptions, storageLayers []storage.Layer) (imagesOutput imagesSorted) {
+func getImagesTemplateOutput(ctx context.Context, runtime *libpod.Runtime, images []*image.Image, opts imagesOptions) (imagesOutput imagesSorted) {
for _, img := range images {
// If all is false and the image doesn't have a name, check to see if the top layer of the image is a parent
// to another image's top layer. If it is, then it is an intermediate image so don't print out if the --all flag
// is not set.
- if !opts.all && len(img.Names()) == 0 && !layerIsLeaf(storageLayers, img.TopLayer()) {
+ isParent, err := img.IsParent()
+ if err != nil {
+ logrus.Errorf("error checking if image is a parent %q: %v", img.ID(), err)
+ }
+ if !opts.all && len(img.Names()) == 0 && isParent {
continue
}
createdTime := img.Created()
@@ -325,17 +330,13 @@ func generateImagesOutput(ctx context.Context, runtime *libpod.Runtime, images [
return nil
}
var out formats.Writer
- storageLayers, err := runtime.GetLayers()
- if err != nil {
- return errors.Wrap(err, "error getting layers from store")
- }
switch opts.format {
case formats.JSONString:
imagesOutput := getImagesJSONOutput(ctx, runtime, images)
out = formats.JSONStructArray{Output: imagesToGeneric([]imagesTemplateParams{}, imagesOutput)}
default:
- imagesOutput := getImagesTemplateOutput(ctx, runtime, images, opts, storageLayers)
+ imagesOutput := getImagesTemplateOutput(ctx, runtime, images, opts)
out = formats.StdoutTemplateArray{Output: imagesToGeneric(imagesOutput, []imagesJSONParams{}), Template: opts.outputformat, Fields: imagesOutput[0].HeaderMap()}
}
return formats.Writer(out).Out()
@@ -391,14 +392,3 @@ func CreateFilterFuncs(ctx context.Context, r *libpod.Runtime, c *cli.Context, i
}
return filterFuncs, nil
}
-
-// layerIsLeaf goes through the layers in the store and checks if "layer" is
-// the parent of any other layer in store.
-func layerIsLeaf(storageLayers []storage.Layer, layer string) bool {
- for _, storeLayer := range storageLayers {
- if storeLayer.Parent == layer {
- return false
- }
- }
- return true
-}
diff --git a/cmd/podman/main.go b/cmd/podman/main.go
index 4d841e0f2..3dbf196c2 100644
--- a/cmd/podman/main.go
+++ b/cmd/podman/main.go
@@ -14,7 +14,9 @@ import (
"github.com/projectatomic/libpod/pkg/rootless"
"github.com/projectatomic/libpod/version"
"github.com/sirupsen/logrus"
+ lsyslog "github.com/sirupsen/logrus/hooks/syslog"
"github.com/urfave/cli"
+ "log/syslog"
)
// This is populated by the Makefile from the VERSION file
@@ -69,6 +71,7 @@ func main() {
mountCommand,
pauseCommand,
psCommand,
+ podCommand,
portCommand,
pullCommand,
pushCommand,
@@ -94,6 +97,12 @@ func main() {
}
app.Before = func(c *cli.Context) error {
+ if c.GlobalBool("syslog") {
+ hook, err := lsyslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
+ if err == nil {
+ logrus.AddHook(hook)
+ }
+ }
logLevel := c.GlobalString("log-level")
if logLevel != "" {
level, err := logrus.ParseLevel(logLevel)
@@ -187,6 +196,10 @@ func main() {
Name: "storage-opt",
Usage: "used to pass an option to the storage driver",
},
+ cli.BoolFlag{
+ Name: "syslog",
+ Usage: "output logging information to syslog as well as the console",
+ },
}
if _, err := os.Stat("/etc/containers/registries.conf"); err != nil {
if os.IsNotExist(err) {
diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go
new file mode 100644
index 000000000..6cf2920a5
--- /dev/null
+++ b/cmd/podman/pod.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "github.com/urfave/cli"
+)
+
+var (
+ podDescription = `
+ podman pod
+
+ Manage container pods.
+ Pods are a group of one or more containers sharing the same network, pid and ipc namespaces.
+`
+ podCommand = cli.Command{
+ Name: "pod",
+ Usage: "Manage pods",
+ Description: podDescription,
+ UseShortOptionHandling: true,
+ Subcommands: []cli.Command{
+ podCreateCommand,
+ podPsCommand,
+ podRmCommand,
+ },
+ }
+)
diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go
new file mode 100644
index 000000000..f86faa409
--- /dev/null
+++ b/cmd/podman/pod_create.go
@@ -0,0 +1,109 @@
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/pkg/errors"
+ "github.com/projectatomic/libpod/cmd/podman/libpodruntime"
+ "github.com/projectatomic/libpod/libpod"
+ "github.com/sirupsen/logrus"
+ "github.com/urfave/cli"
+)
+
+var podCreateDescription = "Creates a new empty pod. The pod ID is then" +
+ " printed to stdout. You can then start it at any time with the" +
+ " podman pod start <pod_id> command. The pod will be created with the" +
+ " initial state 'created'."
+
+var podCreateFlags = []cli.Flag{
+ cli.StringFlag{
+ Name: "cgroup-parent",
+ Usage: "Set parent cgroup for the pod",
+ },
+ cli.StringSliceFlag{
+ Name: "label-file",
+ Usage: "Read in a line delimited file of labels (default [])",
+ },
+ cli.StringSliceFlag{
+ Name: "label, l",
+ Usage: "Set metadata on pod (default [])",
+ },
+ cli.StringFlag{
+ Name: "name, n",
+ Usage: "Assign a name to the pod",
+ },
+ cli.StringFlag{
+ Name: "pod-id-file",
+ Usage: "Write the pod ID to the file",
+ },
+}
+
+var podCreateCommand = cli.Command{
+ Name: "create",
+ Usage: "create a new empty pod",
+ Description: podCreateDescription,
+ Flags: podCreateFlags,
+ Action: podCreateCmd,
+ SkipArgReorder: true,
+ UseShortOptionHandling: true,
+}
+
+func podCreateCmd(c *cli.Context) error {
+ var options []libpod.PodCreateOption
+ var err error
+
+ if err = validateFlags(c, createFlags); err != nil {
+ return err
+ }
+
+ runtime, err := libpodruntime.GetRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "error creating libpod runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ if c.IsSet("pod-id-file") {
+ if _, err = os.Stat(c.String("pod-id-file")); err == nil {
+ return errors.Errorf("pod id file exists. ensure another pod is not using it or delete %s", c.String("pod-id-file"))
+ }
+ if err = libpod.WriteFile("", c.String("pod-id-file")); err != nil {
+ return errors.Wrapf(err, "unable to write pod id file %s", c.String("pod-id-file"))
+ }
+ }
+
+ if c.IsSet("cgroup-parent") {
+ options = append(options, libpod.WithPodCgroupParent(c.String("cgroup-parent")))
+ }
+
+ labels, err := getAllLabels(c.StringSlice("label-file"), c.StringSlice("label"))
+ if err != nil {
+ return errors.Wrapf(err, "unable to process labels")
+ }
+ if len(labels) != 0 {
+ options = append(options, libpod.WithPodLabels(labels))
+ }
+
+ if c.IsSet("name") {
+ options = append(options, libpod.WithPodName(c.String("name")))
+ }
+
+ // always have containers use pod cgroups
+ options = append(options, libpod.WithPodCgroups())
+
+ pod, err := runtime.NewPod(options...)
+ if err != nil {
+ return err
+ }
+
+ if c.IsSet("pod-id-file") {
+ err = libpod.WriteFile(pod.ID(), c.String("pod-id-file"))
+ if err != nil {
+ logrus.Error(err)
+ }
+ }
+
+ fmt.Printf("%s\n", pod.ID())
+
+ return nil
+}
diff --git a/cmd/podman/pod_ps.go b/cmd/podman/pod_ps.go
new file mode 100644
index 000000000..470810901
--- /dev/null
+++ b/cmd/podman/pod_ps.go
@@ -0,0 +1,600 @@
+package main
+
+import (
+ "reflect"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/docker/go-units"
+ "github.com/pkg/errors"
+ "github.com/projectatomic/libpod/cmd/podman/batchcontainer"
+ "github.com/projectatomic/libpod/cmd/podman/formats"
+ "github.com/projectatomic/libpod/cmd/podman/libpodruntime"
+ "github.com/projectatomic/libpod/libpod"
+ "github.com/projectatomic/libpod/pkg/util"
+ "github.com/urfave/cli"
+)
+
+const (
+ STOPPED = "Stopped"
+ RUNNING = "Running"
+ PAUSED = "Paused"
+ EXITED = "Exited"
+ ERROR = "Error"
+ CREATED = "Created"
+ NUM_CTR_INFO = 10
+)
+
+var (
+ bc_opts batchcontainer.PsOptions
+)
+
+type podPsCtrInfo struct {
+ Name string `"json:name,omitempty"`
+ Id string `"json:id,omitempty"`
+ Status string `"json:status,omitempty"`
+}
+
+type podPsOptions struct {
+ NoTrunc bool
+ Format string
+ Sort string
+ Quiet bool
+ NumberOfContainers bool
+ Cgroup bool
+ NamesOfContainers bool
+ IdsOfContainers bool
+ StatusOfContainers bool
+}
+
+type podPsTemplateParams struct {
+ Created string
+ ID string
+ Name string
+ NumberOfContainers int
+ Status string
+ Cgroup string
+ UsePodCgroup bool
+ ContainerInfo string
+}
+
+// podPsJSONParams is used as a base structure for the psParams
+// If template output is requested, podPsJSONParams will be converted to
+// podPsTemplateParams.
+// podPsJSONParams will be populated by data from libpod.Container,
+// the members of the struct are the sama data types as their sources.
+type podPsJSONParams struct {
+ CreatedAt time.Time `json:"createdAt"`
+ ID string `json:"id"`
+ Name string `json:"name"`
+ NumberOfContainers int `json:"numberofcontainers"`
+ Status string `json:"status"`
+ CtrsInfo []podPsCtrInfo `json:"containerinfo,omitempty"`
+ Cgroup string `json:"cgroup,omitempty"`
+ UsePodCgroup bool `json:"podcgroup,omitempty"`
+}
+
+// Type declaration and functions for sorting the pod PS output
+type podPsSorted []podPsJSONParams
+
+func (a podPsSorted) Len() int { return len(a) }
+func (a podPsSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+
+type podPsSortedCreated struct{ podPsSorted }
+
+func (a podPsSortedCreated) Less(i, j int) bool {
+ return a.podPsSorted[i].CreatedAt.After(a.podPsSorted[j].CreatedAt)
+}
+
+type podPsSortedId struct{ podPsSorted }
+
+func (a podPsSortedId) Less(i, j int) bool { return a.podPsSorted[i].ID < a.podPsSorted[j].ID }
+
+type podPsSortedNumber struct{ podPsSorted }
+
+func (a podPsSortedNumber) Less(i, j int) bool {
+ return len(a.podPsSorted[i].CtrsInfo) < len(a.podPsSorted[j].CtrsInfo)
+}
+
+type podPsSortedName struct{ podPsSorted }
+
+func (a podPsSortedName) Less(i, j int) bool { return a.podPsSorted[i].Name < a.podPsSorted[j].Name }
+
+type podPsSortedStatus struct{ podPsSorted }
+
+func (a podPsSortedStatus) Less(i, j int) bool {
+ return a.podPsSorted[i].Status < a.podPsSorted[j].Status
+}
+
+var (
+ podPsFlags = []cli.Flag{
+ cli.BoolFlag{
+ Name: "cgroup",
+ Usage: "Print the Cgroup information of the pod",
+ },
+ cli.BoolFlag{
+ Name: "ctr-names",
+ Usage: "Display the container names",
+ },
+ cli.BoolFlag{
+ Name: "ctr-ids",
+ Usage: "Display the container UUIDs. If no-trunc is not set they will be truncated",
+ },
+ cli.BoolFlag{
+ Name: "ctr-status",
+ Usage: "Display the container status",
+ },
+ cli.StringFlag{
+ Name: "filter, f",
+ Usage: "Filter output based on conditions given",
+ },
+ cli.StringFlag{
+ Name: "format",
+ Usage: "Pretty-print pods to JSON or using a Go template",
+ },
+ cli.BoolFlag{
+ Name: "latest, l",
+ Usage: "Show the latest pod created",
+ },
+ cli.BoolFlag{
+ Name: "no-trunc",
+ Usage: "Do not truncate pod and container IDs",
+ },
+ cli.BoolFlag{
+ Name: "quiet, q",
+ Usage: "Print the numeric IDs of the pods only",
+ },
+ cli.StringFlag{
+ Name: "sort",
+ Usage: "Sort output by created, id, name, or number",
+ Value: "created",
+ },
+ }
+ podPsDescription = "List all pods on system including their names, ids and current state."
+ podPsCommand = cli.Command{
+ Name: "ps",
+ Aliases: []string{"ls", "list"},
+ Usage: "List pods",
+ Description: podPsDescription,
+ Flags: podPsFlags,
+ Action: podPsCmd,
+ UseShortOptionHandling: true,
+ }
+)
+
+func podPsCmd(c *cli.Context) error {
+ if err := validateFlags(c, podPsFlags); err != nil {
+ return err
+ }
+
+ if err := podPsCheckFlagsPassed(c); err != nil {
+ return errors.Wrapf(err, "error with flags passed")
+ }
+
+ runtime, err := libpodruntime.GetRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "error creating libpod runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ if len(c.Args()) > 0 {
+ return errors.Errorf("too many arguments, ps takes no arguments")
+ }
+
+ opts := podPsOptions{
+ NoTrunc: c.Bool("no-trunc"),
+ Quiet: c.Bool("quiet"),
+ Sort: c.String("sort"),
+ IdsOfContainers: c.Bool("ctr-ids"),
+ NamesOfContainers: c.Bool("ctr-names"),
+ StatusOfContainers: c.Bool("ctr-status"),
+ }
+
+ opts.Format = genPodPsFormat(c)
+
+ var filterFuncs []libpod.PodFilter
+ if c.String("filter") != "" {
+ filters := strings.Split(c.String("filter"), ",")
+ for _, f := range filters {
+ filterSplit := strings.Split(f, "=")
+ if len(filterSplit) < 2 {
+ return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
+ }
+ generatedFunc, err := generatePodFilterFuncs(filterSplit[0], filterSplit[1], runtime)
+ if err != nil {
+ return errors.Wrapf(err, "invalid filter")
+ }
+ filterFuncs = append(filterFuncs, generatedFunc)
+ }
+ }
+
+ var pods []*libpod.Pod
+ if c.IsSet("latest") {
+ pod, err := runtime.GetLatestPod()
+ if err != nil {
+ return err
+ }
+ pods = append(pods, pod)
+ } else {
+ pods, err = runtime.GetAllPods()
+ if err != nil {
+ return err
+ }
+ }
+
+ podsFiltered := make([]*libpod.Pod, 0, len(pods))
+ for _, pod := range pods {
+ include := true
+ for _, filter := range filterFuncs {
+ include = include && filter(pod)
+ }
+
+ if include {
+ podsFiltered = append(podsFiltered, pod)
+ }
+ }
+
+ return generatePodPsOutput(podsFiltered, opts, runtime)
+}
+
+// podPsCheckFlagsPassed checks if mutually exclusive flags are passed together
+func podPsCheckFlagsPassed(c *cli.Context) error {
+ // quiet, and format with Go template are mutually exclusive
+ flags := 0
+ if c.Bool("quiet") {
+ flags++
+ }
+ if c.IsSet("format") && c.String("format") != formats.JSONString {
+ flags++
+ }
+ if flags > 1 {
+ return errors.Errorf("quiet and format with Go template are mutually exclusive")
+ }
+ return nil
+}
+
+func generatePodFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) (func(pod *libpod.Pod) bool, error) {
+ switch filter {
+ case "ctr-ids":
+ return func(p *libpod.Pod) bool {
+ ctrIds, err := p.AllContainersByID()
+ if err != nil {
+ return false
+ }
+ return util.StringInSlice(filterValue, ctrIds)
+ }, nil
+ case "ctr-names":
+ return func(p *libpod.Pod) bool {
+ ctrs, err := p.AllContainers()
+ if err != nil {
+ return false
+ }
+ for _, ctr := range ctrs {
+ if filterValue == ctr.Name() {
+ return true
+ }
+ }
+ return false
+ }, nil
+ case "ctr-number":
+ return func(p *libpod.Pod) bool {
+ ctrIds, err := p.AllContainersByID()
+ if err != nil {
+ return false
+ }
+
+ fVint, err2 := strconv.Atoi(filterValue)
+ if err2 != nil {
+ return false
+ }
+ return len(ctrIds) == fVint
+ }, nil
+ case "ctr-status":
+ if !util.StringInSlice(filterValue, []string{"created", "restarting", "running", "paused", "exited", "unknown"}) {
+ return nil, errors.Errorf("%s is not a valid status", filterValue)
+ }
+ return func(p *libpod.Pod) bool {
+ ctrs, err := p.AllContainers()
+ if err != nil {
+ return false
+ }
+ for _, ctr := range ctrs {
+ status, err := ctr.State()
+ if err != nil {
+ return false
+ }
+ state := status.String()
+ if status == libpod.ContainerStateConfigured {
+ state = "created"
+ }
+ if state == filterValue {
+ return true
+ }
+ }
+ return false
+ }, nil
+ case "id":
+ return func(p *libpod.Pod) bool {
+ return strings.Contains(p.ID(), filterValue)
+ }, nil
+ case "name":
+ return func(p *libpod.Pod) bool {
+ return strings.Contains(p.Name(), filterValue)
+ }, nil
+ case "status":
+ if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created"}) {
+ return nil, errors.Errorf("%s is not a valid pod status", filterValue)
+ }
+ return func(p *libpod.Pod) bool {
+ ctrs, err := p.AllContainers()
+ if err != nil {
+ return false
+ }
+ status, err := getPodStatus(ctrs)
+ if err != nil {
+ return false
+ }
+ if strings.ToLower(status) == filterValue {
+ return true
+ }
+ return false
+ }, nil
+ }
+ return nil, errors.Errorf("%s is an invalid filter", filter)
+}
+
+// generate the template based on conditions given
+func genPodPsFormat(c *cli.Context) string {
+ format := ""
+ if c.String("format") != "" {
+ // "\t" from the command line is not being recognized as a tab
+ // replacing the string "\t" to a tab character if the user passes in "\t"
+ format = strings.Replace(c.String("format"), `\t`, "\t", -1)
+ } else if c.Bool("quiet") {
+ format = formats.IDString
+ } else {
+ format = "table {{.ID}}\t{{.Name}}\t{{.Status}}\t{{.Created}}"
+ if c.Bool("cgroup") {
+ format += "\t{{.Cgroup}}\t{{.UsePodCgroup}}"
+ }
+ if c.Bool("ctr-names") || c.Bool("ctr-ids") || c.Bool("ctr-status") {
+ format += "\t{{.ContainerInfo}}"
+ } else {
+ format += "\t{{.NumberOfContainers}}"
+ }
+ }
+ return format
+}
+
+func podPsToGeneric(templParams []podPsTemplateParams, JSONParams []podPsJSONParams) (genericParams []interface{}) {
+ if len(templParams) > 0 {
+ for _, v := range templParams {
+ genericParams = append(genericParams, interface{}(v))
+ }
+ return
+ }
+ for _, v := range JSONParams {
+ genericParams = append(genericParams, interface{}(v))
+ }
+ return
+}
+
+// generate the accurate header based on template given
+func (p *podPsTemplateParams) podHeaderMap() map[string]string {
+ v := reflect.Indirect(reflect.ValueOf(p))
+ values := make(map[string]string)
+
+ for i := 0; i < v.NumField(); i++ {
+ key := v.Type().Field(i).Name
+ value := key
+ if value == "ID" {
+ value = "Pod" + value
+ }
+ values[key] = strings.ToUpper(splitCamelCase(value))
+ }
+ return values
+}
+
+func sortPodPsOutput(sortBy string, psOutput podPsSorted) (podPsSorted, error) {
+ switch sortBy {
+ case "created":
+ sort.Sort(podPsSortedCreated{psOutput})
+ case "id":
+ sort.Sort(podPsSortedId{psOutput})
+ case "name":
+ sort.Sort(podPsSortedName{psOutput})
+ case "number":
+ sort.Sort(podPsSortedNumber{psOutput})
+ case "status":
+ sort.Sort(podPsSortedStatus{psOutput})
+ default:
+ return nil, errors.Errorf("invalid option for --sort, options are: id, names, or number")
+ }
+ return psOutput, nil
+}
+
+// getPodTemplateOutput returns the modified container information
+func getPodTemplateOutput(psParams []podPsJSONParams, opts podPsOptions) ([]podPsTemplateParams, error) {
+ var (
+ psOutput []podPsTemplateParams
+ )
+
+ for _, psParam := range psParams {
+ podID := psParam.ID
+ var ctrStr string
+
+ truncated := ""
+ if !opts.NoTrunc {
+ podID = shortID(podID)
+ if len(psParam.CtrsInfo) > NUM_CTR_INFO {
+ psParam.CtrsInfo = psParam.CtrsInfo[:NUM_CTR_INFO]
+ truncated = "..."
+ }
+ }
+ for _, ctrInfo := range psParam.CtrsInfo {
+ ctrStr += "[ "
+ if opts.IdsOfContainers {
+ if opts.NoTrunc {
+ ctrStr += ctrInfo.Id
+ } else {
+ ctrStr += shortID(ctrInfo.Id)
+ }
+ }
+ if opts.NamesOfContainers {
+ ctrStr += ctrInfo.Name + " "
+ }
+ if opts.StatusOfContainers {
+ ctrStr += ctrInfo.Status + " "
+ }
+ ctrStr += "] "
+ }
+ ctrStr += truncated
+ params := podPsTemplateParams{
+ Created: units.HumanDuration(time.Since(psParam.CreatedAt)) + " ago",
+ ID: podID,
+ Name: psParam.Name,
+ Status: psParam.Status,
+ NumberOfContainers: psParam.NumberOfContainers,
+ UsePodCgroup: psParam.UsePodCgroup,
+ Cgroup: psParam.Cgroup,
+ ContainerInfo: ctrStr,
+ }
+
+ psOutput = append(psOutput, params)
+ }
+
+ return psOutput, nil
+}
+
+func getPodStatus(ctrs []*libpod.Container) (string, error) {
+ ctrNum := len(ctrs)
+ if ctrNum == 0 {
+ return CREATED, nil
+ }
+ statuses := map[string]int{
+ STOPPED: 0,
+ RUNNING: 0,
+ PAUSED: 0,
+ CREATED: 0,
+ ERROR: 0,
+ }
+ for _, ctr := range ctrs {
+ state, err := ctr.State()
+ if err != nil {
+ return "", err
+ }
+ switch state {
+ case libpod.ContainerStateStopped:
+ statuses[STOPPED]++
+ case libpod.ContainerStateRunning:
+ statuses[RUNNING]++
+ case libpod.ContainerStatePaused:
+ statuses[PAUSED]++
+ case libpod.ContainerStateCreated, libpod.ContainerStateConfigured:
+ statuses[CREATED]++
+ default:
+ statuses[ERROR]++
+ }
+ }
+
+ if statuses[RUNNING] > 0 {
+ return RUNNING, nil
+ } else if statuses[PAUSED] == ctrNum {
+ return PAUSED, nil
+ } else if statuses[STOPPED] == ctrNum {
+ return EXITED, nil
+ } else if statuses[STOPPED] > 0 {
+ return STOPPED, nil
+ } else if statuses[ERROR] > 0 {
+ return ERROR, nil
+ } else {
+ return CREATED, nil
+ }
+}
+
+// getAndSortPodJSONOutput returns the container info in its raw, sorted form
+func getAndSortPodJSONParams(pods []*libpod.Pod, opts podPsOptions, runtime *libpod.Runtime) ([]podPsJSONParams, error) {
+ var (
+ psOutput []podPsJSONParams
+ )
+
+ for _, pod := range pods {
+ ctrs, err := pod.AllContainers()
+ ctrsInfo := make([]podPsCtrInfo, 0)
+ if err != nil {
+ return nil, err
+ }
+ ctrNum := len(ctrs)
+ status, err := getPodStatus(ctrs)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, ctr := range ctrs {
+ batchInfo, err := batchcontainer.BatchContainerOp(ctr, bc_opts)
+ if err != nil {
+ return nil, err
+ }
+ var status string
+ switch batchInfo.ConState {
+ case libpod.ContainerStateStopped:
+ status = EXITED
+ case libpod.ContainerStateRunning:
+ status = RUNNING
+ case libpod.ContainerStatePaused:
+ status = PAUSED
+ case libpod.ContainerStateCreated, libpod.ContainerStateConfigured:
+ status = CREATED
+ default:
+ status = ERROR
+ }
+ ctrsInfo = append(ctrsInfo, podPsCtrInfo{
+ Name: batchInfo.ConConfig.Name,
+ Id: ctr.ID(),
+ Status: status,
+ })
+ }
+ params := podPsJSONParams{
+ CreatedAt: pod.CreatedTime(),
+ ID: pod.ID(),
+ Name: pod.Name(),
+ Status: status,
+ Cgroup: pod.CgroupParent(),
+ UsePodCgroup: pod.UsePodCgroup(),
+ NumberOfContainers: ctrNum,
+ CtrsInfo: ctrsInfo,
+ }
+
+ psOutput = append(psOutput, params)
+ }
+ return sortPodPsOutput(opts.Sort, psOutput)
+}
+
+func generatePodPsOutput(pods []*libpod.Pod, opts podPsOptions, runtime *libpod.Runtime) error {
+ if len(pods) == 0 && opts.Format != formats.JSONString {
+ return nil
+ }
+ psOutput, err := getAndSortPodJSONParams(pods, opts, runtime)
+ if err != nil {
+ return err
+ }
+ var out formats.Writer
+
+ switch opts.Format {
+ case formats.JSONString:
+ if err != nil {
+ return errors.Wrapf(err, "unable to create JSON for output")
+ }
+ out = formats.JSONStructArray{Output: podPsToGeneric([]podPsTemplateParams{}, psOutput)}
+ default:
+ psOutput, err := getPodTemplateOutput(psOutput, opts)
+ if err != nil {
+ return errors.Wrapf(err, "unable to create output")
+ }
+ out = formats.StdoutTemplateArray{Output: podPsToGeneric(psOutput, []podPsJSONParams{}), Template: opts.Format, Fields: psOutput[0].podHeaderMap()}
+ }
+
+ return formats.Writer(out).Out()
+}
diff --git a/cmd/podman/pod_rm.go b/cmd/podman/pod_rm.go
new file mode 100644
index 000000000..8cc46761e
--- /dev/null
+++ b/cmd/podman/pod_rm.go
@@ -0,0 +1,103 @@
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/pkg/errors"
+ "github.com/projectatomic/libpod/cmd/podman/libpodruntime"
+ "github.com/projectatomic/libpod/libpod"
+ "github.com/urfave/cli"
+)
+
+var (
+ podRmFlags = []cli.Flag{
+ cli.BoolFlag{
+ Name: "force, f",
+ Usage: "Force removal of a running pod by first stopping all containers, then removing all containers in the pod. The default is false",
+ },
+ cli.BoolFlag{
+ Name: "all, a",
+ Usage: "Remove all pods",
+ },
+ LatestFlag,
+ }
+ podRmDescription = "Remove one or more pods"
+ podRmCommand = cli.Command{
+ Name: "rm",
+ Usage: fmt.Sprintf(`podman rm will remove one or more pods from the host. The pod name or ID can be used.
+ A pod with containers will not be removed without --force.
+ If --force is specified, all containers will be stopped, then removed.`),
+ Description: podRmDescription,
+ Flags: podRmFlags,
+ Action: podRmCmd,
+ ArgsUsage: "[POD ...]",
+ UseShortOptionHandling: true,
+ }
+)
+
+// saveCmd saves the image to either docker-archive or oci
+func podRmCmd(c *cli.Context) error {
+ ctx := getContext()
+ if err := validateFlags(c, rmFlags); err != nil {
+ return err
+ }
+
+ if c.Bool("latest") && c.Bool("all") {
+ return errors.Errorf("--all and --latest cannot be used together")
+ }
+
+ runtime, err := libpodruntime.GetRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "could not get runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ args := c.Args()
+
+ if len(args) == 0 && !c.Bool("all") && !c.Bool("latest") {
+ return errors.Errorf("specify one or more pods to remove")
+ }
+
+ var delPods []*libpod.Pod
+ var lastError error
+ if c.IsSet("all") {
+ delPods, err = runtime.GetAllPods()
+ if err != nil {
+ return errors.Wrapf(err, "unable to get pod list")
+ }
+ } else if c.IsSet("latest") {
+ delPod, err := runtime.GetLatestPod()
+ if err != nil {
+ return errors.Wrapf(err, "unable to get latest pod")
+ }
+ delPods = append(delPods, delPod)
+ } else {
+ for _, i := range args {
+ pod, err := runtime.LookupPod(i)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ if lastError != nil {
+ fmt.Fprintln(os.Stderr, lastError)
+ }
+ lastError = errors.Wrapf(err, "unable to find pods %s", i)
+ continue
+ }
+ delPods = append(delPods, pod)
+ }
+ }
+ force := c.IsSet("force")
+
+ for _, pod := range delPods {
+ err = runtime.RemovePod(ctx, pod, force, force)
+ if err != nil {
+ if lastError != nil {
+ fmt.Fprintln(os.Stderr, lastError)
+ }
+ lastError = errors.Wrapf(err, "failed to delete pod %v", pod.ID())
+ } else {
+ fmt.Println(pod.ID())
+ }
+ }
+ return lastError
+}
diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go
index a89523e83..49e43ffac 100644
--- a/cmd/podman/ps.go
+++ b/cmd/podman/ps.go
@@ -519,7 +519,7 @@ func getTemplateOutput(psParams []psJSONParams, opts batchcontainer.PsOptions) (
case libpod.ContainerStateCreated.String(), libpod.ContainerStateConfigured.String():
status = "Created"
default:
- status = "Dead"
+ status = "Error"
}
if !opts.NoTrunc {
diff --git a/cmd/podman/run.go b/cmd/podman/run.go
index 2964605f6..b126c9e73 100644
--- a/cmd/podman/run.go
+++ b/cmd/podman/run.go
@@ -172,6 +172,10 @@ func runCmd(c *cli.Context) error {
if c.IsSet("attach") || c.IsSet("a") {
outputStream = nil
errorStream = nil
+ if !c.Bool("interactive") {
+ inputStream = nil
+ }
+
inputStream = nil
attachTo := c.StringSlice("attach")
@@ -187,13 +191,7 @@ func runCmd(c *cli.Context) error {
return errors.Wrapf(libpod.ErrInvalidArg, "invalid stream %q for --attach - must be one of stdin, stdout, or stderr", stream)
}
}
-
- // If --interactive is set, restore stdin
- if c.Bool("interactive") {
- inputStream = os.Stdin
- }
}
-
if err := startAttachCtr(ctr, outputStream, errorStream, inputStream, c.String("detach-keys"), c.BoolT("sig-proxy"), true); err != nil {
// This means the command did not exist
exitCode = 127
@@ -203,7 +201,7 @@ func runCmd(c *cli.Context) error {
return err
}
- if ecode, err := ctr.ExitCode(); err != nil {
+ if ecode, err := ctr.Wait(); err != nil {
if errors.Cause(err) == libpod.ErrNoSuchCtr {
// The container may have been removed
// Go looking for an exit file
@@ -213,8 +211,6 @@ func runCmd(c *cli.Context) error {
} else {
exitCode = ctrExitCode
}
- } else {
- logrus.Errorf("Unable to get exit code of container %s: %q", ctr.ID(), err)
}
} else {
exitCode = int(ecode)
diff --git a/cmd/podman/sigproxy.go b/cmd/podman/sigproxy.go
index fd1415dc6..388e23439 100644
--- a/cmd/podman/sigproxy.go
+++ b/cmd/podman/sigproxy.go
@@ -25,6 +25,8 @@ func ProxySignals(ctr *libpod.Container) {
if err := ctr.Kill(uint(s.(syscall.Signal))); err != nil {
logrus.Errorf("Error forwarding signal %d to container %s: %v", s, ctr.ID(), err)
+ signal.StopCatch(sigBuffer)
+ syscall.Kill(syscall.Getpid(), s.(syscall.Signal))
}
}
}()
diff --git a/cmd/podman/start.go b/cmd/podman/start.go
index e917d9198..3dde306d7 100644
--- a/cmd/podman/start.go
+++ b/cmd/podman/start.go
@@ -114,7 +114,7 @@ func startCmd(c *cli.Context) error {
return errors.Wrapf(err, "unable to start container %s", ctr.ID())
}
- if ecode, err := ctr.ExitCode(); err != nil {
+ if ecode, err := ctr.Wait(); err != nil {
logrus.Errorf("unable to get exit code of container %s: %q", ctr.ID(), err)
} else {
exitCode = int(ecode)
diff --git a/completions/bash/podman b/completions/bash/podman
index 051853e9e..b01a30aaa 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -31,6 +31,29 @@ __podman_containers() {
__podman_q ps --format "$format" "$@"
}
+
+# __podman_pods returns a list of pods. Additional options to
+# `podman pod ps` may be specified in order to filter the list, e.g.
+# `__podman_containers --filter status=running`
+# By default, only names are returned.
+# Set PODMAN_COMPLETION_SHOW_CONTAINER_IDS=yes to also complete IDs.
+# An optional first option `--id|--name` may be used to limit the
+# output to the IDs or names of matching items. This setting takes
+# precedence over the environment setting.
+__podman_pods() {
+ local format
+ if [ "$1" = "--id" ] ; then
+ format='{{.ID}}'
+ shift
+ elif [ "$1" = "--name" ] ; then
+ format='{{.Names}}'
+ shift
+ else
+ format='{{.Names}}'
+ fi
+ __podman_q pod ps --format "$format" "$@"
+}
+
# __podman_complete_containers applies completion of containers based on the current
# value of `$cur` or the value of the optional first option `--cur`, if given.
# Additional filters may be appended, see `__podman_containers`.
@@ -43,6 +66,23 @@ __podman_complete_containers() {
COMPREPLY=( $(compgen -W "$(__podman_containers "$@")" -- "$current") )
}
+# __podman_complete_pods applies completion of pods based on the current
+# value of `$cur` or the value of the optional first option `--cur`, if given.
+# Additional filters may be appended, see `__podman_pods`.
+__podman_complete_pods() {
+ local current="$cur"
+ if [ "$1" = "--cur" ] ; then
+ current="$2"
+ shift 2
+ fi
+ COMPREPLY=( $(compgen -W "$(__podman_pods "$@")" -- "$current") )
+}
+
+__podman_complete_pod_names() {
+ local names=( $(__podman_q pod ps --format={{.Name}}) )
+ COMPREPLY=( $(compgen -W "${names[*]}" -- "$cur") )
+}
+
__podman_complete_containers_all() {
__podman_complete_containers "$@" --all
}
@@ -915,7 +955,7 @@ _podman_build() {
--runtime)
COMPREPLY=($(compgen -W 'runc runv' -- "$cur"))
;;
- $(__buildah_to_extglob "$options_with_args"))
+ $(__podman_to_extglob "$options_with_args"))
return
;;
esac
@@ -1662,10 +1702,12 @@ _podman_container_run() {
esac
}
+
_podman_create() {
_podman_container_run
}
+
_podman_run() {
_podman_container_run
}
@@ -2013,6 +2055,105 @@ _podman_logout() {
_complete_ "$options_with_args" "$boolean_options"
}
+_podman_pod_create() {
+ local options_with_args="
+ --cgroup-parent
+ --podidfile
+ --label-file
+ --label
+ -l
+ --name
+ "
+
+ local boolean_options="
+ "
+ _complete_ "$options_with_args" "$boolean_options"
+}
+
+__podman_pod_ps() {
+ local options_with_args="
+ -f
+ --filter
+ --format
+ --sort
+ "
+
+ local boolean_options="
+ --cgroup
+ --ctr-ids
+ --ctr-names
+ --ctr-status
+ -q
+ --quiet
+ --no-trunc
+ --labels
+ -l
+ --latest
+ "
+ _complete_ "$options_with_args" "$boolean_options"
+}
+
+_podman_pod_ls() {
+ __podman_pod_ps
+}
+
+_podman_pod_list() {
+ __podman_pod_ps
+}
+
+_podman_pod_ps() {
+ __podman_pod_ps
+}
+
+_podman_pod_rm() {
+ local options_with_args="
+ "
+
+ local boolean_options="
+ -a
+ --all
+ -f
+ --force
+ --latest
+ -l
+ "
+ _complete_ "$options_with_args" "$boolean_options"
+ case "$cur" in
+ -*)
+ COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
+ ;;
+ *)
+ __podman_complete_pod_names
+ ;;
+ esac
+}
+
+_podman_pod() {
+ local boolean_options="
+ --help
+ -h
+ "
+ subcommands="
+ create
+ ps
+ rm
+ "
+ local aliases="
+ list
+ ls
+ "
+ __podman_subcommands "$subcommands $aliases" && return
+
+ case "$cur" in
+ -*)
+ COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
+ ;;
+ *)
+ COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) )
+ ;;
+ esac
+}
+
_podman_podman() {
local options_with_args="
--config -c
@@ -2026,6 +2167,7 @@ _podman_podman() {
local boolean_options="
--help -h
--version -v
+ --syslog
"
commands="
attach
@@ -2048,6 +2190,7 @@ _podman_podman() {
logs
mount
pause
+ pod
port
ps
pull
diff --git a/contrib/python/examples/eg_attach.py b/contrib/python/examples/eg_attach.py
index f8008163f..f5070dc53 100644
--- a/contrib/python/examples/eg_attach.py
+++ b/contrib/python/examples/eg_attach.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-"""Example: Run Alpine container and attach."""
+"""Example: Run top on Alpine container."""
import podman
@@ -8,10 +8,11 @@ print('{}\n'.format(__doc__))
with podman.Client() as client:
id = client.images.pull('alpine:latest')
img = client.images.get(id)
- cntr = img.create()
- cntr.start()
+ cntr = img.create(detach=True, tty=True, command=['/usr/bin/top'])
+ cntr.attach(eot=4)
try:
- cntr.attach()
- except BrokenPipeError:
- print('Container disconnected.')
+ cntr.start()
+ print()
+ except (BrokenPipeError, KeyboardInterrupt):
+ print('\nContainer disconnected.')
diff --git a/contrib/python/podman/libs/_containers_attach.py b/contrib/python/podman/libs/_containers_attach.py
index bd73542b9..df12fa998 100644
--- a/contrib/python/podman/libs/_containers_attach.py
+++ b/contrib/python/podman/libs/_containers_attach.py
@@ -1,16 +1,11 @@
"""Exported method Container.attach()."""
+import collections
import fcntl
-import os
-import select
-import signal
-import socket
+import logging
import struct
import sys
import termios
-import tty
-
-CONMON_BUFSZ = 8192
class Mixin:
@@ -20,10 +15,8 @@ class Mixin:
"""Attach to container's PID1 stdin and stdout.
stderr is ignored.
+ PseudoTTY work is done in start().
"""
- if not self.containerrunning:
- raise Exception('you can only attach to running containers')
-
if stdin is None:
stdin = sys.stdin.fileno()
@@ -41,73 +34,42 @@ class Mixin:
)
# This is the control socket where resizing events are sent to conmon
- ctl_socket = attach['sockets']['control_socket']
+ # attach['sockets']['control_socket']
+ self.pseudo_tty = collections.namedtuple(
+ 'PseudoTTY',
+ ['stdin', 'stdout', 'io_socket', 'control_socket', 'eot'])(
+ stdin,
+ stdout,
+ attach['sockets']['io_socket'],
+ attach['sockets']['control_socket'],
+ eot,
+ )
- def resize_handler(signum, frame):
- """Send the new window size to conmon.
+ @property
+ def resize_handler(self):
+ """Send the new window size to conmon."""
- The method arguments are not used.
- """
- packed = fcntl.ioctl(stdout, termios.TIOCGWINSZ,
+ def wrapped(signum, frame):
+ packed = fcntl.ioctl(self.pseudo_tty.stdout, termios.TIOCGWINSZ,
struct.pack('HHHH', 0, 0, 0, 0))
rows, cols, _, _ = struct.unpack('HHHH', packed)
+ logging.debug('Resize window({}x{}) using {}'.format(
+ rows, cols, self.pseudo_tty.control_socket))
+
# TODO: Need some kind of timeout in case pipe is blocked
- with open(ctl_socket, 'w') as skt:
+ with open(self.pseudo_tty.control_socket, 'w') as skt:
# send conmon window resize message
skt.write('1 {} {}\n'.format(rows, cols))
- def log_handler(signum, frame):
- """Send command to reopen log to conmon.
+ return wrapped
+
+ @property
+ def log_handler(self):
+ """Send command to reopen log to conmon."""
- The method arguments are not used.
- """
- with open(ctl_socket, 'w') as skt:
+ def wrapped(signum, frame):
+ with open(self.pseudo_tty.control_socket, 'w') as skt:
# send conmon reopen log message
skt.write('2\n')
- try:
- # save off the old settings for terminal
- original_attr = termios.tcgetattr(stdout)
- tty.setraw(stdin)
-
- # initialize containers window size
- resize_handler(None, sys._getframe(0))
-
- # catch any resizing events and send the resize info
- # to the control fifo "socket"
- signal.signal(signal.SIGWINCH, resize_handler)
-
- except termios.error:
- original_attr = None
-
- try:
- # TODO: socket.SOCK_SEQPACKET may not be supported in Windows
- with socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) as skt:
- # Prepare socket for communicating with conmon/container
- skt.connect(io_socket)
- skt.sendall(b'\n')
-
- sources = [skt, stdin]
- while sources:
- readable, _, _ = select.select(sources, [], [])
- if skt in readable:
- data = skt.recv(CONMON_BUFSZ)
- if not data:
- sources.remove(skt)
-
- # Remove source marker when writing
- os.write(stdout, data[1:])
-
- if stdin in readable:
- data = os.read(stdin, CONMON_BUFSZ)
- if not data:
- sources.remove(stdin)
-
- skt.sendall(data)
-
- if eot in data:
- sources.clear()
- finally:
- if original_attr:
- termios.tcsetattr(stdout, termios.TCSADRAIN, original_attr)
- signal.signal(signal.SIGWINCH, signal.SIG_DFL)
+ return wrapped
diff --git a/contrib/python/podman/libs/_containers_start.py b/contrib/python/podman/libs/_containers_start.py
new file mode 100644
index 000000000..ad9f32eab
--- /dev/null
+++ b/contrib/python/podman/libs/_containers_start.py
@@ -0,0 +1,82 @@
+"""Exported method Container.start()."""
+import logging
+import os
+import select
+import signal
+import socket
+import sys
+import termios
+import tty
+
+CONMON_BUFSZ = 8192
+
+
+class Mixin:
+ """Publish start() for inclusion in Container class."""
+
+ def start(self):
+ """Start container, return container on success.
+
+ Will block if container has been detached.
+ """
+ with self._client() as podman:
+ results = podman.StartContainer(self.id)
+ logging.debug('Started Container "{}"'.format(
+ results['container']))
+
+ if not hasattr(self, 'pseudo_tty') or self.pseudo_tty is None:
+ return self._refresh(podman)
+
+ logging.debug('Setting up PseudoTTY for Container "{}"'.format(
+ results['container']))
+
+ try:
+ # save off the old settings for terminal
+ tcoldattr = termios.tcgetattr(self.pseudo_tty.stdin)
+ tty.setraw(self.pseudo_tty.stdin)
+
+ # initialize container's window size
+ self.resize_handler(None, sys._getframe(0))
+
+ # catch any resizing events and send the resize info
+ # to the control fifo "socket"
+ signal.signal(signal.SIGWINCH, self.resize_handler)
+
+ except termios.error:
+ tcoldattr = None
+
+ try:
+ # TODO: Is socket.SOCK_SEQPACKET supported in Windows?
+ with socket.socket(socket.AF_UNIX,
+ socket.SOCK_SEQPACKET) as skt:
+ # Prepare socket for use with conmon/container
+ skt.connect(self.pseudo_tty.io_socket)
+
+ sources = [skt, self.pseudo_tty.stdin]
+ while sources:
+ logging.debug('Waiting on sources: {}'.format(sources))
+ readable, _, _ = select.select(sources, [], [])
+
+ if skt in readable:
+ data = skt.recv(CONMON_BUFSZ)
+ if data:
+ # Remove source marker when writing
+ os.write(self.pseudo_tty.stdout, data[1:])
+ else:
+ sources.remove(skt)
+
+ if self.pseudo_tty.stdin in readable:
+ data = os.read(self.pseudo_tty.stdin, CONMON_BUFSZ)
+ if data:
+ skt.sendall(data)
+
+ if self.pseudo_tty.eot in data:
+ sources.clear()
+ else:
+ sources.remove(self.pseudo_tty.stdin)
+ finally:
+ if tcoldattr:
+ termios.tcsetattr(self.pseudo_tty.stdin, termios.TCSADRAIN,
+ tcoldattr)
+ signal.signal(signal.SIGWINCH, signal.SIG_DFL)
+ return self._refresh(podman)
diff --git a/contrib/python/podman/libs/containers.py b/contrib/python/podman/libs/containers.py
index a350a128a..6dc2c141e 100644
--- a/contrib/python/podman/libs/containers.py
+++ b/contrib/python/podman/libs/containers.py
@@ -7,9 +7,10 @@ import signal
import time
from ._containers_attach import Mixin as AttachMixin
+from ._containers_start import Mixin as StartMixin
-class Container(collections.UserDict, AttachMixin):
+class Container(AttachMixin, StartMixin, collections.UserDict):
"""Model for a container."""
def __init__(self, client, id, data):
@@ -143,12 +144,6 @@ class Container(collections.UserDict, AttachMixin):
message, pause)
return results['image']
- def start(self):
- """Start container, return container on success."""
- with self._client() as podman:
- podman.StartContainer(self.id)
- return self._refresh(podman)
-
def stop(self, timeout=25):
"""Stop container, return id on success."""
with self._client() as podman:
diff --git a/contrib/python/podman/libs/images.py b/contrib/python/podman/libs/images.py
index 3beadec1d..334ff873c 100644
--- a/contrib/python/podman/libs/images.py
+++ b/contrib/python/podman/libs/images.py
@@ -3,6 +3,7 @@ import collections
import copy
import functools
import json
+import logging
from . import Config
from .containers import Container
@@ -37,11 +38,8 @@ class Image(collections.UserDict):
Pulls defaults from image.inspect()
"""
- with self._client() as podman:
- details = self.inspect()
+ details = self.inspect()
- # TODO: remove network settings once defaults implemented in service
- # Inialize config from parameters, then add image information
config = Config(image_id=self.id, **kwargs)
config['command'] = details.containerconfig['cmd']
config['env'] = self._split_token(details.containerconfig['env'])
@@ -49,8 +47,8 @@ class Image(collections.UserDict):
config['labels'] = copy.deepcopy(details.labels)
config['net_mode'] = 'bridge'
config['network'] = 'bridge'
- config['work_dir'] = '/tmp'
+ logging.debug('Image {}: create config: {}'.format(self.id, config))
with self._client() as podman:
id = podman.CreateContainer(config)['container']
cntr = podman.GetContainer(id)
diff --git a/contrib/python/test/test_containers.py b/contrib/python/test/test_containers.py
index 87d43adb4..ec2dcde03 100644
--- a/contrib/python/test/test_containers.py
+++ b/contrib/python/test/test_containers.py
@@ -72,14 +72,18 @@ class TestContainers(PodmanTestCase):
mock_in.write('echo H"ello, World"; exit\n')
mock_in.seek(0, 0)
- self.alpine_ctnr.attach(
- stdin=mock_in.fileno(), stdout=mock_out.fileno())
+ ctnr = self.pclient.images.get(self.alpine_ctnr.image).container(
+ detach=True, tty=True)
+ ctnr.attach(stdin=mock_in.fileno(), stdout=mock_out.fileno())
+ ctnr.start()
mock_out.flush()
mock_out.seek(0, 0)
output = mock_out.read()
self.assertIn('Hello', output)
+ ctnr.remove(force=True)
+
def test_processes(self):
actual = list(self.alpine_ctnr.processes())
self.assertGreaterEqual(len(actual), 2)
@@ -133,8 +137,7 @@ class TestContainers(PodmanTestCase):
def test_commit(self):
# TODO: Test for STOPSIGNAL when supported by OCI
# TODO: Test for message when supported by OCI
- details = self.pclient.images.get(
- self.alpine_ctnr.inspect().image).inspect()
+ details = self.pclient.images.get(self.alpine_ctnr.image).inspect()
changes = ['ENV=' + i for i in details.containerconfig['env']]
changes.append('CMD=/usr/bin/zsh')
changes.append('ENTRYPOINT=/bin/sh date')
diff --git a/contrib/python/test/test_images.py b/contrib/python/test/test_images.py
index c5695b722..14bf90992 100644
--- a/contrib/python/test/test_images.py
+++ b/contrib/python/test/test_images.py
@@ -62,6 +62,7 @@ class TestImages(PodmanTestCase):
actual = self.alpine_image.container()
self.assertIsNotNone(actual)
self.assertEqual(actual.status, 'configured')
+
ctnr = actual.start()
self.assertIn(ctnr.status, ['running', 'exited'])
diff --git a/docs/podman-pod-create.1.md b/docs/podman-pod-create.1.md
new file mode 100644
index 000000000..495c6934a
--- /dev/null
+++ b/docs/podman-pod-create.1.md
@@ -0,0 +1,60 @@
+% podman-pod-create "1"
+
+## NAME
+podman\-pod\-create - Create a new pod
+
+## SYNOPSIS
+**podman pod create** [*options*]
+
+## DESCRIPTION
+
+Creates an empty pod, or unit of multiple containers, and prepares it to have
+containers added to it. The pod id is printed to STDOUT. You can then use
+**podman create --pod <pod_id|pod_name> ...** to add containers to the pod, and
+**podman pod start <pod_id|pod_name>** to start the pod.
+
+## OPTIONS
+
+**--cgroup-parent**=*true*|*false*
+
+Path to cgroups under which the cgroup for the pod will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
+
+**--podidfile**=""
+
+Write the pod ID to the file
+
+**--help**
+
+Print usage statement
+
+**-l**, **--label**=[]
+
+Add metadata to a pod (e.g., --label com.example.key=value)
+
+**--label-file**=[]
+
+Read in a line delimited file of labels
+
+**-n**, **--name**=""
+
+Assign a name to the pod
+
+The operator can identify a pod in three ways:
+UUID long identifier (“f78375b1c487e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778”)
+UUID short identifier (“f78375b1c487”)
+Name (“jonah”)
+
+podman generates a UUID for each pod, and if a name is not assigned
+to the container with **--name** then a random string name will be generated
+for it. The name is useful any place you need to identify a pod.
+
+## EXAMPLES
+
+
+# podman pod create --name test
+
+## SEE ALSO
+podman-pod(1)
+
+## HISTORY
+July 2018, Originally compiled by Peter Hunt <pehunt@redhat.com>
diff --git a/docs/podman-pod-ps.1.md b/docs/podman-pod-ps.1.md
new file mode 100644
index 000000000..1fdbe2935
--- /dev/null
+++ b/docs/podman-pod-ps.1.md
@@ -0,0 +1,165 @@
+% podman-pod-ps "1"
+
+## NAME
+podman\-pod\-ps - Prints out information about pods
+
+## SYNOPSIS
+**podman pod ps** [*options*]
+
+## DESCRIPTION
+**podman pod ps** lists the pods on the system.
+By default it lists:
+
+ * pod id
+ * pod name
+ * number of containers attached to pod
+ * status of pod as defined by the following table
+
+| **Status** | **Description** |
+| ------------ | ------------------------------------------------|
+| Created | No containers running nor stopped |
+| Running | at least one container is running |
+| Stopped | At least one container stopped and none running |
+| Exited | All containers stopped in pod |
+| Dead | Error retrieving state |
+
+
+## OPTIONS
+
+**--ctr-names**
+
+Includes the container names in the container info field
+
+**--ctr-ids**
+
+Includes the container IDs in the container info field
+
+**--ctr-status**
+
+Includes the container statuses in the container info field
+
+**--latest, -l**
+
+Show the latest pod created (all states)
+
+**--no-trunc**
+
+Display the extended information
+
+**--quiet, -q**
+
+Print the numeric IDs of the pods only
+
+**--format**
+
+Pretty-print containers to JSON or using a Go template
+
+Valid placeholders for the Go template are listed below:
+
+| **Placeholder** | **Description** |
+| ------------------- | ----------------------------------------------------------------------------------------------- |
+| .ID | Container ID |
+| .Name | Name of pod |
+| .Status | Status of pod |
+| .Labels | All the labels assigned to the pod |
+| .ContainerInfo | Show the names, ids and/or statuses of containers (only shows 9 unless no-trunc is specified) |
+| .NumberOfContainers | Show the number of containers attached to pod |
+| .Cgroup | Cgroup path of pod |
+| .UsePodCgroup | Whether containers use the Cgroup of the pod |
+
+**--sort**
+
+Sort by created, ID, name, status, or number of containers
+
+Default: created
+
+**--filter, -f**
+
+Filter output based on conditions given
+
+Valid filters are listed below:
+
+| **Filter** | **Description** |
+| --------------- | ------------------------------------------------------------------- |
+| id | [ID] Pod's ID |
+| name | [Name] Pod's name |
+| label | [Key] or [Key=Value] Label assigned to a container |
+| ctr-names | Container name within the pod |
+| ctr-ids | Container ID within the pod |
+| ctr-status | Container status within the pod |
+| ctr-number | Number of containers in the pod |
+
+**--help**, **-h**
+
+Print usage statement
+
+## EXAMPLES
+
+```
+sudo podman pod ps
+POD ID NAME STATUS NUMBER OF CONTAINERS
+00dfd6fa02c0 jolly_goldstine Running 1
+f4df8692e116 nifty_torvalds Created 2
+```
+
+```
+sudo podman pod ps --ctr-names
+POD ID NAME STATUS CONTAINER INFO
+00dfd6fa02c0 jolly_goldstine Running [ loving_archimedes ]
+f4df8692e116 nifty_torvalds Created [ thirsty_hawking ] [ wizardly_golick ]
+```
+
+```
+podman pod ps --ctr-status --ctr-names --ctr-ids
+POD ID NAME STATUS CONTAINER INFO
+00dfd6fa02c0 jolly_goldstine Running [ ba465ab0a3a4 loving_archimedes Running ]
+f4df8692e116 nifty_torvalds Created [ 331693bff40a thirsty_hawking Created ] [ 8e428daeb89e wizardly_golick Created ]
+```
+
+```
+sudo podman pod ps --format "{{.ID}} {{.ContainerInfo}} {{.Cgroup}}" --ctr-names
+00dfd6fa02c0 [ loving_archimedes ] /libpod_parent
+f4df8692e116 [ thirsty_hawking ] [ wizardly_golick ] /libpod_parent
+```
+
+```
+sudo podman pod ps --cgroup
+POD ID NAME STATUS NUMBER OF CONTAINERS CGROUP USE POD CGROUP
+00dfd6fa02c0 jolly_goldstine Running 1 /libpod_parent true
+f4df8692e116 nifty_torvalds Created 2 /libpod_parent true
+```
+
+```
+podman pod ps --sort id --filter ctr-number=2
+POD ID NAME STATUS NUMBER OF CONTAINERS
+f4df8692e116 nifty_torvalds Created 2
+```
+
+```
+sudo podman pod ps --ctr-ids
+POD ID NAME STATUS CONTAINER INFO
+00dfd6fa02c0 jolly_goldstine Running [ ba465ab0a3a4 ]
+f4df8692e116 nifty_torvalds Created [ 331693bff40a ] [ 8e428daeb89e ]
+```
+
+```
+sudo podman pod ps --no-trunc --ctr-ids
+POD ID NAME STATUS CONTAINER INFO
+00dfd6fa02c0a2daaedfdf8fcecd06f22ad114d46d167d71777224735f701866 jolly_goldstine Running [ ba465ab0a3a4e15e3539a1e79c32d1213a02b0989371e274f98e0f1ae9de7050 ]
+f4df8692e116a3e6d1d62572644ed36ca475d933808cc3c93435c45aa139314b nifty_torvalds Created [ 331693bff40a0ef2f05a3aba73ce49e3243108911927fff04d1f7fc44dda8022 ] [ 8e428daeb89e69b71e7916a13accfb87d122889442b5c05c2d99cf94a3230e9d ]
+```
+
+```
+podman pod ps --ctr-names
+POD ID NAME STATUS CONTAINER INFO
+314f4da82d74 hi Created [ jovial_jackson ] [ hopeful_archimedes ] [ vibrant_ptolemy ] [ heuristic_jennings ] [ keen_raman ] [ hopeful_newton ] [ mystifying_bose ] [ silly_lalande ] [ serene_lichterman ] ...
+```
+
+## pod ps
+Print a list of pods
+
+## SEE ALSO
+podman-pod(1)
+
+## HISTORY
+July 2018, Originally compiled by Peter Hunt <pehunt@redhat.com>
diff --git a/docs/podman-pod-rm.1.md b/docs/podman-pod-rm.1.md
new file mode 100644
index 000000000..3b571ee9a
--- /dev/null
+++ b/docs/podman-pod-rm.1.md
@@ -0,0 +1,42 @@
+% podman-pod-rm "1"
+
+## NAME
+podman\-pod\-rm - Remove one or more pods
+
+## SYNOPSIS
+**podman pod rm** [*options*] *pod*
+
+## DESCRIPTION
+**podman pod rm** will remove one or more pods from the host. The pod name or ID can be used. The \-f option stops all containers and then removes them before removing the pod. Without the \-f option, a pod cannot be removed if it has associated containers.
+
+## OPTIONS
+
+**--all, a**
+
+Remove all pods. Can be used in conjunction with \-f as well.
+
+**--latest, -l**
+
+Instead of providing the pod name or ID, use the last created pod.
+
+**--force, f**
+
+Stop running containers and delete all stopped containers before removal of pod.
+
+## EXAMPLE
+
+podman pod rm mywebserverpod
+
+podman pod rm mywebserverpod myflaskserverpod 860a4b23
+
+podman pod rm -f 860a4b23
+
+podman pod rm -f -a
+
+podman pod rm -fa
+
+## SEE ALSO
+podman-pod(1)
+
+## HISTORY
+July 2018, Originally compiled by Peter Hunt <pehunt@redhat.com>
diff --git a/docs/podman-pod.1.md b/docs/podman-pod.1.md
new file mode 100644
index 000000000..511a5841f
--- /dev/null
+++ b/docs/podman-pod.1.md
@@ -0,0 +1,21 @@
+% podman-pod "1"
+
+## NAME
+podman\-pod - Simple management tool for groups of containers, called pods.
+
+## SYNOPSIS
+**podman pod** *subcommand*
+
+# DESCRIPTION
+podman pod is a set of subcommands that manage pods, or groups of containers.
+
+## SUBCOMMANDS
+
+| Subcommand | Description |
+| ------------------------------------------------- | ------------------------------------------------------------------------------ |
+| [podman-pod-create(1)](podman-pod-create.1.md) | Create a new pod. |
+| [podman-pod-ps(1)](podman-pod-ps.1.md) | Prints out information about pods. |
+| [podman-pod-rm(1)](podman-pod-rm.1.md) | Remove one or more pods. |
+
+## HISTORY
+July 2018, Originally compiled by Peter Hunt <pehunt@redhat.com>
diff --git a/docs/podman.1.md b/docs/podman.1.md
index bb84037a0..c946ccb02 100644
--- a/docs/podman.1.md
+++ b/docs/podman.1.md
@@ -59,6 +59,10 @@ Select which storage driver is used to manage storage of images and containers (
Used to pass an option to the storage driver
+**--syslog**
+
+output logging information to syslog as well as the console
+
**--version, -v**
Print the version
diff --git a/hack/apparmor_tag.sh b/hack/apparmor_tag.sh
new file mode 100755
index 000000000..6d4bec91b
--- /dev/null
+++ b/hack/apparmor_tag.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+if pkg-config libapparmor 2> /dev/null ; then
+ # Travis CI does not support AppArmor, so we cannot run tests there.
+ if [ -z "$TRAVIS" ]; then
+ echo apparmor
+ fi
+fi
diff --git a/hack/dind b/hack/dind
new file mode 100755
index 000000000..3254f9dbe
--- /dev/null
+++ b/hack/dind
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+set -e
+
+# DinD: a wrapper script which allows docker to be run inside a docker container.
+# Original version by Jerome Petazzoni <jerome@docker.com>
+# See the blog post: https://blog.docker.com/2013/09/docker-can-now-run-within-docker/
+#
+# This script should be executed inside a docker container in privileged mode
+# ('docker run --privileged', introduced in docker 0.6).
+
+# Usage: dind CMD [ARG...]
+
+# apparmor sucks and Docker needs to know that it's in a container (c) @tianon
+export container=docker
+
+if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security; then
+ mount -t securityfs none /sys/kernel/security || {
+ echo >&2 'Could not mount /sys/kernel/security.'
+ echo >&2 'AppArmor detection and --privileged mode might break.'
+ }
+fi
+
+# Mount /tmp (conditionally)
+if ! mountpoint -q /tmp; then
+ mount -t tmpfs none /tmp
+fi
+
+if [ $# -gt 0 ]; then
+ exec "$@"
+fi
+
+echo >&2 'ERROR: No command specified.'
+echo >&2 'You probably want to run hack/make.sh, or maybe a shell?'
diff --git a/hack/ostree_tag.sh b/hack/ostree_tag.sh
index f7bccf127..b08441ef9 100755
--- a/hack/ostree_tag.sh
+++ b/hack/ostree_tag.sh
@@ -1,4 +1,6 @@
#!/bin/bash
if ! pkg-config glib-2.0 gobject-2.0 ostree-1 libselinux 2> /dev/null ; then
echo containers_image_ostree_stub
+else
+ echo ostree
fi
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 {
diff --git a/pkg/apparmor/aaparser.go b/pkg/apparmor/aaparser.go
new file mode 100644
index 000000000..cec9c4885
--- /dev/null
+++ b/pkg/apparmor/aaparser.go
@@ -0,0 +1,90 @@
+// +build linux,apparmor
+
+package apparmor
+
+import (
+ "fmt"
+ "os/exec"
+ "strconv"
+ "strings"
+)
+
+const (
+ binary = "apparmor_parser"
+)
+
+// getVersion returns the major and minor version of apparmor_parser.
+func getVersion() (int, error) {
+ output, err := cmd("", "--version")
+ if err != nil {
+ return -1, err
+ }
+
+ return parseVersion(output)
+}
+
+// loadProfile runs `apparmor_parser -Kr` on a specified apparmor profile to
+// replace the profile. The `-K` is necessary to make sure that apparmor_parser
+// doesn't try to write to a read-only filesystem.
+func loadProfile(profilePath string) error {
+ _, err := cmd("", "-Kr", profilePath)
+ return err
+}
+
+// cmd runs `apparmor_parser` with the passed arguments.
+func cmd(dir string, arg ...string) (string, error) {
+ c := exec.Command(binary, arg...)
+ c.Dir = dir
+
+ output, err := c.CombinedOutput()
+ if err != nil {
+ return "", fmt.Errorf("running `%s %s` failed with output: %s\nerror: %v", c.Path, strings.Join(c.Args, " "), output, err)
+ }
+
+ return string(output), nil
+}
+
+// parseVersion takes the output from `apparmor_parser --version` and returns
+// a representation of the {major, minor, patch} version as a single number of
+// the form MMmmPPP {major, minor, patch}.
+func parseVersion(output string) (int, error) {
+ // output is in the form of the following:
+ // AppArmor parser version 2.9.1
+ // Copyright (C) 1999-2008 Novell Inc.
+ // Copyright 2009-2012 Canonical Ltd.
+
+ lines := strings.SplitN(output, "\n", 2)
+ words := strings.Split(lines[0], " ")
+ version := words[len(words)-1]
+
+ // split by major minor version
+ v := strings.Split(version, ".")
+ if len(v) == 0 || len(v) > 3 {
+ return -1, fmt.Errorf("parsing version failed for output: `%s`", output)
+ }
+
+ // Default the versions to 0.
+ var majorVersion, minorVersion, patchLevel int
+
+ majorVersion, err := strconv.Atoi(v[0])
+ if err != nil {
+ return -1, err
+ }
+
+ if len(v) > 1 {
+ minorVersion, err = strconv.Atoi(v[1])
+ if err != nil {
+ return -1, err
+ }
+ }
+ if len(v) > 2 {
+ patchLevel, err = strconv.Atoi(v[2])
+ if err != nil {
+ return -1, err
+ }
+ }
+
+ // major*10^5 + minor*10^3 + patch*10^0
+ numericVersion := majorVersion*1e5 + minorVersion*1e3 + patchLevel
+ return numericVersion, nil
+}
diff --git a/pkg/apparmor/aaparser_test.go b/pkg/apparmor/aaparser_test.go
new file mode 100644
index 000000000..9d97969c7
--- /dev/null
+++ b/pkg/apparmor/aaparser_test.go
@@ -0,0 +1,75 @@
+// +build linux,apparmor
+
+package apparmor
+
+import (
+ "testing"
+)
+
+type versionExpected struct {
+ output string
+ version int
+}
+
+func TestParseVersion(t *testing.T) {
+ versions := []versionExpected{
+ {
+ output: `AppArmor parser version 2.10
+Copyright (C) 1999-2008 Novell Inc.
+Copyright 2009-2012 Canonical Ltd.
+
+`,
+ version: 210000,
+ },
+ {
+ output: `AppArmor parser version 2.8
+Copyright (C) 1999-2008 Novell Inc.
+Copyright 2009-2012 Canonical Ltd.
+
+`,
+ version: 208000,
+ },
+ {
+ output: `AppArmor parser version 2.20
+Copyright (C) 1999-2008 Novell Inc.
+Copyright 2009-2012 Canonical Ltd.
+
+`,
+ version: 220000,
+ },
+ {
+ output: `AppArmor parser version 2.05
+Copyright (C) 1999-2008 Novell Inc.
+Copyright 2009-2012 Canonical Ltd.
+
+`,
+ version: 205000,
+ },
+ {
+ output: `AppArmor parser version 2.9.95
+Copyright (C) 1999-2008 Novell Inc.
+Copyright 2009-2012 Canonical Ltd.
+
+`,
+ version: 209095,
+ },
+ {
+ output: `AppArmor parser version 3.14.159
+Copyright (C) 1999-2008 Novell Inc.
+Copyright 2009-2012 Canonical Ltd.
+
+`,
+ version: 314159,
+ },
+ }
+
+ for _, v := range versions {
+ version, err := parseVersion(v.output)
+ if err != nil {
+ t.Fatalf("expected error to be nil for %#v, got: %v", v, err)
+ }
+ if version != v.version {
+ t.Fatalf("expected version to be %d, was %d, for: %#v\n", v.version, version, v)
+ }
+ }
+}
diff --git a/pkg/apparmor/apparmor.go b/pkg/apparmor/apparmor.go
new file mode 100644
index 000000000..1c205f68a
--- /dev/null
+++ b/pkg/apparmor/apparmor.go
@@ -0,0 +1,54 @@
+package apparmor
+
+import (
+ "errors"
+)
+
+var (
+ // profileDirectory is the file store for apparmor profiles and macros.
+ profileDirectory = "/etc/apparmor.d"
+ // DefaultLibpodProfile is the name of default libpod AppArmor profile.
+ DefaultLibpodProfile = "libpod-default"
+ // ErrApparmorUnsupported indicates that AppArmor support is not supported.
+ ErrApparmorUnsupported = errors.New("AppArmor is not supported")
+)
+
+const libpodProfileTemplate = `
+{{range $value := .Imports}}
+{{$value}}
+{{end}}
+
+profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
+{{range $value := .InnerImports}}
+ {{$value}}
+{{end}}
+
+ network,
+ capability,
+ file,
+ umount,
+
+ deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir)
+ # deny write to files not in /proc/<number>/** or /proc/sys/**
+ deny @{PROC}/{[^1-9],[^1-9][^0-9],[^1-9s][^0-9y][^0-9s],[^1-9][^0-9][^0-9][^0-9]*}/** w,
+ deny @{PROC}/sys/[^k]** w, # deny /proc/sys except /proc/sys/k* (effectively /proc/sys/kernel)
+ deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w, # deny everything except shm* in /proc/sys/kernel/
+ deny @{PROC}/sysrq-trigger rwklx,
+ deny @{PROC}/kcore rwklx,
+
+ deny mount,
+
+ deny /sys/[^f]*/** wklx,
+ deny /sys/f[^s]*/** wklx,
+ deny /sys/fs/[^c]*/** wklx,
+ deny /sys/fs/c[^g]*/** wklx,
+ deny /sys/fs/cg[^r]*/** wklx,
+ deny /sys/firmware/** rwklx,
+ deny /sys/kernel/security/** rwklx,
+
+{{if ge .Version 208095}}
+ # suppress ptrace denials when using using 'ps' inside a container
+ ptrace (trace,read) peer={{.Name}},
+{{end}}
+}
+`
diff --git a/pkg/apparmor/apparmor_linux.go b/pkg/apparmor/apparmor_linux.go
new file mode 100644
index 000000000..6e8b7f312
--- /dev/null
+++ b/pkg/apparmor/apparmor_linux.go
@@ -0,0 +1,110 @@
+// +build linux,apparmor
+
+package apparmor
+
+import (
+ "bufio"
+ "io"
+ "io/ioutil"
+ "os"
+ "path"
+ "strings"
+ "text/template"
+)
+
+// profileData holds information about the given profile for generation.
+type profileData struct {
+ // Name is profile name.
+ Name string
+ // Imports defines the apparmor functions to import, before defining the profile.
+ Imports []string
+ // InnerImports defines the apparmor functions to import in the profile.
+ InnerImports []string
+ // Version is the {major, minor, patch} version of apparmor_parser as a single number.
+ Version int
+}
+
+// generateDefault creates an apparmor profile from ProfileData.
+func (p *profileData) generateDefault(out io.Writer) error {
+ compiled, err := template.New("apparmor_profile").Parse(libpodProfileTemplate)
+ if err != nil {
+ return err
+ }
+
+ if macroExists("tunables/global") {
+ p.Imports = append(p.Imports, "#include <tunables/global>")
+ } else {
+ p.Imports = append(p.Imports, "@{PROC}=/proc/")
+ }
+
+ if macroExists("abstractions/base") {
+ p.InnerImports = append(p.InnerImports, "#include <abstractions/base>")
+ }
+
+ ver, err := getVersion()
+ if err != nil {
+ return err
+ }
+ p.Version = ver
+
+ return compiled.Execute(out, p)
+}
+
+// macrosExists checks if the passed macro exists.
+func macroExists(m string) bool {
+ _, err := os.Stat(path.Join(profileDirectory, m))
+ return err == nil
+}
+
+// InstallDefault generates a default profile in a temp directory determined by
+// os.TempDir(), then loads the profile into the kernel using 'apparmor_parser'.
+func InstallDefault(name string) error {
+ p := profileData{
+ Name: name,
+ }
+
+ // Install to a temporary directory.
+ f, err := ioutil.TempFile("", name)
+ if err != nil {
+ return err
+ }
+ profilePath := f.Name()
+
+ defer f.Close()
+ defer os.Remove(profilePath)
+
+ if err := p.generateDefault(f); err != nil {
+ return err
+ }
+
+ return loadProfile(profilePath)
+}
+
+// IsLoaded checks if a profile with the given name has been loaded into the
+// kernel.
+func IsLoaded(name string) (bool, error) {
+ file, err := os.Open("/sys/kernel/security/apparmor/profiles")
+ if err != nil {
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+ return false, err
+ }
+ defer file.Close()
+
+ r := bufio.NewReader(file)
+ for {
+ p, err := r.ReadString('\n')
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return false, err
+ }
+ if strings.HasPrefix(p, name+" ") {
+ return true, nil
+ }
+ }
+
+ return false, nil
+}
diff --git a/pkg/apparmor/apparmor_unsupported.go b/pkg/apparmor/apparmor_unsupported.go
new file mode 100644
index 000000000..0f1ab9464
--- /dev/null
+++ b/pkg/apparmor/apparmor_unsupported.go
@@ -0,0 +1,15 @@
+// +build !linux !apparmor
+
+package apparmor
+
+// InstallDefault generates a default profile in a temp directory determined by
+// os.TempDir(), then loads the profile into the kernel using 'apparmor_parser'.
+func InstallDefault(name string) error {
+ return ErrApparmorUnsupported
+}
+
+// IsLoaded checks if a profile with the given name has been loaded into the
+// kernel.
+func IsLoaded(name string) (bool, error) {
+ return false, ErrApparmorUnsupported
+}
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index a39f4875c..57416732d 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -318,6 +318,14 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib
logrus.Debugf("appending name %s", c.Name)
options = append(options, libpod.WithName(c.Name))
}
+ if c.Pod != "" {
+ logrus.Debugf("adding container to pod %s", c.Pod)
+ pod, err := runtime.LookupPod(c.Pod)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to add container to pod %s", c.Pod)
+ }
+ options = append(options, runtime.WithPod(pod))
+ }
if len(c.PortBindings) > 0 {
portBindings, err = c.CreatePortBindings()
@@ -351,9 +359,20 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib
// does not have one
options = append(options, libpod.WithEntrypoint(c.Entrypoint))
+ networks := make([]string, 0)
+ userNetworks := c.NetMode.UserDefined()
+ if userNetworks != "" {
+ for _, netName := range strings.Split(userNetworks, ",") {
+ if netName == "" {
+ return nil, errors.Wrapf(err, "container networks %q invalid", networks)
+ }
+ networks = append(networks, netName)
+ }
+ }
+
if rootless.IsRootless() {
if !c.NetMode.IsHost() && !c.NetMode.IsNone() {
- options = append(options, libpod.WithNetNS(portBindings, true))
+ options = append(options, libpod.WithNetNS(portBindings, true, networks))
}
} else if c.NetMode.IsContainer() {
connectedCtr, err := c.Runtime.LookupContainer(c.NetMode.ConnectedContainer())
@@ -363,8 +382,7 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib
options = append(options, libpod.WithNetNSFrom(connectedCtr))
} else if !c.NetMode.IsHost() && !c.NetMode.IsNone() {
postConfigureNetNS := (len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0) && !c.UsernsMode.IsHost()
- options = append(options, libpod.WithNetNS([]ocicni.PortMapping{}, postConfigureNetNS))
- options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS))
+ options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, networks))
}
if c.PidMode.IsContainer() {
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index ffa242675..35651f2ed 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -345,10 +345,11 @@ func addNetNS(config *CreateConfig, g *generate.Generator) error {
return nil
} else if netMode.IsContainer() {
logrus.Debug("Using container netmode")
- } else {
- return errors.Errorf("unknown network mode")
+ } else if netMode.IsUserDefined() {
+ logrus.Debug("Using user defined netmode")
+ return nil
}
- return nil
+ return errors.Errorf("unknown network mode")
}
func addUTSNS(config *CreateConfig, g *generate.Generator) error {
diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go
index 391bbb350..6678b6f7a 100644
--- a/pkg/varlinkapi/containers.go
+++ b/pkg/varlinkapi/containers.go
@@ -462,6 +462,20 @@ func (i *LibpodAPI) GetAttachSockets(call ioprojectatomicpodman.VarlinkCall, nam
if err != nil {
return call.ReplyContainerNotFound(name)
}
+
+ status, err := ctr.State()
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+
+ // If the container hasn't been run, we need to run init
+ // so the conmon sockets get created.
+ if status == libpod.ContainerStateConfigured || status == libpod.ContainerStateStopped {
+ if err := ctr.Init(getContext()); err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ }
+
s := ioprojectatomicpodman.Sockets{
Container_id: ctr.ID(),
Io_socket: ctr.AttachSocketPath(),
diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go
index 448dbfc04..6ec995bfc 100644
--- a/test/e2e/libpod_suite_test.go
+++ b/test/e2e/libpod_suite_test.go
@@ -224,6 +224,17 @@ func (p *PodmanTest) Cleanup() {
}
}
+// CleanupPod cleans up the temporary store
+func (p *PodmanTest) CleanupPod() {
+ // Remove all containers
+ session := p.Podman([]string{"pod", "rm", "-fa"})
+ session.Wait(90)
+ // Nuke tempdir
+ if err := os.RemoveAll(p.TempDir); err != nil {
+ fmt.Printf("%q\n", err)
+ }
+}
+
// GrepString takes session output and behaves like grep. it returns a bool
// if successful and an array of strings on positive matches
func (s *PodmanSession) GrepString(term string) (bool, []string) {
@@ -459,6 +470,15 @@ func (p *PodmanTest) RunTopContainer(name string) *PodmanSession {
return p.Podman(podmanArgs)
}
+func (p *PodmanTest) RunTopContainerInPod(name, pod string) *PodmanSession {
+ var podmanArgs = []string{"run", "--pod", pod}
+ if name != "" {
+ podmanArgs = append(podmanArgs, "--name", name)
+ }
+ podmanArgs = append(podmanArgs, "-d", ALPINE, "top")
+ return p.Podman(podmanArgs)
+}
+
//RunLsContainer runs a simple container in the background that
// simply runs ls. If the name passed != "", it will have a name
func (p *PodmanTest) RunLsContainer(name string) (*PodmanSession, int, string) {
@@ -472,6 +492,17 @@ func (p *PodmanTest) RunLsContainer(name string) (*PodmanSession, int, string) {
return session, session.ExitCode(), session.OutputToString()
}
+func (p *PodmanTest) RunLsContainerInPod(name, pod string) (*PodmanSession, int, string) {
+ var podmanArgs = []string{"run", "--pod", pod}
+ if name != "" {
+ podmanArgs = append(podmanArgs, "--name", name)
+ }
+ podmanArgs = append(podmanArgs, "-d", ALPINE, "ls")
+ session := p.Podman(podmanArgs)
+ session.WaitWithDefaultTimeout()
+ return session, session.ExitCode(), session.OutputToString()
+}
+
//NumberOfContainersRunning returns an int of how many
// containers are currently running.
func (p *PodmanTest) NumberOfContainersRunning() int {
@@ -502,6 +533,21 @@ func (p *PodmanTest) NumberOfContainers() int {
return len(containers)
}
+// NumberOfPods returns an int of how many
+// pods are currently defined.
+func (p *PodmanTest) NumberOfPods() int {
+ var pods []string
+ ps := p.Podman([]string{"pod", "ps", "-q"})
+ ps.WaitWithDefaultTimeout()
+ Expect(ps.ExitCode()).To(Equal(0))
+ for _, i := range ps.OutputToStringArray() {
+ if i != "" {
+ pods = append(pods, i)
+ }
+ }
+ return len(pods)
+}
+
// NumberOfRunningContainers returns an int of how many containers are currently
// running
func (p *PodmanTest) NumberOfRunningContainers() int {
diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go
new file mode 100644
index 000000000..fa420675f
--- /dev/null
+++ b/test/e2e/pod_create_test.go
@@ -0,0 +1,84 @@
+package integration
+
+import (
+ "os"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman pod create", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest PodmanTest
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanCreate(tempdir)
+ podmanTest.RestoreAllArtifacts()
+ })
+
+ AfterEach(func() {
+ podmanTest.CleanupPod()
+ })
+
+ It("podman create pod", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ cid := session.OutputToString()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ check := podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc"})
+ check.WaitWithDefaultTimeout()
+ match, _ := check.GrepString(cid)
+ Expect(match).To(BeTrue())
+ Expect(len(check.OutputToStringArray())).To(Equal(1))
+ })
+
+ It("podman create pod with name", func() {
+ name := "test"
+ session := podmanTest.Podman([]string{"pod", "create", "--name", name})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ check := podmanTest.Podman([]string{"pod", "ps", "--no-trunc"})
+ check.WaitWithDefaultTimeout()
+ match, _ := check.GrepString(name)
+ Expect(match).To(BeTrue())
+ })
+
+ It("podman create pod with doubled name", func() {
+ name := "test"
+ session := podmanTest.Podman([]string{"pod", "create", "--name", name})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"pod", "create", "--name", name})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(1)))
+
+ check := podmanTest.Podman([]string{"pod", "ps", "-q"})
+ check.WaitWithDefaultTimeout()
+ Expect(len(check.OutputToStringArray())).To(Equal(1))
+ })
+
+ It("podman create pod with same name as ctr", func() {
+ name := "test"
+ session := podmanTest.Podman([]string{"create", "--name", name, ALPINE, "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"pod", "create", "--name", name})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(1)))
+
+ check := podmanTest.Podman([]string{"pod", "ps", "-q"})
+ check.WaitWithDefaultTimeout()
+ Expect(len(check.OutputToStringArray())).To(Equal(1))
+ })
+})
diff --git a/test/e2e/pod_ps_test.go b/test/e2e/pod_ps_test.go
new file mode 100644
index 000000000..4ab13f119
--- /dev/null
+++ b/test/e2e/pod_ps_test.go
@@ -0,0 +1,227 @@
+package integration
+
+import (
+ "fmt"
+ "os"
+ "sort"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman ps", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest PodmanTest
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanCreate(tempdir)
+ podmanTest.RestoreAllArtifacts()
+ })
+
+ AfterEach(func() {
+ podmanTest.CleanupPod()
+
+ })
+
+ It("podman pod ps no pods", func() {
+ session := podmanTest.Podman([]string{"pod", "ps"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
+ It("podman pod ps default", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ podid := session.OutputToString()
+ session = podmanTest.RunTopContainerInPod("", podid)
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"pod", "ps"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(len(result.OutputToStringArray())).Should(BeNumerically(">", 0))
+ })
+
+ It("podman pod ps quiet flag", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ podid := session.OutputToString()
+ _, ec, _ := podmanTest.RunLsContainerInPod("", podid)
+ Expect(ec).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"pod", "ps", "-q"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(len(result.OutputToStringArray())).Should(BeNumerically(">", 0))
+ Expect(podid).To(ContainSubstring(result.OutputToStringArray()[0]))
+ })
+
+ It("podman pod ps no-trunc", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ podid := session.OutputToString()
+ _, ec, _ := podmanTest.RunLsContainerInPod("", podid)
+ Expect(ec).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(len(result.OutputToStringArray())).Should(BeNumerically(">", 0))
+ Expect(podid).To(Equal(result.OutputToStringArray()[0]))
+ })
+
+ It("podman pod ps latest", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ podid1 := session.OutputToString()
+
+ session = podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ podid2 := session.OutputToString()
+
+ result := podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc", "--latest"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(result.OutputToString()).To(ContainSubstring(podid2))
+ Expect(result.OutputToString()).To(Not(ContainSubstring(podid1)))
+ })
+ It("podman pod ps id filter flag", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"pod", "ps", "--filter", fmt.Sprintf("id=%s", session.OutputToString())})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ })
+
+ It("podman pod ps mutually exclusive flags", func() {
+ session := podmanTest.Podman([]string{"pod", "ps", "-q", "--format", "{{.ID}}"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+
+ })
+
+ It("podman pod ps --sort by name", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"pod", "ps", "--sort=name", "--format", "{{.Name}}"})
+
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ sortedArr := session.OutputToStringArray()
+
+ Expect(sort.SliceIsSorted(sortedArr, func(i, j int) bool { return sortedArr[i] < sortedArr[j] })).To(BeTrue())
+ })
+
+ It("podman pod ps --ctr-names", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ podid := session.OutputToString()
+
+ session = podmanTest.RunTopContainerInPod("test1", podid)
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ _, ec, _ := podmanTest.RunLsContainerInPod("test2", podid)
+ Expect(ec).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"pod", "ps", "--format={{.ContainerInfo}}", "--ctr-names"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring("test1"))
+ Expect(session.OutputToString()).To(ContainSubstring("test2"))
+ })
+
+ It("podman pod ps filter ctr attributes", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ podid1 := session.OutputToString()
+
+ session = podmanTest.RunTopContainerInPod("test1", podid1)
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ podid2 := session.OutputToString()
+
+ _, ec, cid := podmanTest.RunLsContainerInPod("test2", podid2)
+ Expect(ec).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc", "--filter", "ctr-names=test1"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(podid1))
+ Expect(session.OutputToString()).To(Not(ContainSubstring(podid2)))
+
+ session = podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc", "--filter", fmt.Sprintf("ctr-ids=%s", cid)})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(podid2))
+ Expect(session.OutputToString()).To(Not(ContainSubstring(podid1)))
+
+ session = podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ podid3 := session.OutputToString()
+
+ session = podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc", "--filter", "ctr-number=1"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(podid1))
+ Expect(session.OutputToString()).To(ContainSubstring(podid2))
+ Expect(session.OutputToString()).To(Not(ContainSubstring(podid3)))
+
+ session = podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc", "--filter", "ctr-status=running"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(podid1))
+ Expect(session.OutputToString()).To(Not(ContainSubstring(podid2)))
+ Expect(session.OutputToString()).To(Not(ContainSubstring(podid3)))
+
+ session = podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc", "--filter", "ctr-status=exited"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(podid2))
+ Expect(session.OutputToString()).To(Not(ContainSubstring(podid1)))
+ Expect(session.OutputToString()).To(Not(ContainSubstring(podid3)))
+
+ session = podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc", "--filter", "ctr-status=created"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(BeEmpty())
+ })
+})
diff --git a/test/e2e/pod_rm_test.go b/test/e2e/pod_rm_test.go
new file mode 100644
index 000000000..4ebf2f340
--- /dev/null
+++ b/test/e2e/pod_rm_test.go
@@ -0,0 +1,168 @@
+package integration
+
+import (
+ "os"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman pod rm", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest PodmanTest
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanCreate(tempdir)
+ podmanTest.RestoreAllArtifacts()
+ })
+
+ AfterEach(func() {
+ podmanTest.CleanupPod()
+
+ })
+
+ It("podman pod rm empty pod", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ podid := session.OutputToString()
+
+ result := podmanTest.Podman([]string{"pod", "rm", podid})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ })
+
+ It("podman pod rm latest pod", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ podid := session.OutputToString()
+
+ session = podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ podid2 := session.OutputToString()
+
+ result := podmanTest.Podman([]string{"pod", "rm", "--latest"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+
+ result = podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(result.OutputToString()).To(ContainSubstring(podid))
+ Expect(result.OutputToString()).To(Not(ContainSubstring(podid2)))
+ })
+
+ It("podman pod rm doesn't remove a pod with a container", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+
+ podid := session.OutputToString()
+
+ session = podmanTest.Podman([]string{"create", "--pod", podid, ALPINE, "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"pod", "rm", podid})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(125))
+
+ result = podmanTest.Podman([]string{"ps", "-qa"})
+ result.WaitWithDefaultTimeout()
+ Expect(len(result.OutputToStringArray())).To(Equal(1))
+ })
+
+ It("podman pod rm -f does remove a running container", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+
+ podid := session.OutputToString()
+
+ session = podmanTest.Podman([]string{"run", "-d", "--pod", podid, ALPINE, "top"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"pod", "rm", "-f", podid})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+
+ result = podmanTest.Podman([]string{"ps", "-q"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.OutputToString()).To(BeEmpty())
+ })
+
+ It("podman pod rm -a doesn't remove a running container", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+
+ podid1 := session.OutputToString()
+
+ session = podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+
+ session = podmanTest.Podman([]string{"run", "-d", "--pod", podid1, ALPINE, "top"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"pod", "rm", "-a"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Not(Equal(0)))
+
+ result = podmanTest.Podman([]string{"ps", "-q"})
+ result.WaitWithDefaultTimeout()
+ Expect(len(result.OutputToStringArray())).To(Equal(1))
+
+ // one pod should have been deleted
+ result = podmanTest.Podman([]string{"pod", "ps", "-q"})
+ result.WaitWithDefaultTimeout()
+ Expect(len(result.OutputToStringArray())).To(Equal(1))
+ })
+
+ It("podman pod rm -fa removes everything", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+
+ podid1 := session.OutputToString()
+
+ session = podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+
+ podid2 := session.OutputToString()
+
+ session = podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+
+ session = podmanTest.Podman([]string{"run", "-d", "--pod", podid1, ALPINE, "top"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"create", "-d", "--pod", podid1, ALPINE, "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"run", "-d", "--pod", podid2, ALPINE, "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"run", "-d", "--pod", podid2, nginx})
+ session.WaitWithDefaultTimeout()
+
+ result := podmanTest.Podman([]string{"pod", "rm", "-fa"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+
+ result = podmanTest.Podman([]string{"ps", "-q"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.OutputToString()).To(BeEmpty())
+
+ // one pod should have been deleted
+ result = podmanTest.Podman([]string{"pod", "ps", "-q"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.OutputToString()).To(BeEmpty())
+ })
+})
diff --git a/test/e2e/pull_test.go b/test/e2e/pull_test.go
index bb2d0c3a0..36c0936cb 100644
--- a/test/e2e/pull_test.go
+++ b/test/e2e/pull_test.go
@@ -74,7 +74,7 @@ var _ = Describe("Podman pull", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- session = podmanTest.Podman([]string{"rmi", "alpine:latest"})
+ session = podmanTest.Podman([]string{"rmi", "alpine:none"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
})
diff --git a/test/e2e/rmi_test.go b/test/e2e/rmi_test.go
index 399fcd0fc..2d096d7ba 100644
--- a/test/e2e/rmi_test.go
+++ b/test/e2e/rmi_test.go
@@ -103,4 +103,100 @@ var _ = Describe("Podman rmi", func() {
resultForce.WaitWithDefaultTimeout()
Expect(resultForce.ExitCode()).To(Equal(0))
})
+
+ It("podman rmi image that is a parent of another image", func() {
+ session := podmanTest.Podman([]string{"rmi", "-fa"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"run", "--name", "c_test", ALPINE, "true"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"commit", "-q", "c_test", "test"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"rm", "c_test"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"rmi", ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"images", "-q"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(len(session.OutputToStringArray())).To(Equal(1))
+
+ session = podmanTest.Podman([]string{"images", "-q", "-a"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(len(session.OutputToStringArray())).To(Equal(2))
+ untaggedImg := session.OutputToStringArray()[1]
+
+ session = podmanTest.Podman([]string{"rmi", "-f", untaggedImg})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
+ It("podman rmi with cached images", func() {
+ session := podmanTest.Podman([]string{"rmi", "-fa"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ dockerfile := `FROM docker.io/library/alpine:latest
+ RUN mkdir hello
+ RUN touch test.txt
+ ENV foo=bar
+ `
+ podmanTest.BuildImage(dockerfile, "test", "true")
+
+ dockerfile = `FROM docker.io/library/alpine:latest
+ RUN mkdir hello
+ RUN touch test.txt
+ RUN mkdir blah
+ ENV foo=bar
+ `
+ podmanTest.BuildImage(dockerfile, "test2", "true")
+
+ session = podmanTest.Podman([]string{"images", "-q", "-a"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ numOfImages := len(session.OutputToStringArray())
+
+ session = podmanTest.Podman([]string{"rmi", "test2"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"images", "-q", "-a"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(numOfImages - len(session.OutputToStringArray())).To(Equal(2))
+
+ session = podmanTest.Podman([]string{"rmi", "test"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"images", "-q", "-a"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(len(session.OutputToStringArray())).To(Equal(1))
+
+ podmanTest.BuildImage(dockerfile, "test3", "true")
+
+ session = podmanTest.Podman([]string{"rmi", ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"rmi", "test3"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"images", "-q", "-a"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(len(session.OutputToString())).To(Equal(0))
+ })
})
diff --git a/vendor.conf b/vendor.conf
index 8bb44ff0f..f9a11be9b 100644
--- a/vendor.conf
+++ b/vendor.conf
@@ -8,7 +8,7 @@ github.com/boltdb/bolt master
github.com/buger/goterm 2f8dfbc7dbbff5dd1d391ed91482c24df243b2d3
github.com/containerd/cgroups 77e628511d924b13a77cebdc73b757a47f6d751b
github.com/containerd/continuity master
-github.com/containernetworking/cni v0.6.0
+github.com/containernetworking/cni v0.7.0-alpha1
github.com/containernetworking/plugins 1fb94a4222eafc6f948eacdca9c9f2158b427e53
github.com/containers/image 5144ced37a1b21b63c6ef605e56811e29a687528
github.com/containers/storage 90d0a58ffabb324042e074d33d1f6fbf3185c789
diff --git a/vendor/github.com/containernetworking/cni/README.md b/vendor/github.com/containernetworking/cni/README.md
index 793187c79..65ccda9f9 100644
--- a/vendor/github.com/containernetworking/cni/README.md
+++ b/vendor/github.com/containernetworking/cni/README.md
@@ -1,4 +1,5 @@
-[![Build Status](https://travis-ci.org/containernetworking/cni.svg?branch=master)](https://travis-ci.org/containernetworking/cni)
+[![Linux Build Status](https://travis-ci.org/containernetworking/cni.svg?branch=master)](https://travis-ci.org/containernetworking/cni)
+[![Windows Build Status](https://ci.appveyor.com/api/projects/status/wtrkou8oow7x533e/branch/master?svg=true)](https://ci.appveyor.com/project/cni-bot/cni/branch/master)
[![Coverage Status](https://coveralls.io/repos/github/containernetworking/cni/badge.svg?branch=master)](https://coveralls.io/github/containernetworking/cni?branch=master)
[![Slack Status](https://cryptic-tundra-43194.herokuapp.com/badge.svg)](https://cryptic-tundra-43194.herokuapp.com/)
@@ -8,7 +9,9 @@
# Community Sync Meeting
-There is a community sync meeting for users and developers every 1-2 months. The next meeting will help on a Google Hangout and the link is in the [agenda](https://docs.google.com/document/d/10ECyT2mBGewsJUcmYmS8QNo1AcNgy2ZIe2xS7lShYhE/edit?usp=sharing) (Notes from previous meeting are also in this doc). The next meeting will be held on *Wednesday, June 21th* at *3:00pm UTC* [Add to Calendar]https://www.worldtimebuddy.com/?qm=1&lid=100,5,2643743,5391959&h=100&date=2017-6-21&sln=15-16).
+There is a community sync meeting for users and developers every 1-2 months. The next meeting will help on a Google Hangout and the link is in the [agenda](https://docs.google.com/document/d/10ECyT2mBGewsJUcmYmS8QNo1AcNgy2ZIe2xS7lShYhE/edit?usp=sharing) (Notes from previous meeting are also in this doc).
+
+The next meeting will be held on *Wednesday, October 4th* at *3:00pm UTC / 11:00am EDT / 8:00am PDT* [Add to Calendar](https://www.worldtimebuddy.com/?qm=1&lid=100,5,2643743,5391959&h=100&date=2017-10-04&sln=15-16).
---
@@ -35,11 +38,11 @@ To avoid duplication, we think it is prudent to define a common interface betwee
## Who is using CNI?
### Container runtimes
- [rkt - container engine](https://coreos.com/blog/rkt-cni-networking.html)
-- [Kurma - container runtime](http://kurma.io/)
- [Kubernetes - a system to simplify container operations](http://kubernetes.io/docs/admin/network-plugins/)
- [OpenShift - Kubernetes with additional enterprise features](https://github.com/openshift/origin/blob/master/docs/openshift_networking_requirements.md)
- [Cloud Foundry - a platform for cloud applications](https://github.com/cloudfoundry-incubator/cf-networking-release)
-- [Mesos - a distributed systems kernel](https://github.com/apache/mesos/blob/master/docs/cni.md)
+- [Apache Mesos - a distributed systems kernel](https://github.com/apache/mesos/blob/master/docs/cni.md)
+- [Amazon ECS - a highly scalable, high performance container management service](https://aws.amazon.com/ecs/)
### 3rd party plugins
- [Project Calico - a layer 3 virtual network](https://github.com/projectcalico/calico-cni)
@@ -54,6 +57,10 @@ To avoid duplication, we think it is prudent to define a common interface betwee
- [Nuage CNI - Nuage Networks SDN plugin for network policy kubernetes support ](https://github.com/nuagenetworks/nuage-cni)
- [Silk - a CNI plugin designed for Cloud Foundry](https://github.com/cloudfoundry-incubator/silk)
- [Linen - a CNI plugin designed for overlay networks with Open vSwitch and fit in SDN/OpenFlow network environment](https://github.com/John-Lin/linen-cni)
+- [Vhostuser - a Dataplane network plugin - Supports OVS-DPDK & VPP](https://github.com/intel/vhost-user-net-plugin)
+- [Amazon ECS CNI Plugins - a collection of CNI Plugins to configure containers with Amazon EC2 elastic network interfaces (ENIs)](https://github.com/aws/amazon-ecs-cni-plugins)
+- [Bonding CNI - a Link aggregating plugin to address failover and high availability network](https://github.com/Intel-Corp/bond-cni)
+- [ovn-kubernetes - an container network plugin built on Open vSwitch (OVS) and Open Virtual Networking (OVN) with support for both Linux and Windows](https://github.com/openvswitch/ovn-kubernetes)
The CNI team also maintains some [core plugins in a separate repository](https://github.com/containernetworking/plugins).
diff --git a/vendor/github.com/containernetworking/cni/libcni/api.go b/vendor/github.com/containernetworking/cni/libcni/api.go
index a23cbb2c5..d494e43d4 100644
--- a/vendor/github.com/containernetworking/cni/libcni/api.go
+++ b/vendor/github.com/containernetworking/cni/libcni/api.go
@@ -15,7 +15,11 @@
package libcni
import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
"os"
+ "path/filepath"
"strings"
"github.com/containernetworking/cni/pkg/invoke"
@@ -23,6 +27,14 @@ import (
"github.com/containernetworking/cni/pkg/version"
)
+var (
+ CacheDir = "/var/lib/cni"
+)
+
+// A RuntimeConf holds the arguments to one invocation of a CNI plugin
+// excepting the network configuration, with the nested exception that
+// the `runtimeConfig` from the network configuration is included
+// here.
type RuntimeConf struct {
ContainerID string
NetNS string
@@ -34,6 +46,9 @@ type RuntimeConf struct {
// in this map which match the capabilities of the plugin are passed
// to the plugin
CapabilityArgs map[string]interface{}
+
+ // A cache directory in which to library data. Defaults to CacheDir
+ CacheDir string
}
type NetworkConfig struct {
@@ -50,25 +65,38 @@ type NetworkConfigList struct {
type CNI interface {
AddNetworkList(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
+ GetNetworkList(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
DelNetworkList(net *NetworkConfigList, rt *RuntimeConf) error
AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
+ GetNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
DelNetwork(net *NetworkConfig, rt *RuntimeConf) error
}
type CNIConfig struct {
Path []string
+ exec invoke.Exec
}
// CNIConfig implements the CNI interface
var _ CNI = &CNIConfig{}
-func buildOneConfig(list *NetworkConfigList, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) {
+// NewCNIConfig returns a new CNIConfig object that will search for plugins
+// in the given paths and use the given exec interface to run those plugins,
+// or if the exec interface is not given, will use a default exec handler.
+func NewCNIConfig(path []string, exec invoke.Exec) *CNIConfig {
+ return &CNIConfig{
+ Path: path,
+ exec: exec,
+ }
+}
+
+func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) {
var err error
inject := map[string]interface{}{
- "name": list.Name,
- "cniVersion": list.CNIVersion,
+ "name": name,
+ "cniVersion": cniVersion,
}
// Add previous plugin result
if prevResult != nil {
@@ -119,21 +147,37 @@ func injectRuntimeConfig(orig *NetworkConfig, rt *RuntimeConf) (*NetworkConfig,
return orig, nil
}
-// AddNetworkList executes a sequence of plugins with the ADD command
-func (c *CNIConfig) AddNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
- var prevResult types.Result
- for _, net := range list.Plugins {
- pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
- if err != nil {
- return nil, err
+// ensure we have a usable exec if the CNIConfig was not given one
+func (c *CNIConfig) ensureExec() invoke.Exec {
+ if c.exec == nil {
+ c.exec = &invoke.DefaultExec{
+ RawExec: &invoke.RawExec{Stderr: os.Stderr},
+ PluginDecoder: version.PluginDecoder{},
}
+ }
+ return c.exec
+}
- newConf, err := buildOneConfig(list, net, prevResult, rt)
- if err != nil {
- return nil, err
- }
+func (c *CNIConfig) addOrGetNetwork(command, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
+ c.ensureExec()
+ pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
+ if err != nil {
+ return nil, err
+ }
- prevResult, err = invoke.ExecPluginWithResult(pluginPath, newConf.Bytes, c.args("ADD", rt))
+ newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
+ if err != nil {
+ return nil, err
+ }
+
+ return invoke.ExecPluginWithResult(pluginPath, newConf.Bytes, c.args(command, rt), c.exec)
+}
+
+// Note that only GET requests should pass an initial prevResult
+func (c *CNIConfig) addOrGetNetworkList(command string, prevResult types.Result, list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
+ var err error
+ for _, net := range list.Plugins {
+ prevResult, err = c.addOrGetNetwork(command, list.Name, list.CNIVersion, net, prevResult, rt)
if err != nil {
return nil, err
}
@@ -142,68 +186,194 @@ func (c *CNIConfig) AddNetworkList(list *NetworkConfigList, rt *RuntimeConf) (ty
return prevResult, nil
}
+func getResultCacheFilePath(netName string, rt *RuntimeConf) string {
+ cacheDir := rt.CacheDir
+ if cacheDir == "" {
+ cacheDir = CacheDir
+ }
+ return filepath.Join(cacheDir, "results", fmt.Sprintf("%s-%s", netName, rt.ContainerID))
+}
+
+func setCachedResult(result types.Result, netName string, rt *RuntimeConf) error {
+ data, err := json.Marshal(result)
+ if err != nil {
+ return err
+ }
+ fname := getResultCacheFilePath(netName, rt)
+ if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil {
+ return err
+ }
+ return ioutil.WriteFile(fname, data, 0600)
+}
+
+func delCachedResult(netName string, rt *RuntimeConf) error {
+ fname := getResultCacheFilePath(netName, rt)
+ return os.Remove(fname)
+}
+
+func getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) {
+ fname := getResultCacheFilePath(netName, rt)
+ data, err := ioutil.ReadFile(fname)
+ if err != nil {
+ // Ignore read errors; the cached result may not exist on-disk
+ return nil, nil
+ }
+
+ // Read the version of the cached result
+ decoder := version.ConfigDecoder{}
+ resultCniVersion, err := decoder.Decode(data)
+ if err != nil {
+ return nil, err
+ }
+
+ // Ensure we can understand the result
+ result, err := version.NewResult(resultCniVersion, data)
+ if err != nil {
+ return nil, err
+ }
+
+ // Convert to the config version to ensure plugins get prevResult
+ // in the same version as the config. The cached result version
+ // should match the config version unless the config was changed
+ // while the container was running.
+ result, err = result.GetAsVersion(cniVersion)
+ if err != nil && resultCniVersion != cniVersion {
+ return nil, fmt.Errorf("failed to convert cached result version %q to config version %q: %v", resultCniVersion, cniVersion, err)
+ }
+ return result, err
+}
+
+// AddNetworkList executes a sequence of plugins with the ADD command
+func (c *CNIConfig) AddNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
+ result, err := c.addOrGetNetworkList("ADD", nil, list, rt)
+ if err != nil {
+ return nil, err
+ }
+
+ if err = setCachedResult(result, list.Name, rt); err != nil {
+ return nil, fmt.Errorf("failed to set network '%s' cached result: %v", list.Name, err)
+ }
+
+ return result, nil
+}
+
+// GetNetworkList executes a sequence of plugins with the GET command
+func (c *CNIConfig) GetNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
+ // GET was added in CNI spec version 0.4.0 and higher
+ if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
+ return nil, err
+ } else if !gtet {
+ return nil, fmt.Errorf("configuration version %q does not support the GET command", list.CNIVersion)
+ }
+
+ cachedResult, err := getCachedResult(list.Name, list.CNIVersion, rt)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get network '%s' cached result: %v", list.Name, err)
+ }
+ return c.addOrGetNetworkList("GET", cachedResult, list, rt)
+}
+
+func (c *CNIConfig) delNetwork(name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error {
+ c.ensureExec()
+ pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
+ if err != nil {
+ return err
+ }
+
+ newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
+ if err != nil {
+ return err
+ }
+
+ return invoke.ExecPluginWithoutResult(pluginPath, newConf.Bytes, c.args("DEL", rt), c.exec)
+}
+
// DelNetworkList executes a sequence of plugins with the DEL command
func (c *CNIConfig) DelNetworkList(list *NetworkConfigList, rt *RuntimeConf) error {
- for i := len(list.Plugins) - 1; i >= 0; i-- {
- net := list.Plugins[i]
-
- pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
- if err != nil {
- return err
- }
+ var cachedResult types.Result
- newConf, err := buildOneConfig(list, net, nil, rt)
+ // Cached result on DEL was added in CNI spec version 0.4.0 and higher
+ if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
+ return err
+ } else if gtet {
+ cachedResult, err = getCachedResult(list.Name, list.CNIVersion, rt)
if err != nil {
- return err
+ return fmt.Errorf("failed to get network '%s' cached result: %v", list.Name, err)
}
+ }
- if err := invoke.ExecPluginWithoutResult(pluginPath, newConf.Bytes, c.args("DEL", rt)); err != nil {
+ for i := len(list.Plugins) - 1; i >= 0; i-- {
+ net := list.Plugins[i]
+ if err := c.delNetwork(list.Name, list.CNIVersion, net, cachedResult, rt); err != nil {
return err
}
}
+ _ = delCachedResult(list.Name, rt)
return nil
}
// AddNetwork executes the plugin with the ADD command
func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
- pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
+ result, err := c.addOrGetNetwork("ADD", net.Network.Name, net.Network.CNIVersion, net, nil, rt)
if err != nil {
return nil, err
}
- net, err = injectRuntimeConfig(net, rt)
- if err != nil {
+ if err = setCachedResult(result, net.Network.Name, rt); err != nil {
+ return nil, fmt.Errorf("failed to set network '%s' cached result: %v", net.Network.Name, err)
+ }
+
+ return result, nil
+}
+
+// GetNetwork executes the plugin with the GET command
+func (c *CNIConfig) GetNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
+ // GET was added in CNI spec version 0.4.0 and higher
+ if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil {
return nil, err
+ } else if !gtet {
+ return nil, fmt.Errorf("configuration version %q does not support the GET command", net.Network.CNIVersion)
}
- return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt))
+ cachedResult, err := getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get network '%s' cached result: %v", net.Network.Name, err)
+ }
+ return c.addOrGetNetwork("GET", net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt)
}
// DelNetwork executes the plugin with the DEL command
func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error {
- pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
- if err != nil {
+ var cachedResult types.Result
+
+ // Cached result on DEL was added in CNI spec version 0.4.0 and higher
+ if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil {
return err
+ } else if gtet {
+ cachedResult, err = getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
+ if err != nil {
+ return fmt.Errorf("failed to get network '%s' cached result: %v", net.Network.Name, err)
+ }
}
- net, err = injectRuntimeConfig(net, rt)
- if err != nil {
+ if err := c.delNetwork(net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt); err != nil {
return err
}
-
- return invoke.ExecPluginWithoutResult(pluginPath, net.Bytes, c.args("DEL", rt))
+ _ = delCachedResult(net.Network.Name, rt)
+ return nil
}
// GetVersionInfo reports which versions of the CNI spec are supported by
// the given plugin.
func (c *CNIConfig) GetVersionInfo(pluginType string) (version.PluginInfo, error) {
- pluginPath, err := invoke.FindInPath(pluginType, c.Path)
+ c.ensureExec()
+ pluginPath, err := c.exec.FindInPath(pluginType, c.Path)
if err != nil {
return nil, err
}
- return invoke.GetVersionInfo(pluginPath)
+ return invoke.GetVersionInfo(pluginPath, c.exec)
}
// =====
diff --git a/vendor/github.com/containernetworking/cni/libcni/conf.go b/vendor/github.com/containernetworking/cni/libcni/conf.go
index c7738c665..9834d715b 100644
--- a/vendor/github.com/containernetworking/cni/libcni/conf.go
+++ b/vendor/github.com/containernetworking/cni/libcni/conf.go
@@ -45,6 +45,9 @@ func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
if err := json.Unmarshal(bytes, &conf.Network); err != nil {
return nil, fmt.Errorf("error parsing configuration: %s", err)
}
+ if conf.Network.Type == "" {
+ return nil, fmt.Errorf("error parsing configuration: missing 'type'")
+ }
return conf, nil
}
diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go b/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go
index c78a69eeb..21efdf802 100644
--- a/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go
+++ b/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go
@@ -22,32 +22,54 @@ import (
"github.com/containernetworking/cni/pkg/types"
)
-func DelegateAdd(delegatePlugin string, netconf []byte) (types.Result, error) {
- if os.Getenv("CNI_COMMAND") != "ADD" {
- return nil, fmt.Errorf("CNI_COMMAND is not ADD")
+func delegateAddOrGet(command, delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) {
+ if exec == nil {
+ exec = defaultExec
}
paths := filepath.SplitList(os.Getenv("CNI_PATH"))
-
- pluginPath, err := FindInPath(delegatePlugin, paths)
+ pluginPath, err := exec.FindInPath(delegatePlugin, paths)
if err != nil {
return nil, err
}
- return ExecPluginWithResult(pluginPath, netconf, ArgsFromEnv())
+ return ExecPluginWithResult(pluginPath, netconf, ArgsFromEnv(), exec)
+}
+
+// DelegateAdd calls the given delegate plugin with the CNI ADD action and
+// JSON configuration
+func DelegateAdd(delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) {
+ if os.Getenv("CNI_COMMAND") != "ADD" {
+ return nil, fmt.Errorf("CNI_COMMAND is not ADD")
+ }
+ return delegateAddOrGet("ADD", delegatePlugin, netconf, exec)
+}
+
+// DelegateGet calls the given delegate plugin with the CNI GET action and
+// JSON configuration
+func DelegateGet(delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) {
+ if os.Getenv("CNI_COMMAND") != "GET" {
+ return nil, fmt.Errorf("CNI_COMMAND is not GET")
+ }
+ return delegateAddOrGet("GET", delegatePlugin, netconf, exec)
}
-func DelegateDel(delegatePlugin string, netconf []byte) error {
+// DelegateDel calls the given delegate plugin with the CNI DEL action and
+// JSON configuration
+func DelegateDel(delegatePlugin string, netconf []byte, exec Exec) error {
+ if exec == nil {
+ exec = defaultExec
+ }
+
if os.Getenv("CNI_COMMAND") != "DEL" {
return fmt.Errorf("CNI_COMMAND is not DEL")
}
paths := filepath.SplitList(os.Getenv("CNI_PATH"))
-
- pluginPath, err := FindInPath(delegatePlugin, paths)
+ pluginPath, err := exec.FindInPath(delegatePlugin, paths)
if err != nil {
return err
}
- return ExecPluginWithoutResult(pluginPath, netconf, ArgsFromEnv())
+ return ExecPluginWithoutResult(pluginPath, netconf, ArgsFromEnv(), exec)
}
diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go b/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go
index fc47e7c82..cf019d3a0 100644
--- a/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go
+++ b/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go
@@ -22,34 +22,62 @@ import (
"github.com/containernetworking/cni/pkg/version"
)
-func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (types.Result, error) {
- return defaultPluginExec.WithResult(pluginPath, netconf, args)
+// Exec is an interface encapsulates all operations that deal with finding
+// and executing a CNI plugin. Tests may provide a fake implementation
+// to avoid writing fake plugins to temporary directories during the test.
+type Exec interface {
+ ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error)
+ FindInPath(plugin string, paths []string) (string, error)
+ Decode(jsonBytes []byte) (version.PluginInfo, error)
}
-func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs) error {
- return defaultPluginExec.WithoutResult(pluginPath, netconf, args)
-}
-
-func GetVersionInfo(pluginPath string) (version.PluginInfo, error) {
- return defaultPluginExec.GetVersionInfo(pluginPath)
-}
-
-var defaultPluginExec = &PluginExec{
- RawExec: &RawExec{Stderr: os.Stderr},
- VersionDecoder: &version.PluginDecoder{},
-}
+// For example, a testcase could pass an instance of the following fakeExec
+// object to ExecPluginWithResult() to verify the incoming stdin and environment
+// and provide a tailored response:
+//
+//import (
+// "encoding/json"
+// "path"
+// "strings"
+//)
+//
+//type fakeExec struct {
+// version.PluginDecoder
+//}
+//
+//func (f *fakeExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
+// net := &types.NetConf{}
+// err := json.Unmarshal(stdinData, net)
+// if err != nil {
+// return nil, fmt.Errorf("failed to unmarshal configuration: %v", err)
+// }
+// pluginName := path.Base(pluginPath)
+// if pluginName != net.Type {
+// return nil, fmt.Errorf("plugin name %q did not match config type %q", pluginName, net.Type)
+// }
+// for _, e := range environ {
+// // Check environment for forced failure request
+// parts := strings.Split(e, "=")
+// if len(parts) > 0 && parts[0] == "FAIL" {
+// return nil, fmt.Errorf("failed to execute plugin %s", pluginName)
+// }
+// }
+// return []byte("{\"CNIVersion\":\"0.4.0\"}"), nil
+//}
+//
+//func (f *fakeExec) FindInPath(plugin string, paths []string) (string, error) {
+// if len(paths) > 0 {
+// return path.Join(paths[0], plugin), nil
+// }
+// return "", fmt.Errorf("failed to find plugin %s in paths %v", plugin, paths)
+//}
-type PluginExec struct {
- RawExec interface {
- ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error)
+func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs, exec Exec) (types.Result, error) {
+ if exec == nil {
+ exec = defaultExec
}
- VersionDecoder interface {
- Decode(jsonBytes []byte) (version.PluginInfo, error)
- }
-}
-func (e *PluginExec) WithResult(pluginPath string, netconf []byte, args CNIArgs) (types.Result, error) {
- stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv())
+ stdoutBytes, err := exec.ExecPlugin(pluginPath, netconf, args.AsEnv())
if err != nil {
return nil, err
}
@@ -64,8 +92,11 @@ func (e *PluginExec) WithResult(pluginPath string, netconf []byte, args CNIArgs)
return version.NewResult(confVersion, stdoutBytes)
}
-func (e *PluginExec) WithoutResult(pluginPath string, netconf []byte, args CNIArgs) error {
- _, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv())
+func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs, exec Exec) error {
+ if exec == nil {
+ exec = defaultExec
+ }
+ _, err := exec.ExecPlugin(pluginPath, netconf, args.AsEnv())
return err
}
@@ -73,7 +104,10 @@ func (e *PluginExec) WithoutResult(pluginPath string, netconf []byte, args CNIAr
// For recent-enough plugins, it uses the information returned by the VERSION
// command. For older plugins which do not recognize that command, it reports
// version 0.1.0
-func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, error) {
+func GetVersionInfo(pluginPath string, exec Exec) (version.PluginInfo, error) {
+ if exec == nil {
+ exec = defaultExec
+ }
args := &Args{
Command: "VERSION",
@@ -83,7 +117,7 @@ func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, erro
Path: "dummy",
}
stdin := []byte(fmt.Sprintf(`{"cniVersion":%q}`, version.Current()))
- stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, stdin, args.AsEnv())
+ stdoutBytes, err := exec.ExecPlugin(pluginPath, stdin, args.AsEnv())
if err != nil {
if err.Error() == "unknown CNI_COMMAND: VERSION" {
return version.PluginSupports("0.1.0"), nil
@@ -91,5 +125,19 @@ func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, erro
return nil, err
}
- return e.VersionDecoder.Decode(stdoutBytes)
+ return exec.Decode(stdoutBytes)
+}
+
+// DefaultExec is an object that implements the Exec interface which looks
+// for and executes plugins from disk.
+type DefaultExec struct {
+ *RawExec
+ version.PluginDecoder
+}
+
+// DefaultExec implements the Exec interface
+var _ Exec = &DefaultExec{}
+
+var defaultExec = &DefaultExec{
+ RawExec: &RawExec{Stderr: os.Stderr},
}
diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go b/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go
index 93f1e75d9..a598f09c2 100644
--- a/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go
+++ b/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go
@@ -57,3 +57,7 @@ func pluginErr(err error, output []byte) error {
return err
}
+
+func (e *RawExec) FindInPath(plugin string, paths []string) (string, error) {
+ return FindInPath(plugin, paths)
+}
diff --git a/vendor/github.com/containernetworking/cni/pkg/types/current/types.go b/vendor/github.com/containernetworking/cni/pkg/types/current/types.go
index caac92ba7..92980c1a7 100644
--- a/vendor/github.com/containernetworking/cni/pkg/types/current/types.go
+++ b/vendor/github.com/containernetworking/cni/pkg/types/current/types.go
@@ -24,9 +24,9 @@ import (
"github.com/containernetworking/cni/pkg/types/020"
)
-const ImplementedSpecVersion string = "0.3.1"
+const ImplementedSpecVersion string = "0.4.0"
-var SupportedVersions = []string{"0.3.0", ImplementedSpecVersion}
+var SupportedVersions = []string{"0.3.0", "0.3.1", ImplementedSpecVersion}
func NewResult(data []byte) (types.Result, error) {
result := &Result{}
@@ -196,7 +196,7 @@ func (r *Result) Version() string {
func (r *Result) GetAsVersion(version string) (types.Result, error) {
switch version {
- case "0.3.0", ImplementedSpecVersion:
+ case "0.3.0", "0.3.1", ImplementedSpecVersion:
r.CNIVersion = version
return r, nil
case types020.SupportedVersions[0], types020.SupportedVersions[1], types020.SupportedVersions[2]:
diff --git a/vendor/github.com/containernetworking/cni/pkg/types/types.go b/vendor/github.com/containernetworking/cni/pkg/types/types.go
index 641275600..4684a3207 100644
--- a/vendor/github.com/containernetworking/cni/pkg/types/types.go
+++ b/vendor/github.com/containernetworking/cni/pkg/types/types.go
@@ -63,10 +63,12 @@ type NetConf struct {
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Capabilities map[string]bool `json:"capabilities,omitempty"`
- IPAM struct {
- Type string `json:"type,omitempty"`
- } `json:"ipam,omitempty"`
- DNS DNS `json:"dns"`
+ IPAM IPAM `json:"ipam,omitempty"`
+ DNS DNS `json:"dns"`
+}
+
+type IPAM struct {
+ Type string `json:"type,omitempty"`
}
// NetConfList describes an ordered list of networks.
@@ -167,7 +169,7 @@ func (r *Route) UnmarshalJSON(data []byte) error {
return nil
}
-func (r *Route) MarshalJSON() ([]byte, error) {
+func (r Route) MarshalJSON() ([]byte, error) {
rt := route{
Dst: IPNet(r.Dst),
GW: r.GW,
diff --git a/vendor/github.com/containernetworking/cni/pkg/version/plugin.go b/vendor/github.com/containernetworking/cni/pkg/version/plugin.go
index 8a4672810..612335a81 100644
--- a/vendor/github.com/containernetworking/cni/pkg/version/plugin.go
+++ b/vendor/github.com/containernetworking/cni/pkg/version/plugin.go
@@ -18,6 +18,8 @@ import (
"encoding/json"
"fmt"
"io"
+ "strconv"
+ "strings"
)
// PluginInfo reports information about CNI versioning
@@ -79,3 +81,60 @@ func (*PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) {
}
return &info, nil
}
+
+// ParseVersion parses a version string like "3.0.1" or "0.4.5" into major,
+// minor, and micro numbers or returns an error
+func ParseVersion(version string) (int, int, int, error) {
+ var major, minor, micro int
+ parts := strings.Split(version, ".")
+ if len(parts) == 0 || len(parts) >= 4 {
+ return -1, -1, -1, fmt.Errorf("invalid version %q: too many or too few parts", version)
+ }
+
+ major, err := strconv.Atoi(parts[0])
+ if err != nil {
+ return -1, -1, -1, fmt.Errorf("failed to convert major version part %q: %v", parts[0], err)
+ }
+
+ if len(parts) >= 2 {
+ minor, err = strconv.Atoi(parts[1])
+ if err != nil {
+ return -1, -1, -1, fmt.Errorf("failed to convert minor version part %q: %v", parts[1], err)
+ }
+ }
+
+ if len(parts) >= 3 {
+ micro, err = strconv.Atoi(parts[2])
+ if err != nil {
+ return -1, -1, -1, fmt.Errorf("failed to convert micro version part %q: %v", parts[2], err)
+ }
+ }
+
+ return major, minor, micro, nil
+}
+
+// GreaterThanOrEqualTo takes two string versions, parses them into major/minor/micro
+// nubmers, and compares them to determine whether the first version is greater
+// than or equal to the second
+func GreaterThanOrEqualTo(version, otherVersion string) (bool, error) {
+ firstMajor, firstMinor, firstMicro, err := ParseVersion(version)
+ if err != nil {
+ return false, err
+ }
+
+ secondMajor, secondMinor, secondMicro, err := ParseVersion(otherVersion)
+ if err != nil {
+ return false, err
+ }
+
+ if firstMajor > secondMajor {
+ return true, nil
+ } else if firstMajor == secondMajor {
+ if firstMinor > secondMinor {
+ return true, nil
+ } else if firstMinor == secondMinor && firstMicro >= secondMicro {
+ return true, nil
+ }
+ }
+ return false, nil
+}
diff --git a/vendor/github.com/containernetworking/cni/pkg/version/version.go b/vendor/github.com/containernetworking/cni/pkg/version/version.go
index efe8ea871..c8e46d55b 100644
--- a/vendor/github.com/containernetworking/cni/pkg/version/version.go
+++ b/vendor/github.com/containernetworking/cni/pkg/version/version.go
@@ -24,7 +24,7 @@ import (
// Current reports the version of the CNI spec implemented by this library
func Current() string {
- return "0.3.1"
+ return "0.4.0"
}
// Legacy PluginInfo describes a plugin that is backwards compatible with the
@@ -35,7 +35,7 @@ func Current() string {
// Any future CNI spec versions which meet this definition should be added to
// this list.
var Legacy = PluginSupports("0.1.0", "0.2.0")
-var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1")
+var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0")
var resultFactories = []struct {
supportedVersions []string
diff --git a/vendor/github.com/containers/storage/pkg/archive/example_changes.go b/vendor/github.com/containers/storage/pkg/archive/example_changes.go
deleted file mode 100644
index 70f9c5564..000000000
--- a/vendor/github.com/containers/storage/pkg/archive/example_changes.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// +build ignore
-
-// Simple tool to create an archive stream from an old and new directory
-//
-// By default it will stream the comparison of two temporary directories with junk files
-package main
-
-import (
- "flag"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path"
-
- "github.com/containers/storage/pkg/archive"
- "github.com/sirupsen/logrus"
-)
-
-var (
- flDebug = flag.Bool("D", false, "debugging output")
- flNewDir = flag.String("newdir", "", "")
- flOldDir = flag.String("olddir", "", "")
- log = logrus.New()
-)
-
-func main() {
- flag.Usage = func() {
- fmt.Println("Produce a tar from comparing two directory paths. By default a demo tar is created of around 200 files (including hardlinks)")
- fmt.Printf("%s [OPTIONS]\n", os.Args[0])
- flag.PrintDefaults()
- }
- flag.Parse()
- log.Out = os.Stderr
- if (len(os.Getenv("DEBUG")) > 0) || *flDebug {
- logrus.SetLevel(logrus.DebugLevel)
- }
- var newDir, oldDir string
-
- if len(*flNewDir) == 0 {
- var err error
- newDir, err = ioutil.TempDir("", "storage-test-newDir")
- if err != nil {
- log.Fatal(err)
- }
- defer os.RemoveAll(newDir)
- if _, err := prepareUntarSourceDirectory(100, newDir, true); err != nil {
- log.Fatal(err)
- }
- } else {
- newDir = *flNewDir
- }
-
- if len(*flOldDir) == 0 {
- oldDir, err := ioutil.TempDir("", "storage-test-oldDir")
- if err != nil {
- log.Fatal(err)
- }
- defer os.RemoveAll(oldDir)
- } else {
- oldDir = *flOldDir
- }
-
- changes, err := archive.ChangesDirs(newDir, oldDir)
- if err != nil {
- log.Fatal(err)
- }
-
- a, err := archive.ExportChanges(newDir, changes)
- if err != nil {
- log.Fatal(err)
- }
- defer a.Close()
-
- i, err := io.Copy(os.Stdout, a)
- if err != nil && err != io.EOF {
- log.Fatal(err)
- }
- fmt.Fprintf(os.Stderr, "wrote archive of %d bytes", i)
-}
-
-func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) {
- fileData := []byte("fooo")
- for n := 0; n < numberOfFiles; n++ {
- fileName := fmt.Sprintf("file-%d", n)
- if err := ioutil.WriteFile(path.Join(targetPath, fileName), fileData, 0700); err != nil {
- return 0, err
- }
- if makeLinks {
- if err := os.Link(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil {
- return 0, err
- }
- }
- }
- totalSize := numberOfFiles * len(fileData)
- return totalSize, nil
-}
diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go
index e49c1dc84..33a3ae063 100644
--- a/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go
+++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go
@@ -4,13 +4,16 @@ import (
"errors"
"fmt"
"os"
- "os/exec"
+ "path"
"sort"
"strings"
"sync"
"github.com/containernetworking/cni/libcni"
+ cniinvoke "github.com/containernetworking/cni/pkg/invoke"
cnitypes "github.com/containernetworking/cni/pkg/types"
+ cnicurrent "github.com/containernetworking/cni/pkg/types/current"
+ cniversion "github.com/containernetworking/cni/pkg/version"
"github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus"
)
@@ -19,14 +22,16 @@ type cniNetworkPlugin struct {
loNetwork *cniNetwork
sync.RWMutex
- defaultNetwork *cniNetwork
+ defaultNetName string
+ networks map[string]*cniNetwork
- nsenterPath string
- pluginDir string
- cniDirs []string
- vendorCNIDirPrefix string
+ nsManager *nsManager
+ confDir string
+ binDirs []string
- monitorNetDirChan chan struct{}
+ shutdownChan chan struct{}
+ watcher *fsnotify.Watcher
+ done *sync.WaitGroup
// The pod map provides synchronization for a given pod's network
// operations. Each pod's setup/teardown/status operations
@@ -34,12 +39,17 @@ type cniNetworkPlugin struct {
// pods can proceed in parallel.
podsLock sync.Mutex
pods map[string]*podLock
+
+ // For testcases
+ exec cniinvoke.Exec
+ cacheDir string
}
type cniNetwork struct {
name string
+ filePath string
NetworkConfig *libcni.NetworkConfigList
- CNIConfig libcni.CNI
+ CNIConfig *libcni.CNIConfig
}
var errMissingDefaultNetwork = errors.New("Missing CNI default network")
@@ -99,110 +109,150 @@ func (plugin *cniNetworkPlugin) podUnlock(podNetwork PodNetwork) {
}
}
-func (plugin *cniNetworkPlugin) monitorNetDir() {
+func newWatcher(confDir string) (*fsnotify.Watcher, error) {
+ // Ensure plugin directory exists, because the following monitoring logic
+ // relies on that.
+ if err := os.MkdirAll(confDir, 0755); err != nil {
+ return nil, fmt.Errorf("failed to create %q: %v", confDir, err)
+ }
+
watcher, err := fsnotify.NewWatcher()
if err != nil {
- logrus.Errorf("could not create new watcher %v", err)
- return
+ return nil, fmt.Errorf("could not create new watcher %v", err)
}
- defer watcher.Close()
+ defer func() {
+ // Close watcher on error
+ if err != nil {
+ watcher.Close()
+ }
+ }()
- if err = watcher.Add(plugin.pluginDir); err != nil {
- logrus.Errorf("Failed to add watch on %q: %v", plugin.pluginDir, err)
- return
+ if err = watcher.Add(confDir); err != nil {
+ return nil, fmt.Errorf("failed to add watch on %q: %v", confDir, err)
}
- // Now that `watcher` is running and watching the `pluginDir`
- // gather the initial configuration, before starting the
- // goroutine which will actually process events. It has to be
- // done in this order to avoid missing any updates which might
- // otherwise occur between gathering the initial configuration
- // and starting the watcher.
- if err := plugin.syncNetworkConfig(); err != nil {
- logrus.Infof("Initial CNI setting failed, continue monitoring: %v", err)
- } else {
- logrus.Infof("Initial CNI setting succeeded")
- }
-
- go func() {
- for {
- select {
- case event := <-watcher.Events:
- logrus.Debugf("CNI monitoring event %v", event)
- if event.Op&fsnotify.Create != fsnotify.Create &&
- event.Op&fsnotify.Write != fsnotify.Write {
- continue
- }
+ return watcher, nil
+}
- if err = plugin.syncNetworkConfig(); err == nil {
- logrus.Infof("CNI asynchronous setting succeeded")
- continue
+func (plugin *cniNetworkPlugin) monitorConfDir(start *sync.WaitGroup) {
+ start.Done()
+ plugin.done.Add(1)
+ defer plugin.done.Done()
+ for {
+ select {
+ case event := <-plugin.watcher.Events:
+ logrus.Warningf("CNI monitoring event %v", event)
+
+ var defaultDeleted bool
+ createWrite := (event.Op&fsnotify.Create == fsnotify.Create ||
+ event.Op&fsnotify.Write == fsnotify.Write)
+ if event.Op&fsnotify.Remove == fsnotify.Remove {
+ // Care about the event if the default network
+ // was just deleted
+ defNet := plugin.getDefaultNetwork()
+ if defNet != nil && event.Name == defNet.filePath {
+ defaultDeleted = true
}
- logrus.Errorf("CNI setting failed, continue monitoring: %v", err)
+ }
+ if !createWrite && !defaultDeleted {
+ continue
+ }
- case err := <-watcher.Errors:
- if err == nil {
- continue
- }
- logrus.Errorf("CNI monitoring error %v", err)
- close(plugin.monitorNetDirChan)
- return
+ if err := plugin.syncNetworkConfig(); err != nil {
+ logrus.Errorf("CNI config loading failed, continue monitoring: %v", err)
+ continue
}
+
+ case err := <-plugin.watcher.Errors:
+ if err == nil {
+ continue
+ }
+ logrus.Errorf("CNI monitoring error %v", err)
+ return
+
+ case <-plugin.shutdownChan:
+ return
}
- }()
+ }
+}
- <-plugin.monitorNetDirChan
+// InitCNI takes a binary directory in which to search for CNI plugins, and
+// a configuration directory in which to search for CNI JSON config files.
+// If no valid CNI configs exist, network requests will fail until valid CNI
+// config files are present in the config directory.
+// If defaultNetName is not empty, a CNI config with that network name will
+// be used as the default CNI network, and container network operations will
+// fail until that network config is present and valid.
+func InitCNI(defaultNetName string, confDir string, binDirs ...string) (CNIPlugin, error) {
+ return initCNI(nil, "", defaultNetName, confDir, binDirs...)
}
-// InitCNI takes the plugin directory and CNI directories where the CNI config
-// files should be searched for. If no valid CNI configs exist, network requests
-// will fail until valid CNI config files are present in the config directory.
-func InitCNI(pluginDir string, cniDirs ...string) (CNIPlugin, error) {
- vendorCNIDirPrefix := ""
+// Internal function to allow faking out exec functions for testing
+func initCNI(exec cniinvoke.Exec, cacheDir, defaultNetName string, confDir string, binDirs ...string) (CNIPlugin, error) {
+ if confDir == "" {
+ confDir = DefaultConfDir
+ }
+ if len(binDirs) == 0 {
+ binDirs = []string{DefaultBinDir}
+ }
plugin := &cniNetworkPlugin{
- defaultNetwork: nil,
- loNetwork: getLoNetwork(cniDirs, vendorCNIDirPrefix),
- pluginDir: pluginDir,
- cniDirs: cniDirs,
- vendorCNIDirPrefix: vendorCNIDirPrefix,
- monitorNetDirChan: make(chan struct{}),
- pods: make(map[string]*podLock),
+ defaultNetName: defaultNetName,
+ networks: make(map[string]*cniNetwork),
+ loNetwork: getLoNetwork(exec, binDirs),
+ confDir: confDir,
+ binDirs: binDirs,
+ shutdownChan: make(chan struct{}),
+ done: &sync.WaitGroup{},
+ pods: make(map[string]*podLock),
+ exec: exec,
+ cacheDir: cacheDir,
+ }
+
+ if exec == nil {
+ exec = &cniinvoke.DefaultExec{
+ RawExec: &cniinvoke.RawExec{Stderr: os.Stderr},
+ PluginDecoder: cniversion.PluginDecoder{},
+ }
}
- var err error
- plugin.nsenterPath, err = exec.LookPath("nsenter")
+ nsm, err := newNSManager()
if err != nil {
return nil, err
}
+ plugin.nsManager = nsm
- // Ensure plugin directory exists, because the following monitoring logic
- // relies on that.
- if err := os.MkdirAll(pluginDir, 0755); err != nil {
+ plugin.syncNetworkConfig()
+
+ plugin.watcher, err = newWatcher(plugin.confDir)
+ if err != nil {
return nil, err
}
- go plugin.monitorNetDir()
+ startWg := sync.WaitGroup{}
+ startWg.Add(1)
+ go plugin.monitorConfDir(&startWg)
+ startWg.Wait()
return plugin, nil
}
-func getDefaultCNINetwork(pluginDir string, cniDirs []string, vendorCNIDirPrefix string) (*cniNetwork, error) {
- if pluginDir == "" {
- pluginDir = DefaultNetDir
- }
- if len(cniDirs) == 0 {
- cniDirs = []string{DefaultCNIDir}
- }
+func (plugin *cniNetworkPlugin) Shutdown() error {
+ close(plugin.shutdownChan)
+ plugin.watcher.Close()
+ plugin.done.Wait()
+ return nil
+}
- files, err := libcni.ConfFiles(pluginDir, []string{".conf", ".conflist", ".json"})
- switch {
- case err != nil:
- return nil, err
- case len(files) == 0:
- return nil, errMissingDefaultNetwork
+func loadNetworks(exec cniinvoke.Exec, confDir string, binDirs []string) (map[string]*cniNetwork, string, error) {
+ files, err := libcni.ConfFiles(confDir, []string{".conf", ".conflist", ".json"})
+ if err != nil {
+ return nil, "", err
}
+ networks := make(map[string]*cniNetwork)
+ defaultNetName := ""
+
sort.Strings(files)
for _, confFile := range files {
var confList *libcni.NetworkConfigList
@@ -232,27 +282,28 @@ func getDefaultCNINetwork(pluginDir string, cniDirs []string, vendorCNIDirPrefix
logrus.Warningf("CNI config list %s has no networks, skipping", confFile)
continue
}
- logrus.Infof("CNI network %s (type=%v) is used from %s", confList.Name, confList.Plugins[0].Network.Type, confFile)
- // Search for vendor-specific plugins as well as default plugins in the CNI codebase.
- vendorDir := vendorCNIDir(vendorCNIDirPrefix, confList.Plugins[0].Network.Type)
- cninet := &libcni.CNIConfig{
- Path: append(cniDirs, vendorDir),
+ if confList.Name == "" {
+ confList.Name = path.Base(confFile)
}
- network := &cniNetwork{name: confList.Name, NetworkConfig: confList, CNIConfig: cninet}
- return network, nil
- }
- return nil, fmt.Errorf("No valid networks found in %s", pluginDir)
-}
-func vendorCNIDir(prefix, pluginType string) string {
- return fmt.Sprintf(VendorCNIDirTemplate, prefix, pluginType)
-}
+ logrus.Infof("Found CNI network %s (type=%v) at %s", confList.Name, confList.Plugins[0].Network.Type, confFile)
+
+ networks[confList.Name] = &cniNetwork{
+ name: confList.Name,
+ filePath: confFile,
+ NetworkConfig: confList,
+ CNIConfig: libcni.NewCNIConfig(binDirs, exec),
+ }
-func getLoNetwork(cniDirs []string, vendorDirPrefix string) *cniNetwork {
- if len(cniDirs) == 0 {
- cniDirs = []string{DefaultCNIDir}
+ if defaultNetName == "" {
+ defaultNetName = confList.Name
+ }
}
+ return networks, defaultNetName, nil
+}
+
+func getLoNetwork(exec cniinvoke.Exec, binDirs []string) *cniNetwork {
loConfig, err := libcni.ConfListFromBytes([]byte(`{
"cniVersion": "0.2.0",
"name": "cni-loopback",
@@ -265,45 +316,62 @@ func getLoNetwork(cniDirs []string, vendorDirPrefix string) *cniNetwork {
// catch this
panic(err)
}
- vendorDir := vendorCNIDir(vendorDirPrefix, loConfig.Plugins[0].Network.Type)
- cninet := &libcni.CNIConfig{
- Path: append(cniDirs, vendorDir),
- }
loNetwork := &cniNetwork{
name: "lo",
NetworkConfig: loConfig,
- CNIConfig: cninet,
+ CNIConfig: libcni.NewCNIConfig(binDirs, exec),
}
return loNetwork
}
func (plugin *cniNetworkPlugin) syncNetworkConfig() error {
- network, err := getDefaultCNINetwork(plugin.pluginDir, plugin.cniDirs, plugin.vendorCNIDirPrefix)
+ networks, defaultNetName, err := loadNetworks(plugin.exec, plugin.confDir, plugin.binDirs)
if err != nil {
- logrus.Errorf("error updating cni config: %s", err)
return err
}
- plugin.setDefaultNetwork(network)
+
+ plugin.Lock()
+ defer plugin.Unlock()
+ if plugin.defaultNetName == "" {
+ plugin.defaultNetName = defaultNetName
+ }
+ plugin.networks = networks
return nil
}
-func (plugin *cniNetworkPlugin) getDefaultNetwork() *cniNetwork {
+func (plugin *cniNetworkPlugin) getNetwork(name string) (*cniNetwork, error) {
plugin.RLock()
defer plugin.RUnlock()
- return plugin.defaultNetwork
+ net, ok := plugin.networks[name]
+ if !ok {
+ return nil, fmt.Errorf("CNI network %q not found", name)
+ }
+ return net, nil
}
-func (plugin *cniNetworkPlugin) setDefaultNetwork(n *cniNetwork) {
- plugin.Lock()
- defer plugin.Unlock()
- plugin.defaultNetwork = n
+func (plugin *cniNetworkPlugin) getDefaultNetworkName() string {
+ plugin.RLock()
+ defer plugin.RUnlock()
+ return plugin.defaultNetName
}
-func (plugin *cniNetworkPlugin) checkInitialized() error {
- if plugin.getDefaultNetwork() == nil {
- return errors.New("cni config uninitialized")
+func (plugin *cniNetworkPlugin) getDefaultNetwork() *cniNetwork {
+ defaultNetName := plugin.getDefaultNetworkName()
+ if defaultNetName == "" {
+ return nil
+ }
+ network, _ := plugin.getNetwork(defaultNetName)
+ return network
+}
+
+// networksAvailable returns an error if the pod requests no networks and the
+// plugin has no default network, and thus the plugin has no idea what network
+// to attach the pod to.
+func (plugin *cniNetworkPlugin) networksAvailable(podNetwork *PodNetwork) error {
+ if len(podNetwork.Networks) == 0 && plugin.getDefaultNetwork() == nil {
+ return errMissingDefaultNetwork
}
return nil
}
@@ -312,59 +380,119 @@ func (plugin *cniNetworkPlugin) Name() string {
return CNIPluginName
}
-func (plugin *cniNetworkPlugin) SetUpPod(podNetwork PodNetwork) (cnitypes.Result, error) {
- if err := plugin.checkInitialized(); err != nil {
+func (plugin *cniNetworkPlugin) forEachNetwork(podNetwork *PodNetwork, forEachFunc func(*cniNetwork, string, *PodNetwork) error) error {
+ networks := podNetwork.Networks
+ if len(networks) == 0 {
+ networks = append(networks, plugin.getDefaultNetworkName())
+ }
+ for i, netName := range networks {
+ // Interface names start at "eth0" and count up for each network
+ ifName := fmt.Sprintf("eth%d", i)
+ network, err := plugin.getNetwork(netName)
+ if err != nil {
+ logrus.Errorf(err.Error())
+ return err
+ }
+ if err := forEachFunc(network, ifName, podNetwork); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (plugin *cniNetworkPlugin) SetUpPod(podNetwork PodNetwork) ([]cnitypes.Result, error) {
+ if err := plugin.networksAvailable(&podNetwork); err != nil {
return nil, err
}
plugin.podLock(podNetwork).Lock()
defer plugin.podUnlock(podNetwork)
- _, err := plugin.loNetwork.addToNetwork(podNetwork)
+ _, err := plugin.loNetwork.addToNetwork(plugin.cacheDir, &podNetwork, "lo")
if err != nil {
logrus.Errorf("Error while adding to cni lo network: %s", err)
return nil, err
}
- result, err := plugin.getDefaultNetwork().addToNetwork(podNetwork)
- if err != nil {
- logrus.Errorf("Error while adding to cni network: %s", err)
+ results := make([]cnitypes.Result, 0)
+ if err := plugin.forEachNetwork(&podNetwork, func(network *cniNetwork, ifName string, podNetwork *PodNetwork) error {
+ result, err := network.addToNetwork(plugin.cacheDir, podNetwork, ifName)
+ if err != nil {
+ logrus.Errorf("Error while adding pod to CNI network %q: %s", network.name, err)
+ return err
+ }
+ results = append(results, result)
+ return nil
+ }); err != nil {
return nil, err
}
- return result, err
+ return results, nil
}
func (plugin *cniNetworkPlugin) TearDownPod(podNetwork PodNetwork) error {
- if err := plugin.checkInitialized(); err != nil {
+ if err := plugin.networksAvailable(&podNetwork); err != nil {
return err
}
plugin.podLock(podNetwork).Lock()
defer plugin.podUnlock(podNetwork)
- return plugin.getDefaultNetwork().deleteFromNetwork(podNetwork)
+ return plugin.forEachNetwork(&podNetwork, func(network *cniNetwork, ifName string, podNetwork *PodNetwork) error {
+ if err := network.deleteFromNetwork(plugin.cacheDir, podNetwork, ifName); err != nil {
+ logrus.Errorf("Error while removing pod from CNI network %q: %s", network.name, err)
+ return err
+ }
+ return nil
+ })
}
-// TODO: Use the addToNetwork function to obtain the IP of the Pod. That will assume idempotent ADD call to the plugin.
-// Also fix the runtime's call to Status function to be done only in the case that the IP is lost, no need to do periodic calls
-func (plugin *cniNetworkPlugin) GetPodNetworkStatus(podNetwork PodNetwork) (string, error) {
+// GetPodNetworkStatus returns IP addressing and interface details for all
+// networks attached to the pod.
+func (plugin *cniNetworkPlugin) GetPodNetworkStatus(podNetwork PodNetwork) ([]cnitypes.Result, error) {
plugin.podLock(podNetwork).Lock()
defer plugin.podUnlock(podNetwork)
- ip, err := getContainerIP(plugin.nsenterPath, podNetwork.NetNS, DefaultInterfaceName, "-4")
- if err != nil {
- ip, err = getContainerIP(plugin.nsenterPath, podNetwork.NetNS, DefaultInterfaceName, "-6")
- }
- if err != nil {
- return "", err
+ results := make([]cnitypes.Result, 0)
+ if err := plugin.forEachNetwork(&podNetwork, func(network *cniNetwork, ifName string, podNetwork *PodNetwork) error {
+ version := "4"
+ ip, mac, err := getContainerDetails(plugin.nsManager, podNetwork.NetNS, ifName, "-4")
+ if err != nil {
+ ip, mac, err = getContainerDetails(plugin.nsManager, podNetwork.NetNS, ifName, "-6")
+ if err != nil {
+ return err
+ }
+ version = "6"
+ }
+
+ // Until CNI's GET request lands, construct the Result manually
+ results = append(results, &cnicurrent.Result{
+ CNIVersion: "0.3.1",
+ Interfaces: []*cnicurrent.Interface{
+ {
+ Name: ifName,
+ Mac: mac.String(),
+ Sandbox: podNetwork.NetNS,
+ },
+ },
+ IPs: []*cnicurrent.IPConfig{
+ {
+ Version: version,
+ Interface: cnicurrent.Int(0),
+ Address: *ip,
+ },
+ },
+ })
+ return nil
+ }); err != nil {
+ return nil, err
}
- return ip.String(), nil
+ return results, nil
}
-func (network *cniNetwork) addToNetwork(podNetwork PodNetwork) (cnitypes.Result, error) {
- rt, err := buildCNIRuntimeConf(podNetwork)
+func (network *cniNetwork) addToNetwork(cacheDir string, podNetwork *PodNetwork, ifName string) (cnitypes.Result, error) {
+ rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName)
if err != nil {
logrus.Errorf("Error adding network: %v", err)
return nil, err
@@ -381,8 +509,8 @@ func (network *cniNetwork) addToNetwork(podNetwork PodNetwork) (cnitypes.Result,
return res, nil
}
-func (network *cniNetwork) deleteFromNetwork(podNetwork PodNetwork) error {
- rt, err := buildCNIRuntimeConf(podNetwork)
+func (network *cniNetwork) deleteFromNetwork(cacheDir string, podNetwork *PodNetwork, ifName string) error {
+ rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName)
if err != nil {
logrus.Errorf("Error deleting network: %v", err)
return err
@@ -398,13 +526,14 @@ func (network *cniNetwork) deleteFromNetwork(podNetwork PodNetwork) error {
return nil
}
-func buildCNIRuntimeConf(podNetwork PodNetwork) (*libcni.RuntimeConf, error) {
+func buildCNIRuntimeConf(cacheDir string, podNetwork *PodNetwork, ifName string) (*libcni.RuntimeConf, error) {
logrus.Infof("Got pod network %+v", podNetwork)
rt := &libcni.RuntimeConf{
ContainerID: podNetwork.ID,
NetNS: podNetwork.NetNS,
- IfName: DefaultInterfaceName,
+ CacheDir: cacheDir,
+ IfName: ifName,
Args: [][2]string{
{"IgnoreUnknown", "1"},
{"K8S_POD_NAMESPACE", podNetwork.Namespace},
@@ -424,5 +553,8 @@ func buildCNIRuntimeConf(podNetwork PodNetwork) (*libcni.RuntimeConf, error) {
}
func (plugin *cniNetworkPlugin) Status() error {
- return plugin.checkInitialized()
+ if plugin.getDefaultNetwork() == nil {
+ return errMissingDefaultNetwork
+ }
+ return nil
}
diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go
index 39e9b591c..8ca61657a 100644
--- a/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go
+++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go
@@ -36,6 +36,10 @@ type PodNetwork struct {
NetNS string
// PortMappings is the port mapping of the sandbox.
PortMappings []PortMapping
+
+ // Networks is a list of CNI network names to attach to the sandbox
+ // Leave this list empty to attach the default network to the sandbox
+ Networks []string
}
// CNIPlugin is the interface that needs to be implemented by a plugin
@@ -47,14 +51,17 @@ type CNIPlugin interface {
// SetUpPod is the method called after the sandbox container of
// the pod has been created but before the other containers of the
// pod are launched.
- SetUpPod(network PodNetwork) (types.Result, error)
+ SetUpPod(network PodNetwork) ([]types.Result, error)
// TearDownPod is the method called before a pod's sandbox container will be deleted
TearDownPod(network PodNetwork) error
// Status is the method called to obtain the ipv4 or ipv6 addresses of the pod sandbox
- GetPodNetworkStatus(network PodNetwork) (string, error)
+ GetPodNetworkStatus(network PodNetwork) ([]types.Result, error)
// NetworkStatus returns error if the network plugin is in error state
Status() error
+
+ // Shutdown terminates all driver operations
+ Shutdown() error
}
diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/types_unix.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/types_unix.go
index 21e713ffc..88010f737 100644
--- a/vendor/github.com/cri-o/ocicni/pkg/ocicni/types_unix.go
+++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/types_unix.go
@@ -3,10 +3,8 @@
package ocicni
const (
- // DefaultNetDir is the place to look for CNI Network
- DefaultNetDir = "/etc/cni/net.d"
- // DefaultCNIDir is the place to look for cni config files
- DefaultCNIDir = "/opt/cni/bin"
- // VendorCNIDirTemplate is the template for looking up vendor specific cni config/executable files
- VendorCNIDirTemplate = "%s/opt/%s/bin"
+ // DefaultConfDir is the default place to look for CNI Network
+ DefaultConfDir = "/etc/cni/net.d"
+ // DefaultBinDir is the default place to look for CNI config files
+ DefaultBinDir = "/opt/cni/bin"
)
diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/types_windows.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/types_windows.go
index f8b434c12..061ecae5c 100644
--- a/vendor/github.com/cri-o/ocicni/pkg/ocicni/types_windows.go
+++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/types_windows.go
@@ -3,10 +3,8 @@
package ocicni
const (
- // DefaultNetDir is the place to look for CNI Network
- DefaultNetDir = "C:\\cni\\etc\\net.d"
- // DefaultCNIDir is the place to look for cni config files
- DefaultCNIDir = "C:\\cni\\bin"
- // VendorCNIDirTemplate is the template for looking up vendor specific cni config/executable files
- VendorCNIDirTemplate = "C:\\cni\\%s\\opt\\%s\\bin" // XXX(vbatts) Not sure what to do here ...
+ // DefaultConfDir is the default place to look for CNI Network
+ DefaultConfDir = "C:\\cni\\etc\\net.d"
+ // DefaultBinDir is the default place to look for cni config files
+ DefaultBinDir = "C:\\cni\\bin"
)
diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/util.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/util.go
index 547e95972..2af786593 100644
--- a/vendor/github.com/cri-o/ocicni/pkg/ocicni/util.go
+++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/util.go
@@ -1,32 +1,8 @@
package ocicni
-import (
- "fmt"
- "net"
- "os/exec"
- "strings"
-)
-
-func getContainerIP(nsenterPath, netnsPath, interfaceName, addrType string) (net.IP, error) {
- // Try to retrieve ip inside container network namespace
- output, err := exec.Command(nsenterPath, fmt.Sprintf("--net=%s", netnsPath), "-F", "--",
- "ip", "-o", addrType, "addr", "show", "dev", interfaceName, "scope", "global").CombinedOutput()
- if err != nil {
- return nil, fmt.Errorf("Unexpected command output %s with error: %v", output, err)
- }
-
- lines := strings.Split(string(output), "\n")
- if len(lines) < 1 {
- return nil, fmt.Errorf("Unexpected command output %s", output)
- }
- fields := strings.Fields(lines[0])
- if len(fields) < 4 {
- return nil, fmt.Errorf("Unexpected address output %s ", lines[0])
- }
- ip, _, err := net.ParseCIDR(fields[3])
- if err != nil {
- return nil, fmt.Errorf("CNI failed to parse ip from output %s due to %v", output, err)
- }
-
- return ip, nil
+// newNSManager initializes a new namespace manager, which is a platform dependent struct.
+func newNSManager() (*nsManager, error) {
+ nsm := &nsManager{}
+ err := nsm.init()
+ return nsm, err
}
diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/util_linux.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/util_linux.go
new file mode 100644
index 000000000..d263ae4df
--- /dev/null
+++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/util_linux.go
@@ -0,0 +1,71 @@
+// +build linux
+
+package ocicni
+
+import (
+ "fmt"
+ "net"
+ "os/exec"
+ "strings"
+)
+
+var defaultNamespaceEnterCommandName = "nsenter"
+
+type nsManager struct {
+ nsenterPath string
+}
+
+func (nsm *nsManager) init() error {
+ var err error
+ nsm.nsenterPath, err = exec.LookPath(defaultNamespaceEnterCommandName)
+ return err
+}
+
+func getContainerDetails(nsm *nsManager, netnsPath, interfaceName, addrType string) (*net.IPNet, *net.HardwareAddr, error) {
+ // Try to retrieve ip inside container network namespace
+ output, err := exec.Command(nsm.nsenterPath, fmt.Sprintf("--net=%s", netnsPath), "-F", "--",
+ "ip", "-o", addrType, "addr", "show", "dev", interfaceName, "scope", "global").CombinedOutput()
+ if err != nil {
+ return nil, nil, fmt.Errorf("Unexpected command output %s with error: %v", output, err)
+ }
+
+ lines := strings.Split(string(output), "\n")
+ if len(lines) < 1 {
+ return nil, nil, fmt.Errorf("Unexpected command output %s", output)
+ }
+ fields := strings.Fields(lines[0])
+ if len(fields) < 4 {
+ return nil, nil, fmt.Errorf("Unexpected address output %s ", lines[0])
+ }
+ ip, ipNet, err := net.ParseCIDR(fields[3])
+ if err != nil {
+ return nil, nil, fmt.Errorf("CNI failed to parse ip from output %s due to %v", output, err)
+ }
+ if ip.To4() == nil {
+ ipNet.IP = ip
+ } else {
+ ipNet.IP = ip.To4()
+ }
+
+ // Try to retrieve MAC inside container network namespace
+ output, err = exec.Command(nsm.nsenterPath, fmt.Sprintf("--net=%s", netnsPath), "-F", "--",
+ "ip", "link", "show", "dev", interfaceName).CombinedOutput()
+ if err != nil {
+ return nil, nil, fmt.Errorf("unexpected 'ip link' command output %s with error: %v", output, err)
+ }
+
+ lines = strings.Split(string(output), "\n")
+ if len(lines) < 2 {
+ return nil, nil, fmt.Errorf("unexpected 'ip link' command output %s", output)
+ }
+ fields = strings.Fields(lines[1])
+ if len(fields) < 4 {
+ return nil, nil, fmt.Errorf("unexpected link output %s ", lines[0])
+ }
+ mac, err := net.ParseMAC(fields[1])
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed to parse MAC from output %s due to %v", output, err)
+ }
+
+ return ipNet, &mac, nil
+}
diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/util_unsupported.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/util_unsupported.go
new file mode 100644
index 000000000..0b1c72208
--- /dev/null
+++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/util_unsupported.go
@@ -0,0 +1,19 @@
+// +build !linux
+
+package ocicni
+
+import (
+ "fmt"
+ "net"
+)
+
+type nsManager struct {
+}
+
+func (nsm *nsManager) init() error {
+ return nil
+}
+
+func getContainerDetails(nsm *nsManager, netnsPath, interfaceName, addrType string) (*net.IPNet, *net.HardwareAddr, error) {
+ return nil, nil, fmt.Errorf("not supported yet")
+}
diff --git a/vendor/github.com/sirupsen/logrus/hooks/syslog/README.md b/vendor/github.com/sirupsen/logrus/hooks/syslog/README.md
new file mode 100644
index 000000000..92b391c17
--- /dev/null
+++ b/vendor/github.com/sirupsen/logrus/hooks/syslog/README.md
@@ -0,0 +1,39 @@
+# Syslog Hooks for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>
+
+## Usage
+
+```go
+import (
+ "log/syslog"
+ "github.com/sirupsen/logrus"
+ logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
+)
+
+func main() {
+ log := logrus.New()
+ hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
+
+ if err == nil {
+ log.Hooks.Add(hook)
+ }
+}
+```
+
+If you want to connect to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). Just assign empty string to the first two parameters of `NewSyslogHook`. It should look like the following.
+
+```go
+import (
+ "log/syslog"
+ "github.com/sirupsen/logrus"
+ logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
+)
+
+func main() {
+ log := logrus.New()
+ hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
+
+ if err == nil {
+ log.Hooks.Add(hook)
+ }
+}
+```
diff --git a/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go b/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go
new file mode 100644
index 000000000..204f0016d
--- /dev/null
+++ b/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go
@@ -0,0 +1,54 @@
+// +build !windows,!nacl,!plan9
+
+package logrus_syslog
+
+import (
+ "fmt"
+ "github.com/sirupsen/logrus"
+ "log/syslog"
+ "os"
+)
+
+// SyslogHook to send logs via syslog.
+type SyslogHook struct {
+ Writer *syslog.Writer
+ SyslogNetwork string
+ SyslogRaddr string
+}
+
+// Creates a hook to be added to an instance of logger. This is called with
+// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")`
+// `if err == nil { log.Hooks.Add(hook) }`
+func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) {
+ w, err := syslog.Dial(network, raddr, priority, tag)
+ return &SyslogHook{w, network, raddr}, err
+}
+
+func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
+ line, err := entry.String()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err)
+ return err
+ }
+
+ switch entry.Level {
+ case logrus.PanicLevel:
+ return hook.Writer.Crit(line)
+ case logrus.FatalLevel:
+ return hook.Writer.Crit(line)
+ case logrus.ErrorLevel:
+ return hook.Writer.Err(line)
+ case logrus.WarnLevel:
+ return hook.Writer.Warning(line)
+ case logrus.InfoLevel:
+ return hook.Writer.Info(line)
+ case logrus.DebugLevel:
+ return hook.Writer.Debug(line)
+ default:
+ return nil
+ }
+}
+
+func (hook *SyslogHook) Levels() []logrus.Level {
+ return logrus.AllLevels
+}