aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md8
-rw-r--r--cmd/podman/networks/inspect.go28
-rw-r--r--cmd/podman/networks/list.go13
-rw-r--r--completions/bash/podman5
-rw-r--r--docs/source/markdown/podman-network-inspect.1.md14
-rw-r--r--docs/source/markdown/podman-network-ls.1.md18
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rwxr-xr-xhack/podman-registry231
-rw-r--r--hack/podman-registry-go/registry.go98
-rw-r--r--hack/podman-registry-go/registry_test.go40
-rw-r--r--libpod/oci_conmon_linux.go20
-rw-r--r--pkg/api/handlers/compat/containers_attach.go2
-rw-r--r--pkg/api/handlers/compat/containers_start.go3
-rw-r--r--pkg/api/handlers/compat/containers_stop.go (renamed from pkg/api/handlers/compat/container_start.go)0
-rw-r--r--pkg/api/handlers/compat/ping.go8
-rw-r--r--pkg/api/handlers/compat/version.go42
-rw-r--r--pkg/api/handlers/handler.go6
-rw-r--r--pkg/api/handlers/utils/handler.go86
-rw-r--r--pkg/api/handlers/utils/handler_test.go139
-rw-r--r--pkg/api/server/register_images.go2
-rw-r--r--pkg/bindings/bindings.go7
-rw-r--r--pkg/bindings/connection.go17
-rw-r--r--pkg/bindings/containers/containers.go8
-rw-r--r--pkg/bindings/test/attach_test.go4
-rw-r--r--pkg/bindings/version.go3
-rw-r--r--pkg/domain/entities/network.go2
-rw-r--r--pkg/domain/infra/abi/network.go37
-rw-r--r--pkg/domain/infra/tunnel/containers.go9
-rw-r--r--test/apiv2/01-basic.at14
-rw-r--r--test/e2e/commit_test.go1
-rw-r--r--test/e2e/create_test.go20
-rw-r--r--test/e2e/logs_test.go20
-rw-r--r--test/e2e/network_test.go39
-rw-r--r--test/e2e/pod_create_test.go2
-rw-r--r--test/system/200-pod-top.bats40
-rw-r--r--test/system/200-pod.bats174
-rw-r--r--vendor/github.com/blang/semver/.travis.yml21
-rw-r--r--vendor/github.com/blang/semver/README.md5
-rw-r--r--vendor/github.com/blang/semver/package.json17
-rw-r--r--vendor/github.com/blang/semver/range.go200
-rw-r--r--vendor/github.com/blang/semver/semver.go23
-rw-r--r--vendor/modules.txt2
43 files changed, 1299 insertions, 132 deletions
diff --git a/README.md b/README.md
index a8e0e65ef..4a5fb2333 100644
--- a/README.md
+++ b/README.md
@@ -6,12 +6,12 @@ Libpod provides a library for applications looking to use the Container Pod conc
popularized by Kubernetes. Libpod also contains the Pod Manager tool `(Podman)`. Podman manages pods, containers, container images, and container volumes.
* [Latest Version: 1.9.2](https://github.com/containers/libpod/releases/latest)
+ * Latest Remote client for Windows
+ * Latest Remote client for MacOs
+ * Latest Static Remote client for Linux
+
* [Continuous Integration:](contrib/cirrus/README.md) [![Build Status](https://api.cirrus-ci.com/github/containers/libpod.svg)](https://cirrus-ci.com/github/containers/libpod/master)
* [GoDoc: ![GoDoc](https://godoc.org/github.com/containers/libpod/libpod?status.svg)](https://godoc.org/github.com/containers/libpod/libpod)
-* Automated continuous release downloads (including remote-client):
- * [Latest remote client for Windows](https://storage.googleapis.com/libpod-master-releases/podman-remote-latest-master-windows-amd64.msi)
- * [Latest remote client for MacOS](https://storage.googleapis.com/libpod-master-releases/podman-remote-latest-master-darwin-amd64.zip)
- * [Latest Snap package](https://snapcraft.io/podman)
## Overview and scope
diff --git a/cmd/podman/networks/inspect.go b/cmd/podman/networks/inspect.go
index 60cede894..1b2e89909 100644
--- a/cmd/podman/networks/inspect.go
+++ b/cmd/podman/networks/inspect.go
@@ -3,6 +3,10 @@ package network
import (
"encoding/json"
"fmt"
+ "html/template"
+ "io"
+ "os"
+ "strings"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
@@ -24,12 +28,18 @@ var (
}
)
+var (
+ networkInspectOptions entities.NetworkInspectOptions
+)
+
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: networkinspectCommand,
Parent: networkCmd,
})
+ flags := networkinspectCommand.Flags()
+ flags.StringVarP(&networkInspectOptions.Format, "format", "f", "", "Pretty-print network to JSON or using a Go template")
}
func networkInspect(cmd *cobra.Command, args []string) error {
@@ -41,6 +51,22 @@ func networkInspect(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
- fmt.Println(string(b))
+ if strings.ToLower(networkInspectOptions.Format) == "json" || networkInspectOptions.Format == "" {
+ fmt.Println(string(b))
+ } else {
+ var w io.Writer = os.Stdout
+ //There can be more than 1 in the inspect output.
+ format := "{{range . }}" + networkInspectOptions.Format + "{{end}}"
+ tmpl, err := template.New("inspectNetworks").Parse(format)
+ if err != nil {
+ return err
+ }
+ if err := tmpl.Execute(w, responses); err != nil {
+ return err
+ }
+ if flusher, ok := w.(interface{ Flush() error }); ok {
+ return flusher.Flush()
+ }
+ }
return nil
}
diff --git a/cmd/podman/networks/list.go b/cmd/podman/networks/list.go
index 1c0528e5c..24604c055 100644
--- a/cmd/podman/networks/list.go
+++ b/cmd/podman/networks/list.go
@@ -40,8 +40,9 @@ var (
func networkListFlags(flags *pflag.FlagSet) {
// TODO enable filters based on something
//flags.StringSliceVarP(&networklistCommand.Filter, "filter", "f", []string{}, "Pause all running containers")
- flags.StringVarP(&networkListOptions.Format, "format", "f", "", "Pretty-print containers to JSON or using a Go template")
+ flags.StringVarP(&networkListOptions.Format, "format", "f", "", "Pretty-print networks to JSON or using a Go template")
flags.BoolVarP(&networkListOptions.Quiet, "quiet", "q", false, "display only names")
+ flags.StringVarP(&networkListOptions.Filter, "filter", "", "", "Provide filter values (e.g. 'name=podman')")
}
func init() {
@@ -59,6 +60,14 @@ func networkList(cmd *cobra.Command, args []string) error {
nlprs []NetworkListPrintReports
)
+ // validate the filter pattern.
+ if len(networkListOptions.Filter) > 0 {
+ tokens := strings.Split(networkListOptions.Filter, "=")
+ if len(tokens) != 2 {
+ return fmt.Errorf("invalid filter syntax : %s", networkListOptions.Filter)
+ }
+ }
+
responses, err := registry.ContainerEngine().NetworkList(registry.Context(), networkListOptions)
if err != nil {
return err
@@ -69,7 +78,7 @@ func networkList(cmd *cobra.Command, args []string) error {
return quietOut(responses)
}
- if networkListOptions.Format == "json" {
+ if strings.ToLower(networkListOptions.Format) == "json" {
return jsonOut(responses)
}
diff --git a/completions/bash/podman b/completions/bash/podman
index 5dbd179ce..9baf7901e 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -1024,6 +1024,8 @@ _podman_network_inspect() {
local boolean_options="
--help
-h
+ --format
+ -f
"
_complete_ "$options_with_args" "$boolean_options"
@@ -1042,6 +1044,9 @@ _podman_network_ls() {
-h
--quiet
-q
+ --format
+ -f
+ -- filter
"
_complete_ "$options_with_args" "$boolean_options"
diff --git a/docs/source/markdown/podman-network-inspect.1.md b/docs/source/markdown/podman-network-inspect.1.md
index dfa7e4b0c..ca6875d18 100644
--- a/docs/source/markdown/podman-network-inspect.1.md
+++ b/docs/source/markdown/podman-network-inspect.1.md
@@ -9,6 +9,15 @@ podman\-network\-inspect - Displays the raw CNI network configuration for one or
## DESCRIPTION
Display the raw (JSON format) network configuration. This command is not available for rootless users.
+## OPTIONS
+**--quiet**, **-q**
+
+The `quiet` option will restrict the output to only the network names.
+
+**--format**, **-f**
+
+Pretty-print networks to JSON or using a Go template.
+
## EXAMPLE
Inspect the default podman network
@@ -43,6 +52,11 @@ Inspect the default podman network
]
```
+```
+# podman network inspect podman --format '{{(index .plugins 0).ipam.ranges}}'
+[[map[gateway:10.88.0.1 subnet:10.88.0.0/16]]]
+```
+
## SEE ALSO
podman(1), podman-network(1), podman-network-ls(1)
diff --git a/docs/source/markdown/podman-network-ls.1.md b/docs/source/markdown/podman-network-ls.1.md
index 46e424593..7b20cf5e0 100644
--- a/docs/source/markdown/podman-network-ls.1.md
+++ b/docs/source/markdown/podman-network-ls.1.md
@@ -12,7 +12,15 @@ Displays a list of existing podman networks. This command is not available for r
## OPTIONS
**--quiet**, **-q**
-The `quiet` option will restrict the output to only the network names
+The `quiet` option will restrict the output to only the network names.
+
+**--format**, **-f**
+
+Pretty-print networks to JSON or using a Go template.
+
+**--filter**
+
+Provide filter values (e.g. 'name=podman').
## EXAMPLE
@@ -36,6 +44,14 @@ outside
podman9
```
+Display name of network which support bridge plugin
+```
+# podman network ls --filter plugin=portmap --format {{.Name}}
+podman
+podman2
+podman9
+```
+
## SEE ALSO
podman(1), podman-network(1), podman-network-inspect(1)
diff --git a/go.mod b/go.mod
index aac3f4c6f..3160186aa 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.12
require (
github.com/BurntSushi/toml v0.3.1
+ github.com/blang/semver v3.5.1+incompatible
github.com/buger/goterm v0.0.0-20181115115552-c206103e1f37
github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect
diff --git a/go.sum b/go.sum
index 9a89453e1..e5d967b7d 100644
--- a/go.sum
+++ b/go.sum
@@ -39,6 +39,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver v3.1.0+incompatible h1:7hqmJYuaEK3qwVjWubYiht3j93YI0WQBuysxHIfUriU=
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
+github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
+github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/buger/goterm v0.0.0-20181115115552-c206103e1f37 h1:uxxtrnACqI9zK4ENDMf0WpXfUsHP5V8liuq5QdgDISU=
github.com/buger/goterm v0.0.0-20181115115552-c206103e1f37/go.mod h1:u9UyCz2eTrSGy6fbupqJ54eY5c4IC8gREQ1053dK12U=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
diff --git a/hack/podman-registry b/hack/podman-registry
new file mode 100755
index 000000000..e7708ce6a
--- /dev/null
+++ b/hack/podman-registry
@@ -0,0 +1,231 @@
+#! /bin/bash
+#
+# podman-registry - start/stop/monitor a local instance of registry:2
+#
+ME=$(basename $0)
+
+###############################################################################
+# BEGIN defaults
+
+PODMAN_REGISTRY_IMAGE=docker.io/library/registry:2
+
+PODMAN_REGISTRY_USER=
+PODMAN_REGISTRY_PASS=
+PODMAN_REGISTRY_PORT=
+
+# Podman binary to run
+PODMAN=${PODMAN:-$(type -p podman)}
+
+# END defaults
+###############################################################################
+# BEGIN help messages
+
+missing=" argument is missing; see $ME --help for details"
+usage="Usage: $ME [options] [start|stop|ps|logs]
+
+$ME manages a local instance of a container registry.
+
+When called to start a registry, $ME will pull an image
+into a local temporary directory, create an htpasswd, start the
+registry, and dump a series of environment variables to stdout:
+
+ \$ $ME start
+ PODMAN_REGISTRY_IMAGE=\"docker.io/library/registry:2\"
+ PODMAN_REGISTRY_PORT=\"5050\"
+ PODMAN_REGISTRY_USER=\"userZ3RZ\"
+ PODMAN_REGISTRY_PASS=\"T8JVJzKrcl4p6uT\"
+
+Expected usage, therefore, is something like this in a script
+
+ eval \$($ME start)
+
+To stop the registry, you will need to know the port number:
+
+ $ME -P \$PODMAN_REGISTRY_PORT stop
+
+Override the default image, port, user, password with:
+
+ -i IMAGE registry image to pull (default: $PODMAN_REGISTRY_IMAGE)
+ -u USER registry user (default: random)
+ -p PASS password for registry user (default: random)
+ -P PORT port to bind to (on 127.0.0.1) (default: random, 5000-5999)
+
+Other options:
+
+ -h display usage message
+"
+
+die () {
+ echo "$ME: $*" >&2
+ exit 1
+}
+
+# END help messages
+###############################################################################
+# BEGIN option processing
+
+while getopts "i:u:p:P:hv" opt; do
+ case "$opt" in
+ i) PODMAN_REGISTRY_IMAGE=$OPTARG ;;
+ u) PODMAN_REGISTRY_USER=$OPTARG ;;
+ p) PODMAN_REGISTRY_PASS=$OPTARG ;;
+ P) PODMAN_REGISTRY_PORT=$OPTARG ;;
+ h) echo "$usage"; exit 0;;
+ v) verbose=1 ;;
+ \?) echo "Run '$ME -h' for help" >&2; exit 1;;
+ esac
+done
+shift $((OPTIND-1))
+
+# END option processing
+###############################################################################
+# BEGIN helper functions
+
+function random_string() {
+ local length=${1:-10}
+
+ head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length
+}
+
+function podman() {
+ if [ -z "${PODMAN_REGISTRY_PORT}" ]; then
+ die "podman port undefined; please invoke me with -P PORT"
+ fi
+
+ if [ -z "${PODMAN_REGISTRY_WORKDIR}" ]; then
+ PODMAN_REGISTRY_WORKDIR=${TMPDIR:-/tmp}/podman-registry-${PODMAN_REGISTRY_PORT}
+ if [ ! -d ${PODMAN_REGISTRY_WORKDIR} ]; then
+ die "$ME: directory does not exist: ${PODMAN_REGISTRY_WORKDIR}"
+ fi
+ fi
+
+ ${PODMAN} --root ${PODMAN_REGISTRY_WORKDIR}/root \
+ --runroot ${PODMAN_REGISTRY_WORKDIR}/runroot \
+ "$@"
+}
+
+# END helper functions
+###############################################################################
+# BEGIN action processing
+
+function do_start() {
+ # If called without a port, assign a random one in the 5xxx range
+ if [ -z "${PODMAN_REGISTRY_PORT}" ]; then
+ for port in $(shuf -i 5000-5999);do
+ if ! { exec 3<> /dev/tcp/127.0.0.1/$port; } &>/dev/null; then
+ PODMAN_REGISTRY_PORT=$port
+ break
+ fi
+ done
+ fi
+
+ PODMAN_REGISTRY_WORKDIR=${TMPDIR:-/tmp}/podman-registry-${PODMAN_REGISTRY_PORT}
+ if [ -d ${PODMAN_REGISTRY_WORKDIR} ]; then
+ die "$ME: directory exists: ${PODMAN_REGISTRY_WORKDIR} (another registry might already be running on this port)"
+ fi
+
+ # Randomly-generated username and password, if none given on command line
+ if [ -z "${PODMAN_REGISTRY_USER}" ]; then
+ PODMAN_REGISTRY_USER="user$(random_string 4)"
+ fi
+ if [ -z "${PODMAN_REGISTRY_PASS}" ]; then
+ PODMAN_REGISTRY_PASS=$(random_string 15)
+ fi
+
+ # Die on any error
+ set -e
+
+ mkdir -p ${PODMAN_REGISTRY_WORKDIR}
+
+ local AUTHDIR=${PODMAN_REGISTRY_WORKDIR}/auth
+ mkdir -p $AUTHDIR
+
+ # We have to be silent; our only output must be env. vars. Log output here.
+ local log=${PODMAN_REGISTRY_WORKDIR}/log
+ touch $log
+
+ # Pull registry image, but into a separate container storage
+ mkdir -p ${PODMAN_REGISTRY_WORKDIR}/root
+ mkdir -p ${PODMAN_REGISTRY_WORKDIR}/runroot
+
+ # Give it three tries, to compensate for flakes
+ podman pull ${PODMAN_REGISTRY_IMAGE} &>> $log ||
+ podman pull ${PODMAN_REGISTRY_IMAGE} &>> $log ||
+ podman pull ${PODMAN_REGISTRY_IMAGE} &>> $log
+
+ # Registry image needs a cert. Self-signed is good enough.
+ local CERT=$AUTHDIR/domain.crt
+ # FIXME: if this fails, we fail silently! It'd be more helpful
+ # to say 'openssl failed' and cat the logfile
+ openssl req -newkey rsa:4096 -nodes -sha256 \
+ -keyout ${AUTHDIR}/domain.key -x509 -days 2 \
+ -out ${AUTHDIR}/domain.crt \
+ -subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=localhost" \
+ &>> $log
+
+ # Store credentials where container will see them
+ podman run --rm \
+ --entrypoint htpasswd ${PODMAN_REGISTRY_IMAGE} \
+ -Bbn ${PODMAN_REGISTRY_USER} ${PODMAN_REGISTRY_PASS} \
+ > $AUTHDIR/htpasswd
+
+ # In case someone needs to debug
+ echo "${PODMAN_REGISTRY_USER}:${PODMAN_REGISTRY_PASS}" \
+ > $AUTHDIR/htpasswd-plaintext
+
+ # Run the registry container.
+ podman run --quiet -d \
+ -p ${PODMAN_REGISTRY_PORT}:5000 \
+ --name registry \
+ -v $AUTHDIR:/auth:Z \
+ -e "REGISTRY_AUTH=htpasswd" \
+ -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
+ -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
+ -e "REGISTRY_HTTP_TLS_CERTIFICATE=/auth/domain.crt" \
+ -e "REGISTRY_HTTP_TLS_KEY=/auth/domain.key" \
+ registry:2 &>> $log
+
+ # Dump settings. Our caller will use these to access the registry.
+ for v in IMAGE PORT USER PASS; do
+ echo "PODMAN_REGISTRY_${v}=\"$(eval echo \$PODMAN_REGISTRY_${v})\""
+ done
+}
+
+
+function do_stop() {
+ podman stop registry
+ podman rm -f registry
+
+ rm -rf ${PODMAN_REGISTRY_WORKDIR}
+}
+
+
+function do_ps() {
+ podman ps -a
+}
+
+
+function do_logs() {
+ podman logs registry
+}
+
+# END action processing
+###############################################################################
+# BEGIN command-line processing
+
+# First command-line arg must be an action
+action=${1?ACTION$missing}
+shift
+
+case "$action" in
+ start) do_start ;;
+ stop) do_stop ;;
+ ps) do_ps ;;
+ logs) do_logs ;;
+ *) die "Unknown action '$action'; must be start / stop / ps / logs" ;;
+esac
+
+# END command-line processing
+###############################################################################
+
+exit 0
diff --git a/hack/podman-registry-go/registry.go b/hack/podman-registry-go/registry.go
new file mode 100644
index 000000000..a83304914
--- /dev/null
+++ b/hack/podman-registry-go/registry.go
@@ -0,0 +1,98 @@
+package registry
+
+import (
+ "strings"
+
+ "github.com/containers/libpod/utils"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ imageKey = "PODMAN_REGISTRY_IMAGE"
+ userKey = "PODMAN_REGISTRY_USER"
+ passKey = "PODMAN_REGISTRY_PASS"
+ portKey = "PODMAN_REGISTRY_PORT"
+)
+
+var binary = "podman-registry"
+
+// Registry is locally running registry.
+type Registry struct {
+ // Image - container image of the registry.
+ Image string
+ // User - the user to authenticate against the registry.
+ User string
+ // Password - the accompanying password for the user.
+ Password string
+ // Port - the port the registry is listening to on the host.
+ Port string
+ // running indicates if the registry is running.
+ running bool
+}
+
+// Start a new registry and return it along with it's image, user, password, and port.
+func Start() (*Registry, error) {
+ // Start a registry.
+ out, err := utils.ExecCmd(binary, "start")
+ if err != nil {
+ return nil, errors.Wrapf(err, "error running %q: %s", binary, out)
+ }
+
+ // Parse the output.
+ registry := Registry{}
+ for _, s := range strings.Split(out, "\n") {
+ if s == "" {
+ continue
+ }
+ spl := strings.Split(s, "=")
+ if len(spl) != 2 {
+ return nil, errors.Errorf("unexpected output format %q: want 'PODMAN_...=...'", s)
+ }
+ key := spl[0]
+ val := strings.TrimSuffix(strings.TrimPrefix(spl[1], "\""), "\"")
+ switch key {
+ case imageKey:
+ registry.Image = val
+ case userKey:
+ registry.User = val
+ case passKey:
+ registry.Password = val
+ case portKey:
+ registry.Port = val
+ default:
+ logrus.Errorf("unexpected podman-registry output: %q", s)
+ }
+ }
+
+ // Extra sanity check.
+ if registry.Image == "" {
+ return nil, errors.Errorf("unexpected output %q: %q missing", out, imageKey)
+ }
+ if registry.User == "" {
+ return nil, errors.Errorf("unexpected output %q: %q missing", out, userKey)
+ }
+ if registry.Password == "" {
+ return nil, errors.Errorf("unexpected output %q: %q missing", out, passKey)
+ }
+ if registry.Port == "" {
+ return nil, errors.Errorf("unexpected output %q: %q missing", out, portKey)
+ }
+
+ registry.running = true
+
+ return &registry, nil
+}
+
+// Stop the registry.
+func (r *Registry) Stop() error {
+ // Stop a registry.
+ if !r.running {
+ return nil
+ }
+ if _, err := utils.ExecCmd(binary, "-P", r.Port, "stop"); err != nil {
+ return errors.Wrapf(err, "error stopping registry (%v) with %q", *r, binary)
+ }
+ r.running = false
+ return nil
+}
diff --git a/hack/podman-registry-go/registry_test.go b/hack/podman-registry-go/registry_test.go
new file mode 100644
index 000000000..4e4bf5fe2
--- /dev/null
+++ b/hack/podman-registry-go/registry_test.go
@@ -0,0 +1,40 @@
+package registry
+
+import (
+ "testing"
+
+ "github.com/hashicorp/go-multierror"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestStartAndStopMultipleRegistries(t *testing.T) {
+ binary = "../podman-registry"
+
+ registries := []*Registry{}
+
+ // Start registries.
+ var errors *multierror.Error
+ for i := 0; i < 3; i++ {
+ reg, err := Start()
+ if err != nil {
+ errors = multierror.Append(errors, err)
+ continue
+ }
+ assert.True(t, len(reg.Image) > 0)
+ assert.True(t, len(reg.User) > 0)
+ assert.True(t, len(reg.Password) > 0)
+ assert.True(t, len(reg.Port) > 0)
+ registries = append(registries, reg)
+ }
+
+ // Stop registries.
+ for _, reg := range registries {
+ // Make sure we can stop it properly.
+ errors = multierror.Append(errors, reg.Stop())
+ // Stopping an already stopped registry is fine as well.
+ errors = multierror.Append(errors, reg.Stop())
+ }
+
+ require.NoError(t, errors.ErrorOrNil())
+}
diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go
index 895a67747..7ba36fe7c 100644
--- a/libpod/oci_conmon_linux.go
+++ b/libpod/oci_conmon_linux.go
@@ -1347,15 +1347,21 @@ func (r *ConmonOCIRuntime) configureConmonEnv(runtimeDir string) ([]string, []*o
// sharedConmonArgs takes common arguments for exec and create/restore and formats them for the conmon CLI
func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, pidPath, logPath, exitDir, ociLogPath, logTag string) []string {
// set the conmon API version to be able to use the correct sync struct keys
- args := []string{"--api-version", "1"}
+ args := []string{
+ "--api-version", "1",
+ "-c", ctr.ID(),
+ "-u", cuuid,
+ "-r", r.path,
+ "-b", bundlePath,
+ "-p", pidPath,
+ "-n", ctr.Name(),
+ "--exit-dir", exitDir,
+ "--socket-dir-path", r.socketsDir,
+ }
+
if r.cgroupManager == config.SystemdCgroupsManager && !ctr.config.NoCgroups {
args = append(args, "-s")
}
- args = append(args, "-c", ctr.ID())
- args = append(args, "-u", cuuid)
- args = append(args, "-r", r.path)
- args = append(args, "-b", bundlePath)
- args = append(args, "-p", pidPath)
var logDriver string
switch ctr.LogDriver() {
@@ -1376,8 +1382,6 @@ func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, p
}
args = append(args, "-l", logDriver)
- args = append(args, "--exit-dir", exitDir)
- args = append(args, "--socket-dir-path", r.socketsDir)
if r.logSizeMax >= 0 {
args = append(args, "--log-size-max", fmt.Sprintf("%v", r.logSizeMax))
}
diff --git a/pkg/api/handlers/compat/containers_attach.go b/pkg/api/handlers/compat/containers_attach.go
index 5fc3117b9..012e20daf 100644
--- a/pkg/api/handlers/compat/containers_attach.go
+++ b/pkg/api/handlers/compat/containers_attach.go
@@ -94,7 +94,7 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) {
return
}
} else if !(state == define.ContainerStateCreated || state == define.ContainerStateRunning) {
- utils.InternalServerError(w, errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers"))
+ utils.InternalServerError(w, errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers - currently in state %s", state.String()))
return
}
diff --git a/pkg/api/handlers/compat/containers_start.go b/pkg/api/handlers/compat/containers_start.go
index 9cb1492fb..cdbc8ff76 100644
--- a/pkg/api/handlers/compat/containers_start.go
+++ b/pkg/api/handlers/compat/containers_start.go
@@ -33,7 +33,6 @@ func StartContainer(w http.ResponseWriter, r *http.Request) {
utils.ContainerNotFound(w, name, err)
return
}
-
state, err := con.State()
if err != nil {
utils.InternalServerError(w, err)
@@ -43,7 +42,7 @@ func StartContainer(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusNotModified, "")
return
}
- if err := con.Start(r.Context(), false); err != nil {
+ if err := con.Start(r.Context(), len(con.PodID()) > 0); err != nil {
utils.InternalServerError(w, err)
return
}
diff --git a/pkg/api/handlers/compat/container_start.go b/pkg/api/handlers/compat/containers_stop.go
index d26ef2c82..d26ef2c82 100644
--- a/pkg/api/handlers/compat/container_start.go
+++ b/pkg/api/handlers/compat/containers_stop.go
diff --git a/pkg/api/handlers/compat/ping.go b/pkg/api/handlers/compat/ping.go
index 6e77e270f..abee3d8e8 100644
--- a/pkg/api/handlers/compat/ping.go
+++ b/pkg/api/handlers/compat/ping.go
@@ -5,22 +5,22 @@ import (
"net/http"
"github.com/containers/buildah"
- "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
)
// Ping returns headers to client about the service
//
// This handler must always be the same for the compatibility and libpod URL trees!
// Clients will use the Header availability to test which backend engine is in use.
+// Note: Additionally handler supports GET and HEAD methods
func Ping(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("API-Version", handlers.DefaultApiVersion)
+ w.Header().Set("API-Version", utils.ApiVersion[utils.CompatTree][utils.CurrentApiVersion].String())
w.Header().Set("BuildKit-Version", "")
w.Header().Set("Docker-Experimental", "true")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Pragma", "no-cache")
- // API-Version and Libpod-API-Version may not always be equal
- w.Header().Set("Libpod-API-Version", handlers.DefaultApiVersion)
+ w.Header().Set("Libpod-API-Version", utils.ApiVersion[utils.LibpodTree][utils.CurrentApiVersion].String())
w.Header().Set("Libpod-Buildha-Version", buildah.Version)
w.WriteHeader(http.StatusOK)
diff --git a/pkg/api/handlers/compat/version.go b/pkg/api/handlers/compat/version.go
index 8786f1d5b..bfc226bb8 100644
--- a/pkg/api/handlers/compat/version.go
+++ b/pkg/api/handlers/compat/version.go
@@ -8,7 +8,6 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
- "github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/domain/entities"
docker "github.com/docker/docker/api/types"
@@ -35,34 +34,35 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) {
Name: "Podman Engine",
Version: versionInfo.Version,
Details: map[string]string{
- "APIVersion": handlers.DefaultApiVersion,
+ "APIVersion": utils.ApiVersion[utils.LibpodTree][utils.CurrentApiVersion].String(),
"Arch": goRuntime.GOARCH,
"BuildTime": time.Unix(versionInfo.Built, 0).Format(time.RFC3339),
"Experimental": "true",
"GitCommit": versionInfo.GitCommit,
"GoVersion": versionInfo.GoVersion,
"KernelVersion": infoData.Host.Kernel,
- "MinAPIVersion": handlers.MinimalApiVersion,
+ "MinAPIVersion": utils.ApiVersion[utils.LibpodTree][utils.MinimalApiVersion].String(),
"Os": goRuntime.GOOS,
},
}}
- utils.WriteResponse(w, http.StatusOK, entities.ComponentVersion{Version: docker.Version{
- Platform: struct {
- Name string
- }{
- Name: fmt.Sprintf("%s/%s/%s-%s", goRuntime.GOOS, goRuntime.GOARCH, infoData.Host.Distribution.Distribution, infoData.Host.Distribution.Version),
- },
- APIVersion: components[0].Details["APIVersion"],
- Arch: components[0].Details["Arch"],
- BuildTime: components[0].Details["BuildTime"],
- Components: components,
- Experimental: true,
- GitCommit: components[0].Details["GitCommit"],
- GoVersion: components[0].Details["GoVersion"],
- KernelVersion: components[0].Details["KernelVersion"],
- MinAPIVersion: components[0].Details["MinAPIVersion"],
- Os: components[0].Details["Os"],
- Version: components[0].Version,
- }})
+ utils.WriteResponse(w, http.StatusOK, entities.ComponentVersion{
+ Version: docker.Version{
+ Platform: struct {
+ Name string
+ }{
+ Name: fmt.Sprintf("%s/%s/%s-%s", goRuntime.GOOS, goRuntime.GOARCH, infoData.Host.Distribution.Distribution, infoData.Host.Distribution.Version),
+ },
+ APIVersion: components[0].Details["APIVersion"],
+ Arch: components[0].Details["Arch"],
+ BuildTime: components[0].Details["BuildTime"],
+ Components: components,
+ Experimental: true,
+ GitCommit: components[0].Details["GitCommit"],
+ GoVersion: components[0].Details["GoVersion"],
+ KernelVersion: components[0].Details["KernelVersion"],
+ MinAPIVersion: components[0].Details["MinAPIVersion"],
+ Os: components[0].Details["Os"],
+ Version: components[0].Version,
+ }})
}
diff --git a/pkg/api/handlers/handler.go b/pkg/api/handlers/handler.go
deleted file mode 100644
index 2dd2c886b..000000000
--- a/pkg/api/handlers/handler.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package handlers
-
-const (
- DefaultApiVersion = "1.40" // See https://docs.docker.com/engine/api/v1.40/
- MinimalApiVersion = "1.24"
-)
diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go
index b5bd488fb..2f4a54b98 100644
--- a/pkg/api/handlers/utils/handler.go
+++ b/pkg/api/handlers/utils/handler.go
@@ -9,11 +9,55 @@ import (
"os"
"strings"
+ "github.com/blang/semver"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
+type (
+ // VersionTree determines which API endpoint tree for version
+ VersionTree int
+ // VersionLevel determines which API level, current or something from the past
+ VersionLevel int
+)
+
+const (
+ // LibpodTree supports Libpod endpoints
+ LibpodTree = VersionTree(iota)
+ // CompatTree supports Libpod endpoints
+ CompatTree
+
+ // CurrentApiVersion announces what is the current API level
+ CurrentApiVersion = VersionLevel(iota)
+ // MinimalApiVersion announces what is the oldest API level supported
+ MinimalApiVersion
+)
+
+var (
+ // See https://docs.docker.com/engine/api/v1.40/
+ // libpod compat handlers are expected to honor docker API versions
+
+ // ApiVersion provides the current and minimal API versions for compat and libpod endpoint trees
+ // Note: GET|HEAD /_ping is never versioned and provides the API-Version and Libpod-API-Version headers to allow
+ // clients to shop for the Version they wish to support
+ ApiVersion = map[VersionTree]map[VersionLevel]semver.Version{
+ LibpodTree: {
+ CurrentApiVersion: semver.MustParse("1.0.0"),
+ MinimalApiVersion: semver.MustParse("1.0.0"),
+ },
+ CompatTree: {
+ CurrentApiVersion: semver.MustParse("1.40.0"),
+ MinimalApiVersion: semver.MustParse("1.24.0"),
+ },
+ }
+
+ // ErrVersionNotGiven returned when version not given by client
+ ErrVersionNotGiven = errors.New("version not given in URL path")
+ // ErrVersionNotSupported returned when given version is too old
+ ErrVersionNotSupported = errors.New("given version is not supported")
+)
+
// IsLibpodRequest returns true if the request related to a libpod endpoint
// (e.g., /v2/libpod/...).
func IsLibpodRequest(r *http.Request) bool {
@@ -21,6 +65,48 @@ func IsLibpodRequest(r *http.Request) bool {
return len(split) >= 3 && split[2] == "libpod"
}
+// SupportedVersion validates that the version provided by client is included in the given condition
+// https://github.com/blang/semver#ranges provides the details for writing conditions
+// If a version is not given in URL path, ErrVersionNotGiven is returned
+func SupportedVersion(r *http.Request, condition string) (semver.Version, error) {
+ version := semver.Version{}
+ val, ok := mux.Vars(r)["version"]
+ if !ok {
+ return version, ErrVersionNotGiven
+ }
+ safeVal, err := url.PathUnescape(val)
+ if err != nil {
+ return version, errors.Wrapf(err, "unable to unescape given API version: %q", val)
+ }
+ version, err = semver.ParseTolerant(safeVal)
+ if err != nil {
+ return version, errors.Wrapf(err, "unable to parse given API version: %q from %q", safeVal, val)
+ }
+
+ inRange, err := semver.ParseRange(condition)
+ if err != nil {
+ return version, err
+ }
+
+ if inRange(version) {
+ return version, nil
+ }
+ return version, ErrVersionNotSupported
+}
+
+// SupportedVersionWithDefaults validates that the version provided by client valid is supported by server
+// minimal API version <= client path version <= maximum API version focused on the endpoint tree from URL
+func SupportedVersionWithDefaults(r *http.Request) (semver.Version, error) {
+ tree := CompatTree
+ if IsLibpodRequest(r) {
+ tree = LibpodTree
+ }
+
+ return SupportedVersion(r,
+ fmt.Sprintf(">=%s <=%s", ApiVersion[tree][MinimalApiVersion].String(),
+ ApiVersion[tree][CurrentApiVersion].String()))
+}
+
// WriteResponse encodes the given value as JSON or string and renders it for http client
func WriteResponse(w http.ResponseWriter, code int, value interface{}) {
// RFC2616 explicitly states that the following status codes "MUST NOT
diff --git a/pkg/api/handlers/utils/handler_test.go b/pkg/api/handlers/utils/handler_test.go
new file mode 100644
index 000000000..6009432b5
--- /dev/null
+++ b/pkg/api/handlers/utils/handler_test.go
@@ -0,0 +1,139 @@
+package utils
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/gorilla/mux"
+)
+
+func TestSupportedVersion(t *testing.T) {
+ req, err := http.NewRequest("GET",
+ fmt.Sprintf("/v%s/libpod/testing/versions", ApiVersion[LibpodTree][CurrentApiVersion]),
+ nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ req = mux.SetURLVars(req, map[string]string{"version": ApiVersion[LibpodTree][CurrentApiVersion].String()})
+
+ rr := httptest.NewRecorder()
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ _, err := SupportedVersionWithDefaults(r)
+ switch {
+ case errors.Is(err, ErrVersionNotGiven): // for compat endpoints version optional
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ case errors.Is(err, ErrVersionNotSupported): // version given but not supported
+ w.WriteHeader(http.StatusBadRequest)
+ fmt.Fprint(w, err.Error())
+ case err != nil:
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ default: // all good
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprint(w, "OK")
+ }
+ })
+ handler.ServeHTTP(rr, req)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Errorf("handler returned wrong status code: got %v want %v",
+ status, http.StatusOK)
+ }
+
+ // Check the response body is what we expect.
+ expected := `OK`
+ if rr.Body.String() != expected {
+ t.Errorf("handler returned unexpected body: got %q want %q",
+ rr.Body.String(), expected)
+ }
+}
+
+func TestUnsupportedVersion(t *testing.T) {
+ version := "999.999.999"
+ req, err := http.NewRequest("GET",
+ fmt.Sprintf("/v%s/libpod/testing/versions", version),
+ nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ req = mux.SetURLVars(req, map[string]string{"version": version})
+
+ rr := httptest.NewRecorder()
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ _, err := SupportedVersionWithDefaults(r)
+ switch {
+ case errors.Is(err, ErrVersionNotGiven): // for compat endpoints version optional
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ case errors.Is(err, ErrVersionNotSupported): // version given but not supported
+ w.WriteHeader(http.StatusBadRequest)
+ fmt.Fprint(w, err.Error())
+ case err != nil:
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ default: // all good
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprint(w, "OK")
+ }
+ })
+ handler.ServeHTTP(rr, req)
+
+ if status := rr.Code; status != http.StatusBadRequest {
+ t.Errorf("handler returned wrong status code: got %v want %v",
+ status, http.StatusBadRequest)
+ }
+
+ // Check the response body is what we expect.
+ expected := ErrVersionNotSupported.Error()
+ if rr.Body.String() != expected {
+ t.Errorf("handler returned unexpected body: got %q want %q",
+ rr.Body.String(), expected)
+ }
+}
+
+func TestEqualVersion(t *testing.T) {
+ version := "1.30.0"
+ req, err := http.NewRequest("GET",
+ fmt.Sprintf("/v%s/libpod/testing/versions", version),
+ nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ req = mux.SetURLVars(req, map[string]string{"version": version})
+
+ rr := httptest.NewRecorder()
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ _, err := SupportedVersion(r, "=="+version)
+ switch {
+ case errors.Is(err, ErrVersionNotGiven): // for compat endpoints version optional
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ case errors.Is(err, ErrVersionNotSupported): // version given but not supported
+ w.WriteHeader(http.StatusBadRequest)
+ fmt.Fprint(w, err.Error())
+ case err != nil:
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ default: // all good
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprint(w, "OK")
+ }
+ })
+ handler.ServeHTTP(rr, req)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Errorf("handler returned wrong status code: got %v want %v",
+ status, http.StatusOK)
+ }
+
+ // Check the response body is what we expect.
+ expected := http.StatusText(http.StatusOK)
+ if rr.Body.String() != expected {
+ t.Errorf("handler returned unexpected body: got %q want %q",
+ rr.Body.String(), expected)
+ }
+}
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index 36f939779..01854b9c4 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -698,7 +698,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// responses:
// 200:
// $ref: '#/responses/LibpodImageTreeResponse'
- // 401:
+ // 404:
// $ref: '#/responses/NoSuchImage'
// 500:
// $ref: '#/responses/InternalError'
diff --git a/pkg/bindings/bindings.go b/pkg/bindings/bindings.go
index 5e2882aae..7e2a444bd 100644
--- a/pkg/bindings/bindings.go
+++ b/pkg/bindings/bindings.go
@@ -8,6 +8,10 @@
package bindings
+import (
+ "github.com/blang/semver"
+)
+
var (
// PTrue is a convenience variable that can be used in bindings where
// a pointer to a bool (optional parameter) is required.
@@ -17,4 +21,7 @@ var (
// a pointer to a bool (optional parameter) is required.
pFalse = false
PFalse = &pFalse
+
+ // _*YES*- podman will fail to run if this value is wrong
+ APIVersion = semver.MustParse("1.0.0")
)
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index d83c0482c..d21d55beb 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -15,6 +15,7 @@ import (
"strings"
"time"
+ "github.com/blang/semver"
"github.com/containers/libpod/pkg/api/types"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
@@ -143,7 +144,7 @@ func tcpClient(_url *url.URL) (Connection, error) {
}
// pingNewConnection pings to make sure the RESTFUL service is up
-// and running. it should only be used where initializing a connection
+// and running. it should only be used when initializing a connection
func pingNewConnection(ctx context.Context) error {
client, err := GetClient(ctx)
if err != nil {
@@ -154,8 +155,20 @@ func pingNewConnection(ctx context.Context) error {
if err != nil {
return err
}
+
if response.StatusCode == http.StatusOK {
- return nil
+ v, err := semver.ParseTolerant(response.Header.Get("Libpod-API-Version"))
+ if err != nil {
+ return err
+ }
+
+ switch APIVersion.Compare(v) {
+ case 1, 0:
+ // Server's job when client version is equal or older
+ return nil
+ case -1:
+ return errors.Errorf("server API version is too old. client %q server %q", APIVersion.String(), v.String())
+ }
}
return errors.Errorf("ping response was %q", response.StatusCode)
}
diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go
index f0984b8e3..74f6ded45 100644
--- a/pkg/bindings/containers/containers.go
+++ b/pkg/bindings/containers/containers.go
@@ -346,7 +346,7 @@ func ContainerInit(ctx context.Context, nameOrID string) error {
}
// Attach attaches to a running container
-func Attach(ctx context.Context, nameOrId string, detachKeys *string, logs, stream *bool, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
+func Attach(ctx context.Context, nameOrId string, detachKeys *string, logs, stream *bool, stdin io.Reader, stdout io.Writer, stderr io.Writer, attachReady chan bool) error {
conn, err := bindings.GetClient(ctx)
if err != nil {
return err
@@ -427,6 +427,12 @@ func Attach(ctx context.Context, nameOrId string, detachKeys *string, logs, stre
return err
}
defer response.Body.Close()
+ // If we are attaching around a start, we need to "signal"
+ // back that we are in fact attached so that started does
+ // not execute before we can attach.
+ if attachReady != nil {
+ attachReady <- true
+ }
if !(response.IsSuccess() || response.IsInformational()) {
return response.Process(nil)
}
diff --git a/pkg/bindings/test/attach_test.go b/pkg/bindings/test/attach_test.go
index 906bd2950..6fb166828 100644
--- a/pkg/bindings/test/attach_test.go
+++ b/pkg/bindings/test/attach_test.go
@@ -54,7 +54,7 @@ var _ = Describe("Podman containers attach", func() {
go func() {
defer GinkgoRecover()
- err := containers.Attach(bt.conn, id, nil, bindings.PTrue, bindings.PTrue, nil, stdout, stderr)
+ err := containers.Attach(bt.conn, id, nil, bindings.PTrue, bindings.PTrue, nil, stdout, stderr, nil)
Expect(err).ShouldNot(HaveOccurred())
}()
@@ -98,7 +98,7 @@ var _ = Describe("Podman containers attach", func() {
go func() {
defer GinkgoRecover()
- err := containers.Attach(bt.conn, ctnr.ID, nil, bindings.PFalse, bindings.PTrue, stdin, stdout, stderr)
+ err := containers.Attach(bt.conn, ctnr.ID, nil, bindings.PFalse, bindings.PTrue, stdin, stdout, stderr, nil)
Expect(err).ShouldNot(HaveOccurred())
}()
diff --git a/pkg/bindings/version.go b/pkg/bindings/version.go
deleted file mode 100644
index c833a644c..000000000
--- a/pkg/bindings/version.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package bindings
-
-func (c Connection) Version() {}
diff --git a/pkg/domain/entities/network.go b/pkg/domain/entities/network.go
index d001553e0..9beeeb042 100644
--- a/pkg/domain/entities/network.go
+++ b/pkg/domain/entities/network.go
@@ -10,6 +10,7 @@ import (
type NetworkListOptions struct {
Format string
Quiet bool
+ Filter string
}
// NetworkListReport describes the results from listing networks
@@ -19,6 +20,7 @@ type NetworkListReport struct {
// NetworkInspectOptions describes options for inspect networks
type NetworkInspectOptions struct {
+ Format string
}
// NetworkInspectReport describes the results from inspect networks
diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go
index dfde3a939..8e3515824 100644
--- a/pkg/domain/infra/abi/network.go
+++ b/pkg/domain/infra/abi/network.go
@@ -6,7 +6,9 @@ import (
"fmt"
"io/ioutil"
"path/filepath"
+ "strings"
+ "github.com/containernetworking/cni/libcni"
cniversion "github.com/containernetworking/cni/pkg/version"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/domain/entities"
@@ -28,8 +30,19 @@ func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.Net
return nil, err
}
+ var tokens []string
+ // tokenize the networkListOptions.Filter in key=value.
+ if len(options.Filter) > 0 {
+ tokens = strings.Split(options.Filter, "=")
+ if len(tokens) != 2 {
+ return nil, fmt.Errorf("invalid filter syntax : %s", options.Filter)
+ }
+ }
+
for _, n := range networks {
- reports = append(reports, &entities.NetworkListReport{NetworkConfigList: n})
+ if ifPassesFilterTest(n, tokens) {
+ reports = append(reports, &entities.NetworkListReport{NetworkConfigList: n})
+ }
}
return reports, nil
}
@@ -255,3 +268,25 @@ func createMacVLAN(r *libpod.Runtime, name string, options entities.NetworkCreat
err = ioutil.WriteFile(cniPathName, b, 0644)
return cniPathName, err
}
+
+func ifPassesFilterTest(netconf *libcni.NetworkConfigList, filter []string) bool {
+ result := false
+ if len(filter) == 0 {
+ // No filter, so pass
+ return true
+ }
+ switch strings.ToLower(filter[0]) {
+ case "name":
+ if filter[1] == netconf.Name {
+ result = true
+ }
+ case "plugin":
+ plugins := network.GetCNIPlugins(netconf)
+ if strings.Contains(plugins, filter[1]) {
+ result = true
+ }
+ default:
+ result = false
+ }
+ return result
+}
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
index cebd332e3..828bfae5b 100644
--- a/pkg/domain/infra/tunnel/containers.go
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -326,7 +326,7 @@ func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []strin
}
func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string, options entities.AttachOptions) error {
- return containers.Attach(ic.ClientCxt, nameOrId, &options.DetachKeys, nil, bindings.PTrue, options.Stdin, options.Stdout, options.Stderr)
+ return containers.Attach(ic.ClientCxt, nameOrId, &options.DetachKeys, nil, bindings.PTrue, options.Stdin, options.Stdout, options.Stderr, nil)
}
func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions) (int, error) {
@@ -335,11 +335,14 @@ func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, o
func startAndAttach(ic *ContainerEngine, name string, detachKeys *string, input, output, errput *os.File) error { //nolint
attachErr := make(chan error)
+ attachReady := make(chan bool)
go func() {
- err := containers.Attach(ic.ClientCxt, name, detachKeys, bindings.PFalse, bindings.PTrue, input, output, errput)
+ err := containers.Attach(ic.ClientCxt, name, detachKeys, bindings.PFalse, bindings.PTrue, input, output, errput, attachReady)
attachErr <- err
}()
-
+ // Wait for the attach to actually happen before starting
+ // the container.
+ <-attachReady
if err := containers.Start(ic.ClientCxt, name, detachKeys); err != nil {
return err
}
diff --git a/test/apiv2/01-basic.at b/test/apiv2/01-basic.at
index 0e94ddb7a..18ec9bbe8 100644
--- a/test/apiv2/01-basic.at
+++ b/test/apiv2/01-basic.at
@@ -10,13 +10,13 @@ t HEAD /_ping 200
t GET /libpod/_ping 200 OK
for i in /version version; do
- t GET $i 200 \
- .Components[0].Name="Podman Engine" \
- .Components[0].Details.APIVersion=1.40 \
- .Components[0].Details.MinAPIVersion=1.24 \
- .Components[0].Details.Os=linux \
- .ApiVersion=1.40 \
- .MinAPIVersion=1.24 \
+ t GET $i 200 \
+ .Components[0].Name="Podman Engine" \
+ .Components[0].Details.APIVersion=1.0.0 \
+ .Components[0].Details.MinAPIVersion=1.0.0 \
+ .Components[0].Details.Os=linux \
+ .ApiVersion=1.0.0 \
+ .MinAPIVersion=1.0.0 \
.Os=linux
done
diff --git a/test/e2e/commit_test.go b/test/e2e/commit_test.go
index d3b6fba0c..72387ed8c 100644
--- a/test/e2e/commit_test.go
+++ b/test/e2e/commit_test.go
@@ -217,7 +217,6 @@ var _ = Describe("Podman commit", func() {
})
It("podman commit container check env variables", func() {
- Skip(v2remotefail)
s := podmanTest.Podman([]string{"run", "--name", "test1", "-e", "TEST=1=1-01=9.01", "-it", "alpine", "true"})
s.WaitWithDefaultTimeout()
Expect(s.ExitCode()).To(Equal(0))
diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go
index ef1dfa134..7d4858551 100644
--- a/test/e2e/create_test.go
+++ b/test/e2e/create_test.go
@@ -35,14 +35,13 @@ var _ = Describe("Podman create", func() {
})
It("podman create container based on a local image", func() {
- Skip(v2remotefail)
- session := podmanTest.Podman([]string{"create", ALPINE, "ls"})
+ session := podmanTest.Podman([]string{"create", "--name", "local_image_test", ALPINE, "ls"})
session.WaitWithDefaultTimeout()
cid := session.OutputToString()
Expect(session.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(1))
- check := podmanTest.Podman([]string{"inspect", "-l"})
+ check := podmanTest.Podman([]string{"inspect", "local_image_test"})
check.WaitWithDefaultTimeout()
data := check.InspectContainerToJSON()
Expect(data[0].ID).To(ContainSubstring(cid))
@@ -81,13 +80,12 @@ var _ = Describe("Podman create", func() {
})
It("podman create adds annotation", func() {
- Skip(v2remotefail)
- session := podmanTest.Podman([]string{"create", "--annotation", "HELLO=WORLD", ALPINE, "ls"})
+ session := podmanTest.Podman([]string{"create", "--annotation", "HELLO=WORLD", "--name", "annotate_test", ALPINE, "ls"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(1))
- check := podmanTest.Podman([]string{"inspect", "-l"})
+ check := podmanTest.Podman([]string{"inspect", "annotate_test"})
check.WaitWithDefaultTimeout()
data := check.InspectContainerToJSON()
value, ok := data[0].Config.Annotations["HELLO"]
@@ -96,13 +94,12 @@ var _ = Describe("Podman create", func() {
})
It("podman create --entrypoint command", func() {
- Skip(v2remotefail)
- session := podmanTest.Podman([]string{"create", "--entrypoint", "/bin/foobar", ALPINE})
+ session := podmanTest.Podman([]string{"create", "--name", "entrypoint_test", "--entrypoint", "/bin/foobar", ALPINE})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(1))
- result := podmanTest.Podman([]string{"inspect", "-l", "--format", "{{.Config.Entrypoint}}"})
+ result := podmanTest.Podman([]string{"inspect", "entrypoint_test", "--format", "{{.Config.Entrypoint}}"})
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(0))
Expect(result.OutputToString()).To(Equal("/bin/foobar"))
@@ -121,14 +118,13 @@ var _ = Describe("Podman create", func() {
})
It("podman create --entrypoint json", func() {
- Skip(v2remotefail)
jsonString := `[ "/bin/foo", "-c"]`
- session := podmanTest.Podman([]string{"create", "--entrypoint", jsonString, ALPINE})
+ session := podmanTest.Podman([]string{"create", "--name", "entrypoint_json", "--entrypoint", jsonString, ALPINE})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(1))
- result := podmanTest.Podman([]string{"inspect", "-l", "--format", "{{.Config.Entrypoint}}"})
+ result := podmanTest.Podman([]string{"inspect", "entrypoint_json", "--format", "{{.Config.Entrypoint}}"})
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(0))
Expect(result.OutputToString()).To(Equal("/bin/foo -c"))
diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go
index 8417051f0..8924db670 100644
--- a/test/e2e/logs_test.go
+++ b/test/e2e/logs_test.go
@@ -19,7 +19,7 @@ var _ = Describe("Podman logs", func() {
)
BeforeEach(func() {
- Skip(v2remotefail)
+ SkipIfRemote() // v2remotefail
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
@@ -173,6 +173,24 @@ var _ = Describe("Podman logs", func() {
Expect(string(out)).To(ContainSubstring("alpine"))
})
+ It("podman journald logs for container name", func() {
+ Skip("need to verify images have correct packages for journald")
+ containerName := "inside-journal"
+ logc := podmanTest.Podman([]string{"run", "--log-driver", "journald", "-d", "--name", containerName, ALPINE, "sh", "-c", "echo podman; sleep 0.1; echo podman; sleep 0.1; echo podman"})
+ logc.WaitWithDefaultTimeout()
+ Expect(logc.ExitCode()).To(Equal(0))
+ cid := logc.OutputToString()
+
+ wait := podmanTest.Podman([]string{"wait", "-l"})
+ wait.WaitWithDefaultTimeout()
+ Expect(wait.ExitCode()).To(BeZero())
+
+ cmd := exec.Command("journalctl", "--no-pager", "-o", "json", "--output-fields=CONTAINER_NAME", "-u", fmt.Sprintf("libpod-conmon-%s.scope", cid))
+ out, err := cmd.CombinedOutput()
+ Expect(err).To(BeNil())
+ Expect(string(out)).To(ContainSubstring(containerName))
+ })
+
It("podman journald logs for container", func() {
Skip("need to verify images have correct packages for journald")
logc := podmanTest.Podman([]string{"run", "--log-driver", "journald", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"})
diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go
index 8d575d7f9..e293876b9 100644
--- a/test/e2e/network_test.go
+++ b/test/e2e/network_test.go
@@ -105,6 +105,32 @@ var _ = Describe("Podman network", func() {
Expect(session.LineInOutputContains("podman-integrationtest")).To(BeTrue())
})
+ It("podman network list --filter success", func() {
+ // Setup, use uuid to prevent conflict with other tests
+ uuid := stringid.GenerateNonCryptoID()
+ secondPath := filepath.Join(cniPath, fmt.Sprintf("%s.conflist", uuid))
+ writeConf([]byte(secondConf), secondPath)
+ defer removeConf(secondPath)
+
+ session := podmanTest.Podman([]string{"network", "ls", "--filter", "plugin=bridge"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.LineInOutputContains("podman-integrationtest")).To(BeTrue())
+ })
+
+ It("podman network list --filter failure", func() {
+ // Setup, use uuid to prevent conflict with other tests
+ uuid := stringid.GenerateNonCryptoID()
+ secondPath := filepath.Join(cniPath, fmt.Sprintf("%s.conflist", uuid))
+ writeConf([]byte(secondConf), secondPath)
+ defer removeConf(secondPath)
+
+ session := podmanTest.Podman([]string{"network", "ls", "--filter", "plugin=test"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.LineInOutputContains("podman-integrationtest")).To(BeFalse())
+ })
+
It("podman network rm no args", func() {
session := podmanTest.Podman([]string{"network", "rm"})
session.WaitWithDefaultTimeout()
@@ -152,6 +178,19 @@ var _ = Describe("Podman network", func() {
Expect(session.IsJSONOutputValid()).To(BeTrue())
})
+ It("podman network inspect", func() {
+ // Setup, use uuid to prevent conflict with other tests
+ uuid := stringid.GenerateNonCryptoID()
+ secondPath := filepath.Join(cniPath, fmt.Sprintf("%s.conflist", uuid))
+ writeConf([]byte(secondConf), secondPath)
+ defer removeConf(secondPath)
+
+ session := podmanTest.Podman([]string{"network", "inspect", "podman-integrationtest", "--format", "{{.cniVersion}}"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.LineInOutputContains("0.3.0")).To(BeTrue())
+ })
+
It("podman inspect container single CNI network", func() {
netName := "testNetSingleCNI"
network := podmanTest.Podman([]string{"network", "create", "--subnet", "10.50.50.0/24", netName})
diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go
index 2f86776cc..24bfa69ce 100644
--- a/test/e2e/pod_create_test.go
+++ b/test/e2e/pod_create_test.go
@@ -84,7 +84,6 @@ var _ = Describe("Podman pod create", func() {
})
It("podman create pod without network portbindings", func() {
- Skip(v2remotefail)
name := "test"
session := podmanTest.Podman([]string{"pod", "create", "--name", name})
session.WaitWithDefaultTimeout()
@@ -100,7 +99,6 @@ var _ = Describe("Podman pod create", func() {
})
It("podman create pod with network portbindings", func() {
- Skip(v2remotefail)
name := "test"
session := podmanTest.Podman([]string{"pod", "create", "--name", name, "-p", "8080:80"})
session.WaitWithDefaultTimeout()
diff --git a/test/system/200-pod-top.bats b/test/system/200-pod-top.bats
deleted file mode 100644
index bba1e8d14..000000000
--- a/test/system/200-pod-top.bats
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/env bats
-
-load helpers
-
-@test "podman pod top - containers in different PID namespaces" {
- skip_if_remote "podman-pod does not work with podman-remote"
-
- # With infra=false, we don't get a /pause container (we also
- # don't pull k8s.gcr.io/pause )
- no_infra='--infra=false'
- run_podman pod create $no_infra
- podid="$output"
-
- # Start two containers...
- run_podman run -d --pod $podid $IMAGE top -d 2
- cid1="$output"
- run_podman run -d --pod $podid $IMAGE top -d 2
- cid2="$output"
-
- # ...and wait for them to actually start.
- wait_for_output "PID \+PPID \+USER " $cid1
- wait_for_output "PID \+PPID \+USER " $cid2
-
- # Both containers have emitted at least one top-like line.
- # Now run 'pod top', and expect two 'top -d 2' processes running.
- run_podman pod top $podid
- is "$output" ".*root.*top -d 2.*root.*top -d 2" "two 'top' containers"
-
- # By default (podman pod create w/ default --infra) there should be
- # a /pause container.
- if [ -z "$no_infra" ]; then
- is "$output" ".*0 \+1 \+0 \+[0-9. ?s]\+/pause" "there is a /pause container"
- fi
-
- # Clean up
- run_podman pod rm -f $podid
-}
-
-
-# vim: filetype=sh
diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats
new file mode 100644
index 000000000..f3d278826
--- /dev/null
+++ b/test/system/200-pod.bats
@@ -0,0 +1,174 @@
+#!/usr/bin/env bats
+
+load helpers
+
+# This is a long ugly way to clean up pods and remove the pause image
+function teardown() {
+ run_podman pod rm -f -a
+ run_podman rm -f -a
+ run_podman image list --format '{{.ID}} {{.Repository}}'
+ while read id name; do
+ if [[ "$name" =~ /pause ]]; then
+ run_podman rmi $id
+ fi
+ done <<<"$output"
+
+ basic_teardown
+}
+
+
+@test "podman pod top - containers in different PID namespaces" {
+ skip_if_remote "podman-pod does not work with podman-remote"
+
+ # With infra=false, we don't get a /pause container (we also
+ # don't pull k8s.gcr.io/pause )
+ no_infra='--infra=false'
+ run_podman pod create $no_infra
+ podid="$output"
+
+ # Start two containers...
+ run_podman run -d --pod $podid $IMAGE top -d 2
+ cid1="$output"
+ run_podman run -d --pod $podid $IMAGE top -d 2
+ cid2="$output"
+
+ # ...and wait for them to actually start.
+ wait_for_output "PID \+PPID \+USER " $cid1
+ wait_for_output "PID \+PPID \+USER " $cid2
+
+ # Both containers have emitted at least one top-like line.
+ # Now run 'pod top', and expect two 'top -d 2' processes running.
+ run_podman pod top $podid
+ is "$output" ".*root.*top -d 2.*root.*top -d 2" "two 'top' containers"
+
+ # By default (podman pod create w/ default --infra) there should be
+ # a /pause container.
+ if [ -z "$no_infra" ]; then
+ is "$output" ".*0 \+1 \+0 \+[0-9. ?s]\+/pause" "there is a /pause container"
+ fi
+
+ # Clean up
+ run_podman pod rm -f $podid
+}
+
+
+@test "podman pod - communicating between pods" {
+ skip_if_remote "podman-pod does not work with podman-remote"
+
+ podname=pod$(random_string)
+ run_podman pod create --infra=true --name=$podname
+
+ # Randomly-assigned port in the 5xxx range
+ for port in $(shuf -i 5000-5999);do
+ if ! { exec 3<> /dev/tcp/127.0.0.1/$port; } &>/dev/null; then
+ break
+ fi
+ done
+
+ # Listener. This will exit as soon as it receives a message.
+ run_podman run -d --pod $podname $IMAGE nc -l -p $port
+ cid1="$output"
+
+ # Talker: send the message via common port on localhost
+ message=$(random_string 15)
+ run_podman run --rm --pod $podname $IMAGE \
+ sh -c "echo $message | nc 127.0.0.1 $port"
+
+ # Back to the first (listener) container. Make sure message was received.
+ run_podman logs $cid1
+ is "$output" "$message" "message sent from one container to another"
+
+ # Clean up. First the nc -l container...
+ run_podman rm $cid1
+
+ # ...then, from pause container, find the image ID of the pause image...
+ # FIXME: if #6283 gets implemented, use 'inspect --format ...'
+ run_podman pod inspect $podname
+ pause_cid=$(jq -r '.Containers[0].Id' <<<"$output")
+ run_podman container inspect --format '{{.Image}}' $pause_cid
+ pause_iid="$output"
+
+ # ...then rm the pod, then rmi the pause image so we don't leave strays.
+ run_podman pod rm $podname
+ run_podman rmi $pause_iid
+}
+
+# Random byte
+function octet() {
+ echo $(( $RANDOM & 255 ))
+}
+
+# random MAC address: convention seems to be that 2nd lsb=1, lsb=0
+# (i.e. 0bxxxxxx10) in the first octet guarantees a private space.
+# FIXME: I can't find a definitive reference for this though
+# Generate the address IN CAPS (A-F), but we will test it in lowercase.
+function random_mac() {
+ local mac=$(printf "%02X" $(( $(octet) & 242 | 2 )) )
+ for i in $(seq 2 6); do
+ mac+=$(printf ":%02X" $(octet))
+ done
+
+ echo $mac
+}
+
+# Random RFC1918 IP address
+function random_ip() {
+ local ip="172.20"
+ for i in 1 2;do
+ ip+=$(printf ".%d" $(octet))
+ done
+ echo $ip
+}
+
+@test "podman pod create - hashtag AllTheOptions" {
+ mac=$(random_mac)
+ add_host_ip=$(random_ip)
+ add_host_n=$(random_string | tr A-Z a-z).$(random_string | tr A-Z a-z).xyz
+
+ dns_server=$(random_ip)
+ dns_opt="ndots:$(octet)"
+ dns_search=$(random_string 15 | tr A-Z a-z).abc
+
+ hostname=$(random_string | tr A-Z a-z).$(random_string | tr A-Z a-z).net
+
+ pod_id_file=${PODMAN_TMPDIR}/pod-id-file
+
+ # Create a pod with all the desired options
+ # FIXME: --ip=$ip fails:
+ # Error adding network: failed to allocate all requested IPs
+ run_podman pod create --name=mypod \
+ --pod-id-file=$pod_id_file \
+ --mac-address=$mac \
+ --hostname=$hostname \
+ --add-host "$add_host_n:$add_host_ip" \
+ --dns "$dns_server" \
+ --dns-search "$dns_search" \
+ --dns-opt "$dns_opt"
+ pod_id="$output"
+
+ # Check --pod-id-file
+ # FIXME: broken in master; reenable once #6292 is fixed
+ #is "$(<$pod_id_file)" "$pod_id" "contents of pod-id-file"
+
+ # Check each of the options
+ if ! is_rootless; then
+ run_podman run --rm --pod mypod $IMAGE ip link show
+ # 'ip' outputs hex in lower-case, ${expr,,} converts UC to lc
+ is "$output" ".* link/ether ${mac,,} " "requested MAC address was set"
+ fi
+
+ run_podman run --rm --pod mypod $IMAGE hostname
+ is "$output" "$hostname" "--hostname set the hostname"
+
+ run_podman run --rm --pod $pod_id $IMAGE cat /etc/hosts
+ is "$output" ".*$add_host_ip $add_host_n" "--add-host was added"
+ is "$output" ".* $hostname" "--hostname is in /etc/hosts"
+ # ^^^^ this must be a tab, not a space
+
+ run_podman run --rm --pod mypod $IMAGE cat /etc/resolv.conf
+ is "$output" ".*nameserver $dns_server" "--dns [server] was added"
+ is "$output" ".*search $dns_search" "--dns-search was added"
+ is "$output" ".*options $dns_opt" "--dns-opt was added"
+}
+
+# vim: filetype=sh
diff --git a/vendor/github.com/blang/semver/.travis.yml b/vendor/github.com/blang/semver/.travis.yml
new file mode 100644
index 000000000..102fb9a69
--- /dev/null
+++ b/vendor/github.com/blang/semver/.travis.yml
@@ -0,0 +1,21 @@
+language: go
+matrix:
+ include:
+ - go: 1.4.3
+ - go: 1.5.4
+ - go: 1.6.3
+ - go: 1.7
+ - go: tip
+ allow_failures:
+ - go: tip
+install:
+- go get golang.org/x/tools/cmd/cover
+- go get github.com/mattn/goveralls
+script:
+- echo "Test and track coverage" ; $HOME/gopath/bin/goveralls -package "." -service=travis-ci
+ -repotoken $COVERALLS_TOKEN
+- echo "Build examples" ; cd examples && go build
+- echo "Check if gofmt'd" ; diff -u <(echo -n) <(gofmt -d -s .)
+env:
+ global:
+ secure: HroGEAUQpVq9zX1b1VIkraLiywhGbzvNnTZq2TMxgK7JHP8xqNplAeF1izrR2i4QLL9nsY+9WtYss4QuPvEtZcVHUobw6XnL6radF7jS1LgfYZ9Y7oF+zogZ2I5QUMRLGA7rcxQ05s7mKq3XZQfeqaNts4bms/eZRefWuaFZbkw=
diff --git a/vendor/github.com/blang/semver/README.md b/vendor/github.com/blang/semver/README.md
index 4399639e2..08b2e4a3d 100644
--- a/vendor/github.com/blang/semver/README.md
+++ b/vendor/github.com/blang/semver/README.md
@@ -1,4 +1,4 @@
-semver for golang [![Build Status](https://drone.io/github.com/blang/semver/status.png)](https://drone.io/github.com/blang/semver/latest) [![GoDoc](https://godoc.org/github.com/blang/semver?status.png)](https://godoc.org/github.com/blang/semver) [![Coverage Status](https://img.shields.io/coveralls/blang/semver.svg)](https://coveralls.io/r/blang/semver?branch=master)
+semver for golang [![Build Status](https://travis-ci.org/blang/semver.svg?branch=master)](https://travis-ci.org/blang/semver) [![GoDoc](https://godoc.org/github.com/blang/semver?status.png)](https://godoc.org/github.com/blang/semver) [![Coverage Status](https://img.shields.io/coveralls/blang/semver.svg)](https://coveralls.io/r/blang/semver?branch=master)
======
semver is a [Semantic Versioning](http://semver.org/) library written in golang. It fully covers spec version `2.0.0`.
@@ -41,6 +41,7 @@ Features
- Compare Helper Methods
- InPlace manipulation
- Ranges `>=1.0.0 <2.0.0 || >=3.0.0 !3.0.1-beta.1`
+- Wildcards `>=1.x`, `<=2.5.x`
- Sortable (implements sort.Interface)
- database/sql compatible (sql.Scanner/Valuer)
- encoding/json compatible (json.Marshaler/Unmarshaler)
@@ -59,6 +60,8 @@ A condition is composed of an operator and a version. The supported operators ar
- `1.0.0`, `=1.0.0`, `==1.0.0` Equal to `1.0.0`
- `!1.0.0`, `!=1.0.0` Not equal to `1.0.0`. Excludes version `1.0.0`.
+Note that spaces between the operator and the version will be gracefully tolerated.
+
A `Range` can link multiple `Ranges` separated by space:
Ranges can be linked by logical AND:
diff --git a/vendor/github.com/blang/semver/package.json b/vendor/github.com/blang/semver/package.json
new file mode 100644
index 000000000..1cf8ebdd9
--- /dev/null
+++ b/vendor/github.com/blang/semver/package.json
@@ -0,0 +1,17 @@
+{
+ "author": "blang",
+ "bugs": {
+ "URL": "https://github.com/blang/semver/issues",
+ "url": "https://github.com/blang/semver/issues"
+ },
+ "gx": {
+ "dvcsimport": "github.com/blang/semver"
+ },
+ "gxVersion": "0.10.0",
+ "language": "go",
+ "license": "MIT",
+ "name": "semver",
+ "releaseCmd": "git commit -a -m \"gx publish $VERSION\"",
+ "version": "3.5.1"
+}
+
diff --git a/vendor/github.com/blang/semver/range.go b/vendor/github.com/blang/semver/range.go
index 0a8eaa1c9..fca406d47 100644
--- a/vendor/github.com/blang/semver/range.go
+++ b/vendor/github.com/blang/semver/range.go
@@ -2,10 +2,33 @@ package semver
import (
"fmt"
+ "strconv"
"strings"
"unicode"
)
+type wildcardType int
+
+const (
+ noneWildcard wildcardType = iota
+ majorWildcard wildcardType = 1
+ minorWildcard wildcardType = 2
+ patchWildcard wildcardType = 3
+)
+
+func wildcardTypefromInt(i int) wildcardType {
+ switch i {
+ case 1:
+ return majorWildcard
+ case 2:
+ return minorWildcard
+ case 3:
+ return patchWildcard
+ default:
+ return noneWildcard
+ }
+}
+
type comparator func(Version, Version) bool
var (
@@ -92,8 +115,12 @@ func ParseRange(s string) (Range, error) {
if err != nil {
return nil, err
}
+ expandedParts, err := expandWildcardVersion(orParts)
+ if err != nil {
+ return nil, err
+ }
var orFn Range
- for _, p := range orParts {
+ for _, p := range expandedParts {
var andFn Range
for _, ap := range p {
opStr, vStr, err := splitComparatorVersion(ap)
@@ -164,20 +191,39 @@ func buildVersionRange(opStr, vStr string) (*versionRange, error) {
}
-// splitAndTrim splits a range string by spaces and cleans leading and trailing spaces
+// inArray checks if a byte is contained in an array of bytes
+func inArray(s byte, list []byte) bool {
+ for _, el := range list {
+ if el == s {
+ return true
+ }
+ }
+ return false
+}
+
+// splitAndTrim splits a range string by spaces and cleans whitespaces
func splitAndTrim(s string) (result []string) {
last := 0
+ var lastChar byte
+ excludeFromSplit := []byte{'>', '<', '='}
for i := 0; i < len(s); i++ {
- if s[i] == ' ' {
+ if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) {
if last < i-1 {
result = append(result, s[last:i])
}
last = i + 1
+ } else if s[i] != ' ' {
+ lastChar = s[i]
}
}
if last < len(s)-1 {
result = append(result, s[last:])
}
+
+ for i, v := range result {
+ result[i] = strings.Replace(v, " ", "", -1)
+ }
+
// parts := strings.Split(s, " ")
// for _, x := range parts {
// if s := strings.TrimSpace(x); len(s) != 0 {
@@ -188,7 +234,6 @@ func splitAndTrim(s string) (result []string) {
}
// splitComparatorVersion splits the comparator from the version.
-// Spaces between the comparator and the version are not allowed.
// Input must be free of leading or trailing spaces.
func splitComparatorVersion(s string) (string, string, error) {
i := strings.IndexFunc(s, unicode.IsDigit)
@@ -198,6 +243,144 @@ func splitComparatorVersion(s string) (string, string, error) {
return strings.TrimSpace(s[0:i]), s[i:], nil
}
+// getWildcardType will return the type of wildcard that the
+// passed version contains
+func getWildcardType(vStr string) wildcardType {
+ parts := strings.Split(vStr, ".")
+ nparts := len(parts)
+ wildcard := parts[nparts-1]
+
+ possibleWildcardType := wildcardTypefromInt(nparts)
+ if wildcard == "x" {
+ return possibleWildcardType
+ }
+
+ return noneWildcard
+}
+
+// createVersionFromWildcard will convert a wildcard version
+// into a regular version, replacing 'x's with '0's, handling
+// special cases like '1.x.x' and '1.x'
+func createVersionFromWildcard(vStr string) string {
+ // handle 1.x.x
+ vStr2 := strings.Replace(vStr, ".x.x", ".x", 1)
+ vStr2 = strings.Replace(vStr2, ".x", ".0", 1)
+ parts := strings.Split(vStr2, ".")
+
+ // handle 1.x
+ if len(parts) == 2 {
+ return vStr2 + ".0"
+ }
+
+ return vStr2
+}
+
+// incrementMajorVersion will increment the major version
+// of the passed version
+func incrementMajorVersion(vStr string) (string, error) {
+ parts := strings.Split(vStr, ".")
+ i, err := strconv.Atoi(parts[0])
+ if err != nil {
+ return "", err
+ }
+ parts[0] = strconv.Itoa(i + 1)
+
+ return strings.Join(parts, "."), nil
+}
+
+// incrementMajorVersion will increment the minor version
+// of the passed version
+func incrementMinorVersion(vStr string) (string, error) {
+ parts := strings.Split(vStr, ".")
+ i, err := strconv.Atoi(parts[1])
+ if err != nil {
+ return "", err
+ }
+ parts[1] = strconv.Itoa(i + 1)
+
+ return strings.Join(parts, "."), nil
+}
+
+// expandWildcardVersion will expand wildcards inside versions
+// following these rules:
+//
+// * when dealing with patch wildcards:
+// >= 1.2.x will become >= 1.2.0
+// <= 1.2.x will become < 1.3.0
+// > 1.2.x will become >= 1.3.0
+// < 1.2.x will become < 1.2.0
+// != 1.2.x will become < 1.2.0 >= 1.3.0
+//
+// * when dealing with minor wildcards:
+// >= 1.x will become >= 1.0.0
+// <= 1.x will become < 2.0.0
+// > 1.x will become >= 2.0.0
+// < 1.0 will become < 1.0.0
+// != 1.x will become < 1.0.0 >= 2.0.0
+//
+// * when dealing with wildcards without
+// version operator:
+// 1.2.x will become >= 1.2.0 < 1.3.0
+// 1.x will become >= 1.0.0 < 2.0.0
+func expandWildcardVersion(parts [][]string) ([][]string, error) {
+ var expandedParts [][]string
+ for _, p := range parts {
+ var newParts []string
+ for _, ap := range p {
+ if strings.Index(ap, "x") != -1 {
+ opStr, vStr, err := splitComparatorVersion(ap)
+ if err != nil {
+ return nil, err
+ }
+
+ versionWildcardType := getWildcardType(vStr)
+ flatVersion := createVersionFromWildcard(vStr)
+
+ var resultOperator string
+ var shouldIncrementVersion bool
+ switch opStr {
+ case ">":
+ resultOperator = ">="
+ shouldIncrementVersion = true
+ case ">=":
+ resultOperator = ">="
+ case "<":
+ resultOperator = "<"
+ case "<=":
+ resultOperator = "<"
+ shouldIncrementVersion = true
+ case "", "=", "==":
+ newParts = append(newParts, ">="+flatVersion)
+ resultOperator = "<"
+ shouldIncrementVersion = true
+ case "!=", "!":
+ newParts = append(newParts, "<"+flatVersion)
+ resultOperator = ">="
+ shouldIncrementVersion = true
+ }
+
+ var resultVersion string
+ if shouldIncrementVersion {
+ switch versionWildcardType {
+ case patchWildcard:
+ resultVersion, _ = incrementMinorVersion(flatVersion)
+ case minorWildcard:
+ resultVersion, _ = incrementMajorVersion(flatVersion)
+ }
+ } else {
+ resultVersion = flatVersion
+ }
+
+ ap = resultOperator + resultVersion
+ }
+ newParts = append(newParts, ap)
+ }
+ expandedParts = append(expandedParts, newParts)
+ }
+
+ return expandedParts, nil
+}
+
func parseComparator(s string) comparator {
switch s {
case "==":
@@ -222,3 +405,12 @@ func parseComparator(s string) comparator {
return nil
}
+
+// MustParseRange is like ParseRange but panics if the range cannot be parsed.
+func MustParseRange(s string) Range {
+ r, err := ParseRange(s)
+ if err != nil {
+ panic(`semver: ParseRange(` + s + `): ` + err.Error())
+ }
+ return r
+}
diff --git a/vendor/github.com/blang/semver/semver.go b/vendor/github.com/blang/semver/semver.go
index bbf85ce97..8ee0842e6 100644
--- a/vendor/github.com/blang/semver/semver.go
+++ b/vendor/github.com/blang/semver/semver.go
@@ -200,6 +200,29 @@ func Make(s string) (Version, error) {
return Parse(s)
}
+// ParseTolerant allows for certain version specifications that do not strictly adhere to semver
+// specs to be parsed by this library. It does so by normalizing versions before passing them to
+// Parse(). It currently trims spaces, removes a "v" prefix, and adds a 0 patch number to versions
+// with only major and minor components specified
+func ParseTolerant(s string) (Version, error) {
+ s = strings.TrimSpace(s)
+ s = strings.TrimPrefix(s, "v")
+
+ // Split into major.minor.(patch+pr+meta)
+ parts := strings.SplitN(s, ".", 3)
+ if len(parts) < 3 {
+ if strings.ContainsAny(parts[len(parts)-1], "+-") {
+ return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data")
+ }
+ for len(parts) < 3 {
+ parts = append(parts, "0")
+ }
+ s = strings.Join(parts, ".")
+ }
+
+ return Parse(s)
+}
+
// Parse parses version string and returns a validated Version or error
func Parse(s string) (Version, error) {
if len(s) == 0 {
diff --git a/vendor/modules.txt b/vendor/modules.txt
index f3d216559..9159143c4 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -34,7 +34,7 @@ github.com/VividCortex/ewma
github.com/acarl005/stripansi
# github.com/beorn7/perks v1.0.1
github.com/beorn7/perks/quantile
-# github.com/blang/semver v3.1.0+incompatible
+# github.com/blang/semver v3.5.1+incompatible
github.com/blang/semver
# github.com/buger/goterm v0.0.0-20181115115552-c206103e1f37
github.com/buger/goterm