summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile18
-rw-r--r--cmd/podman/build.go3
-rw-r--r--cmd/podman/cliconfig/config.go1
-rw-r--r--cmd/podman/generate_systemd.go1
-rw-r--r--cmd/podman/tree.go2
-rw-r--r--cmd/service/main.go55
-rw-r--r--docs/source/markdown/podman-create.1.md15
-rw-r--r--docs/source/markdown/podman-exec.1.md14
-rw-r--r--docs/source/markdown/podman-generate-systemd.1.md5
-rw-r--r--docs/source/markdown/podman-run.1.md30
-rw-r--r--go.mod4
-rw-r--r--go.sum5
-rw-r--r--libpod/container.go7
-rw-r--r--libpod/container_commit.go17
-rw-r--r--libpod/container_inspect.go30
-rw-r--r--libpod/container_internal.go2
-rw-r--r--libpod/image/image.go10
-rw-r--r--libpod/image/prune.go10
-rw-r--r--libpod/image/pull.go2
-rw-r--r--libpod/oci_conmon_linux.go37
-rw-r--r--libpod/options.go17
-rw-r--r--libpod/runtime_img.go13
-rw-r--r--pkg/adapter/containers.go15
-rw-r--r--pkg/adapter/images.go5
-rw-r--r--pkg/adapter/images_remote.go7
-rw-r--r--pkg/adapter/runtime.go10
-rw-r--r--pkg/adapter/runtime_remote.go14
-rw-r--r--pkg/api/handlers/containers.go194
-rw-r--r--pkg/api/handlers/containers_top.go61
-rw-r--r--pkg/api/handlers/events.go46
-rw-r--r--pkg/api/handlers/generic/containers.go306
-rw-r--r--pkg/api/handlers/generic/containers_create.go243
-rw-r--r--pkg/api/handlers/generic/containers_stats.go198
-rw-r--r--pkg/api/handlers/generic/images.go363
-rw-r--r--pkg/api/handlers/generic/info.go196
-rw-r--r--pkg/api/handlers/generic/ping.go25
-rw-r--r--pkg/api/handlers/generic/system.go18
-rw-r--r--pkg/api/handlers/generic/version.go74
-rw-r--r--pkg/api/handlers/handler.go46
-rw-r--r--pkg/api/handlers/images.go185
-rw-r--r--pkg/api/handlers/images_build.go233
-rw-r--r--pkg/api/handlers/libpod/containers.go186
-rw-r--r--pkg/api/handlers/libpod/healthcheck.go25
-rw-r--r--pkg/api/handlers/libpod/images.go165
-rw-r--r--pkg/api/handlers/libpod/pods.go440
-rw-r--r--pkg/api/handlers/libpod/volumes.go144
-rw-r--r--pkg/api/handlers/swagger.go126
-rw-r--r--pkg/api/handlers/types.go534
-rw-r--r--pkg/api/handlers/unsupported.go17
-rw-r--r--pkg/api/handlers/utils/containers.go103
-rw-r--r--pkg/api/handlers/utils/errors.go88
-rw-r--r--pkg/api/handlers/utils/handler.go44
-rw-r--r--pkg/api/handlers/utils/images.go32
-rw-r--r--pkg/api/server/handler_api.go37
-rw-r--r--pkg/api/server/register_auth.go11
-rw-r--r--pkg/api/server/register_containers.go729
-rw-r--r--pkg/api/server/register_distribution.go11
-rw-r--r--pkg/api/server/register_events.go32
-rw-r--r--pkg/api/server/register_healthcheck.go13
-rw-r--r--pkg/api/server/register_images.go571
-rw-r--r--pkg/api/server/register_info.go28
-rw-r--r--pkg/api/server/register_monitor.go11
-rw-r--r--pkg/api/server/register_ping.go17
-rw-r--r--pkg/api/server/register_plugins.go11
-rw-r--r--pkg/api/server/register_pods.go256
-rw-r--r--pkg/api/server/register_swarm.go27
-rw-r--r--pkg/api/server/register_system.go11
-rw-r--r--pkg/api/server/register_version.go12
-rw-r--r--pkg/api/server/register_volumes.go85
-rw-r--r--pkg/api/server/server.go189
-rw-r--r--pkg/api/server/swagger.go133
-rw-r--r--pkg/bindings/containers.go137
-rw-r--r--pkg/bindings/generate.go4
-rw-r--r--pkg/bindings/healthcheck.go19
-rw-r--r--pkg/bindings/info.go3
-rw-r--r--pkg/bindings/mount.go26
-rw-r--r--pkg/bindings/network.go37
-rw-r--r--pkg/bindings/play.go3
-rw-r--r--pkg/bindings/pods.go128
-rw-r--r--pkg/bindings/search.go39
-rw-r--r--pkg/bindings/version.go3
-rw-r--r--pkg/bindings/volumes.go60
-rw-r--r--pkg/network/config.go5
-rw-r--r--pkg/network/files.go3
-rw-r--r--pkg/spec/createconfig.go5
-rw-r--r--pkg/spec/parse.go13
-rw-r--r--pkg/systemdgen/systemdgen.go63
-rw-r--r--pkg/systemdgen/systemdgen_test.go51
-rw-r--r--pkg/util/utils.go19
-rw-r--r--pkg/varlinkapi/images.go2
-rw-r--r--test/e2e/generate_systemd_test.go28
-rw-r--r--test/e2e/libpod_suite_remoteclient_test.go2
-rw-r--r--test/e2e/logs_test.go19
-rw-r--r--vendor/github.com/google/uuid/.travis.yml9
-rw-r--r--vendor/github.com/google/uuid/CONTRIBUTING.md10
-rw-r--r--vendor/github.com/google/uuid/CONTRIBUTORS9
-rw-r--r--vendor/github.com/google/uuid/LICENSE27
-rw-r--r--vendor/github.com/google/uuid/README.md19
-rw-r--r--vendor/github.com/google/uuid/dce.go80
-rw-r--r--vendor/github.com/google/uuid/doc.go12
-rw-r--r--vendor/github.com/google/uuid/go.mod1
-rw-r--r--vendor/github.com/google/uuid/hash.go53
-rw-r--r--vendor/github.com/google/uuid/marshal.go37
-rw-r--r--vendor/github.com/google/uuid/node.go90
-rw-r--r--vendor/github.com/google/uuid/node_js.go12
-rw-r--r--vendor/github.com/google/uuid/node_net.go33
-rw-r--r--vendor/github.com/google/uuid/sql.go59
-rw-r--r--vendor/github.com/google/uuid/time.go123
-rw-r--r--vendor/github.com/google/uuid/util.go43
-rw-r--r--vendor/github.com/google/uuid/uuid.go245
-rw-r--r--vendor/github.com/google/uuid/version1.go44
-rw-r--r--vendor/github.com/google/uuid/version4.go38
-rw-r--r--vendor/github.com/gorilla/schema/.travis.yml18
-rw-r--r--vendor/github.com/gorilla/schema/LICENSE27
-rw-r--r--vendor/github.com/gorilla/schema/README.md90
-rw-r--r--vendor/github.com/gorilla/schema/cache.go305
-rw-r--r--vendor/github.com/gorilla/schema/converter.go145
-rw-r--r--vendor/github.com/gorilla/schema/decoder.go504
-rw-r--r--vendor/github.com/gorilla/schema/doc.go148
-rw-r--r--vendor/github.com/gorilla/schema/encoder.go195
-rw-r--r--vendor/modules.txt4
121 files changed, 9585 insertions, 89 deletions
diff --git a/Makefile b/Makefile
index 8c04005a8..3530b73c9 100644
--- a/Makefile
+++ b/Makefile
@@ -153,7 +153,7 @@ lint: .gopathok varlink_generate ## Execute the source code linter
@./.tool/lint
golangci-lint: .gopathok varlink_generate .install.golangci-lint
- $(GOBIN)/golangci-lint run --tests=false
+ $(GOBIN)/golangci-lint run --tests=false --skip-files swagger.go
gofmt: ## Verify the source code gofmt
find . -name '*.go' ! -path './vendor/*' -exec gofmt -s -w {} \+
@@ -194,6 +194,19 @@ bin/podman.cross.%: .gopathok
GOARCH="$${TARGET##*.}" \
$(GO_BUILD) -gcflags '$(GCFLAGS)' -asmflags '$(ASMFLAGS)' -ldflags '$(LDFLAGS_PODMAN)' -tags '$(BUILDTAGS_CROSS)' -o "$@" $(PROJECT)/cmd/podman
+.PHONY: service
+service: .gopathok
+ $(GO_BUILD) $(BUILDFLAGS) -gcflags '$(GCFLAGS)' -asmflags '$(ASMFLAGS)' -ldflags '$(LDFLAGS_PODMAN)' -tags "$(BUILDTAGS)" -o bin/$@ $(PROJECT)/cmd/service
+
+.PHONY:
+run-service:
+ systemd-socket-activate -l 8080 ./bin/service
+
+.PHONY: run-docker-py-tests
+run-docker-py-tests:
+ $(eval testLogs=$(shell mktemp))
+ ./bin/podman run --rm --security-opt label=disable --privileged -v $(testLogs):/testLogs --net=host -e DOCKER_HOST=tcp://localhost:8080 $(DOCKERPY_IMAGE) sh -c "pytest $(DOCKERPY_TEST) "
+
clean: ## Clean artifacts
rm -rf \
.gopathok \
@@ -452,7 +465,6 @@ install.systemd:
install ${SELINUXOPT} -m 644 contrib/varlink/podman.conf ${DESTDIR}${TMPFILESDIR}/podman.conf
uninstall:
- # Remove manpages
for i in $(filter %.1,$(MANPAGES_DEST)); do \
rm -f $(DESTDIR)$(MANDIR)/man1/$$(basename $${i}); \
done; \
@@ -520,7 +532,7 @@ install.libseccomp.sudo:
cd ../../seccomp/libseccomp && git checkout --detach $(LIBSECCOMP_COMMIT) && ./autogen.sh && ./configure --prefix=/usr && make all && make install
-cmd/podman/varlink/iopodman.go: cmd/podman/varlink/io.podman.varlink
+cmd/podman/varlink/iopodman.go: .gopathok cmd/podman/varlink/io.podman.varlink
GO111MODULE=off $(GO) generate ./cmd/podman/varlink/...
API.md: cmd/podman/varlink/io.podman.varlink
diff --git a/cmd/podman/build.go b/cmd/podman/build.go
index fbf85fc97..08d3edaa3 100644
--- a/cmd/podman/build.go
+++ b/cmd/podman/build.go
@@ -375,7 +375,8 @@ func buildCmd(c *cliconfig.BuildValues) error {
},
Target: c.Target,
}
- return runtime.Build(getContext(), c, options, containerfiles)
+ _, _, err = runtime.Build(getContext(), c, options, containerfiles)
+ return err
}
// useLayers returns false if BUILDAH_LAYERS is set to "0" or "false"
diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go
index 0e4315411..b261599e6 100644
--- a/cmd/podman/cliconfig/config.go
+++ b/cmd/podman/cliconfig/config.go
@@ -163,6 +163,7 @@ type GenerateKubeValues struct {
type GenerateSystemdValues struct {
PodmanCommand
Name bool
+ New bool
Files bool
RestartPolicy string
StopTimeout int
diff --git a/cmd/podman/generate_systemd.go b/cmd/podman/generate_systemd.go
index aa202a68d..a9775f9cb 100644
--- a/cmd/podman/generate_systemd.go
+++ b/cmd/podman/generate_systemd.go
@@ -45,6 +45,7 @@ func init() {
}
flags.IntVarP(&containerSystemdCommand.StopTimeout, "timeout", "t", -1, "stop timeout override")
flags.StringVar(&containerSystemdCommand.RestartPolicy, "restart-policy", "on-failure", "applicable systemd restart-policy")
+ flags.BoolVarP(&containerSystemdCommand.New, "new", "", false, "create a new container instead of starting an existing one")
}
func generateSystemdCmd(c *cliconfig.GenerateSystemdValues) error {
diff --git a/cmd/podman/tree.go b/cmd/podman/tree.go
index cb1b3fc9c..566f96995 100644
--- a/cmd/podman/tree.go
+++ b/cmd/podman/tree.go
@@ -56,7 +56,7 @@ func treeCmd(c *cliconfig.TreeValues) error {
return errors.Wrapf(err, "error creating libpod runtime")
}
defer runtime.DeferredShutdown(false)
- imageInfo, layerInfoMap, img, err := runtime.Tree(c)
+ imageInfo, layerInfoMap, img, err := runtime.Tree(c.InputArgs[0])
if err != nil {
return err
}
diff --git a/cmd/service/main.go b/cmd/service/main.go
new file mode 100644
index 000000000..0290de892
--- /dev/null
+++ b/cmd/service/main.go
@@ -0,0 +1,55 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ "github.com/containers/libpod/cmd/podman/cliconfig"
+ "github.com/containers/libpod/cmd/podman/libpodruntime"
+ api "github.com/containers/libpod/pkg/api/server"
+ "github.com/containers/storage/pkg/reexec"
+ log "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+)
+
+func initConfig() {
+ // we can do more stuff in here.
+}
+
+func main() {
+ if reexec.Init() {
+ // We were invoked with a different argv[0] indicating that we
+ // had a specific job to do as a subprocess, and it's done.
+ return
+ }
+
+ cobra.OnInitialize(initConfig)
+ log.SetLevel(log.DebugLevel)
+
+ config := cliconfig.PodmanCommand{
+ Command: &cobra.Command{},
+ InputArgs: []string{},
+ GlobalFlags: cliconfig.MainFlags{},
+ Remote: false,
+ }
+ // Create a single runtime for http
+ runtime, err := libpodruntime.GetRuntimeDisableFDs(context.Background(), &config)
+ if err != nil {
+ fmt.Printf("error creating libpod runtime: %s", err.Error())
+ os.Exit(1)
+ }
+ defer runtime.DeferredShutdown(false)
+
+ server, err := api.NewServer(runtime)
+ if err != nil {
+ fmt.Println(err.Error())
+ os.Exit(1)
+ }
+
+ err = server.Serve()
+ if err != nil {
+ fmt.Println(err.Error())
+ os.Exit(1)
+ }
+}
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index 85aa81553..fdc2edf39 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -419,6 +419,17 @@ Logging driver specific options. Used to set the path to the container log file
`--log-opt path=/var/log/container/mycontainer.json`
+**--log-opt**=*tag*
+
+Set custom logging configuration. Presently supports the `tag` option
+which specified a custom log tag for the container. For example:
+
+`--log-opt tag="{{.ImageName}}"`
+
+It supports the same keys as `podman inspect --format`.
+
+It is currently supported only by the journald log driver.
+
**--mac-address**=*address*
Container MAC address (e.g. 92:d0:c6:0a:29:33)
@@ -575,7 +586,7 @@ To make a pod with more granular options, use the `podman pod create` command be
Give extended privileges to this container. The default is *false*.
By default, Podman containers are
-“unprivileged” (=false) and cannot, for example, modify parts of the kernel.
+“unprivileged” (=false) and cannot, for example, modify parts of the operating system.
This is because by default a container is not allowed to access any devices.
A “privileged” container is given access to all devices.
@@ -584,6 +595,8 @@ to all devices on the host, turns off graphdriver mount options, as well as
turning off most of the security measures protecting the host from the
container.
+Rootless containers cannot have more privileges than the account that launched them.
+
**--publish**, **-p**=*port*
Publish a container's port, or range of ports, to the host
diff --git a/docs/source/markdown/podman-exec.1.md b/docs/source/markdown/podman-exec.1.md
index d46427c91..fc67211d1 100644
--- a/docs/source/markdown/podman-exec.1.md
+++ b/docs/source/markdown/podman-exec.1.md
@@ -43,7 +43,19 @@ Pass down to the process N additional file descriptors (in addition to 0, 1, 2).
**--privileged**
-Give the process extended Linux capabilities when running the command in container.
+Give extended privileges to this container. The default is *false*.
+
+By default, Podman containers are
+"unprivileged" and cannot, for example, modify parts of the operating system.
+This is because by default a container is only allowed limited access to devices.
+A "privileged" container is given the same access to devices as the user launching the container.
+
+A privileged container turns off the security features that isolate the
+container from the host. Dropped Capabilities, limited devices, read/only mount
+points, Apparmor/SELinux separation, and Seccomp filters are all disabled.
+
+Rootless containers cannot have more privileges than the account that launched them.
+
**--tty**, **-t**
diff --git a/docs/source/markdown/podman-generate-systemd.1.md b/docs/source/markdown/podman-generate-systemd.1.md
index b81e68a46..4d3f9ba48 100644
--- a/docs/source/markdown/podman-generate-systemd.1.md
+++ b/docs/source/markdown/podman-generate-systemd.1.md
@@ -22,11 +22,16 @@ Generate files instead of printing to stdout. The generated files are named {co
Use the name of the container for the start, stop, and description in the unit file
+**--new**
+
+Create a new container via podman-run instead of starting an existing one. This option relies on container configuration files, which may not map directly to podman CLI flags; please review the generated output carefully before placing in production.
+
**--timeout**, **-t**=*value*
Override the default stop timeout for the container with the given value.
**--restart-policy**=*policy*
+
Set the systemd restart policy. The restart-policy must be one of: "no", "on-success", "on-failure", "on-abnormal",
"on-watchdog", "on-abort", or "always". The default policy is *on-failure*.
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index e8744de35..d9fdd9650 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -426,10 +426,21 @@ Logging driver for the container. Currently available options are *k8s-file* an
**--log-opt**=*path*
-Logging driver specific options. Used to set the path to the container log file. For example:
+Set custom logging configuration. Presently supports the `tag` option
+which specified a custom log tag for the container. For example:
`--log-opt path=/var/log/container/mycontainer.json`
+**--log-opt**=*tag*
+
+Specify a custom log tag for the container. For example:
+
+`--log-opt tag="{{.ImageName}}"`
+
+It supports the same keys as `podman inspect --format`.
+
+It is currently supported only by the journald log driver.
+
**--mac-address**=*address*
Container MAC address (e.g. `92:d0:c6:0a:29:33`)
@@ -588,15 +599,16 @@ If a container is run with a pod, and the pod has an infra-container, the infra-
Give extended privileges to this container. The default is *false*.
-By default, Podman containers are “unprivileged” (=false) and cannot,
-for example, modify parts of the kernel. This is because by default a
-container is not allowed to access any devices. A “privileged” container
-is given access to all devices.
+By default, Podman containers are “unprivileged” (=false) and cannot, for
+example, modify parts of the operating system. This is because by default a
+container is only allowed limited access to devices. A "privileged" container
+is given the same access to devices as the user launching the container.
-When the operator executes **podman run --privileged**, Podman enables access
-to all devices on the host, turns off graphdriver mount options, as well as
-turning off most of the security measures protecting the host from the
-container.
+A privileged container turns off the security features that isolate the
+container from the host. Dropped Capabilities, limited devices, read/only mount
+points, Apparmor/SELinux separation, and Seccomp filters are all disabled.
+
+Rootless containers cannot have more privileges than the account that launched them.
**--publish**, **-p**=*port*
diff --git a/go.mod b/go.mod
index 86c4de615..1b9d40194 100644
--- a/go.mod
+++ b/go.mod
@@ -33,6 +33,10 @@ require (
github.com/ghodss/yaml v1.0.0
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
+ github.com/google/uuid v1.1.1
+ github.com/gorilla/handlers v1.4.2 // indirect
+ github.com/gorilla/mux v1.7.3
+ github.com/gorilla/schema v1.1.0
github.com/hashicorp/go-multierror v1.0.0
github.com/hpcloud/tail v1.0.0
github.com/json-iterator/go v1.1.8
diff --git a/go.sum b/go.sum
index 85ff5f260..947985b58 100644
--- a/go.sum
+++ b/go.sum
@@ -212,15 +212,20 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
+github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v0.0.0-20170217192616-94e7d24fd285/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
+github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
diff --git a/libpod/container.go b/libpod/container.go
index edf72f4ee..a046dcafc 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -379,6 +379,8 @@ type ContainerConfig struct {
CgroupParent string `json:"cgroupParent"`
// LogPath log location
LogPath string `json:"logPath"`
+ // LogTag is the tag used for logging
+ LogTag string `json:"logTag"`
// LogDriver driver for logs
LogDriver string `json:"logDriver"`
// File containing the conmon PID
@@ -726,6 +728,11 @@ func (c *Container) LogPath() string {
return c.config.LogPath
}
+// LogTag returns the tag to the container's log file
+func (c *Container) LogTag() string {
+ return c.config.LogTag
+}
+
// RestartPolicy returns the container's restart policy.
func (c *Container) RestartPolicy() string {
return c.config.RestartPolicy
diff --git a/libpod/container_commit.go b/libpod/container_commit.go
index a0ba57f4f..ccc23621e 100644
--- a/libpod/container_commit.go
+++ b/libpod/container_commit.go
@@ -8,6 +8,7 @@ import (
"github.com/containers/buildah"
"github.com/containers/buildah/util"
is "github.com/containers/image/v5/storage"
+ "github.com/containers/image/v5/types"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/libpod/image"
@@ -32,6 +33,10 @@ type ContainerCommitOptions struct {
// Commit commits the changes between a container and its image, creating a new
// image
func (c *Container) Commit(ctx context.Context, destImage string, options ContainerCommitOptions) (*image.Image, error) {
+ var (
+ imageRef types.ImageReference
+ )
+
if c.config.Rootfs != "" {
return nil, errors.Errorf("cannot commit a container that uses an exploded rootfs")
}
@@ -71,7 +76,6 @@ func (c *Container) Commit(ctx context.Context, destImage string, options Contai
if err != nil {
return nil, err
}
-
if options.Author != "" {
importBuilder.SetMaintainer(options.Author)
}
@@ -191,12 +195,11 @@ func (c *Container) Commit(ctx context.Context, destImage string, options Contai
if err != nil {
return nil, errors.Wrapf(err, "error resolving name %q", destImage)
}
- if len(candidates) == 0 {
- return nil, errors.Errorf("error parsing target image name %q", destImage)
- }
- imageRef, err := is.Transport.ParseStoreReference(c.runtime.store, candidates[0])
- if err != nil {
- return nil, errors.Wrapf(err, "error parsing target image name %q", destImage)
+ if len(candidates) > 0 {
+ imageRef, err = is.Transport.ParseStoreReference(c.runtime.store, candidates[0])
+ if err != nil {
+ return nil, errors.Wrapf(err, "error parsing target image name %q", destImage)
+ }
}
id, _, _, err := importBuilder.Commit(ctx, imageRef, commitOptions)
if err != nil {
diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go
index 639dd6e91..3f4ab394f 100644
--- a/libpod/container_inspect.go
+++ b/libpod/container_inspect.go
@@ -107,6 +107,7 @@ type InspectContainerData struct {
OCIConfigPath string `json:"OCIConfigPath,omitempty"`
OCIRuntime string `json:"OCIRuntime,omitempty"`
LogPath string `json:"LogPath"`
+ LogTag string `json:"LogTag"`
ConmonPidFile string `json:"ConmonPidFile"`
Name string `json:"Name"`
RestartCount int32 `json:"RestartCount"`
@@ -629,17 +630,9 @@ type InspectNetworkSettings struct {
MacAddress string `json:"MacAddress"`
}
-// Inspect a container for low-level information
-func (c *Container) Inspect(size bool) (*InspectContainerData, error) {
- if !c.batched {
- c.lock.Lock()
- defer c.lock.Unlock()
-
- if err := c.syncContainer(); err != nil {
- return nil, err
- }
- }
-
+// inspectLocked inspects a container for low-level information.
+// The caller must held c.lock.
+func (c *Container) inspectLocked(size bool) (*InspectContainerData, error) {
storeCtr, err := c.runtime.store.Container(c.ID())
if err != nil {
return nil, errors.Wrapf(err, "error getting container from store %q", c.ID())
@@ -655,6 +648,20 @@ func (c *Container) Inspect(size bool) (*InspectContainerData, error) {
return c.getContainerInspectData(size, driverData)
}
+// Inspect a container for low-level information
+func (c *Container) Inspect(size bool) (*InspectContainerData, error) {
+ if !c.batched {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ if err := c.syncContainer(); err != nil {
+ return nil, err
+ }
+ }
+
+ return c.inspectLocked(size)
+}
+
func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) (*InspectContainerData, error) {
config := c.config
runtimeInfo := c.state
@@ -732,6 +739,7 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data)
HostsPath: hostsPath,
StaticDir: config.StaticDir,
LogPath: config.LogPath,
+ LogTag: config.LogTag,
OCIRuntime: config.OCIRuntime,
ConmonPidFile: config.ConmonPidFile,
Name: config.Name,
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 562f783a7..46c83149a 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -1195,6 +1195,7 @@ func (c *Container) pause() error {
}
if err := c.ociRuntime.PauseContainer(c); err != nil {
+ // TODO when using docker-py there is some sort of race/incompatibility here
return err
}
@@ -1212,6 +1213,7 @@ func (c *Container) unpause() error {
}
if err := c.ociRuntime.UnpauseContainer(c); err != nil {
+ // TODO when using docker-py there is some sort of race/incompatibility here
return err
}
diff --git a/libpod/image/image.go b/libpod/image/image.go
index c8583a1c5..bce10e24c 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -781,6 +781,7 @@ type History struct {
CreatedBy string `json:"createdBy"`
Size int64 `json:"size"`
Comment string `json:"comment"`
+ Tags []string `json:"tags"`
}
// History gets the history of an image and the IDs of images that are part of
@@ -840,14 +841,17 @@ func (i *Image) History(ctx context.Context) ([]*History, error) {
delete(topLayerMap, layer.ID)
}
}
-
- allHistory = append(allHistory, &History{
+ h := History{
ID: id,
Created: oci.History[x].Created,
CreatedBy: oci.History[x].CreatedBy,
Size: size,
Comment: oci.History[x].Comment,
- })
+ }
+ if layer != nil {
+ h.Tags = layer.Names
+ }
+ allHistory = append(allHistory, &h)
if layer != nil && layer.Parent != "" && !oci.History[x].EmptyLayer {
layer, err = i.imageruntime.store.Layer(layer.Parent)
diff --git a/libpod/image/prune.go b/libpod/image/prune.go
index f5be8ed50..3afff22af 100644
--- a/libpod/image/prune.go
+++ b/libpod/image/prune.go
@@ -116,6 +116,10 @@ func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) (
return nil, errors.Wrap(err, "unable to get images to prune")
}
for _, p := range pruneImages {
+ repotags, err := p.RepoTags()
+ if err != nil {
+ return nil, err
+ }
if err := p.Remove(ctx, true); err != nil {
if errors.Cause(err) == storage.ErrImageUsedByContainer {
logrus.Warnf("Failed to prune image %s as it is in use: %v", p.ID(), err)
@@ -124,7 +128,11 @@ func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) (
return nil, errors.Wrap(err, "failed to prune image")
}
defer p.newImageEvent(events.Prune)
- prunedCids = append(prunedCids, p.ID())
+ nameOrID := p.ID()
+ if len(repotags) > 0 {
+ nameOrID = repotags[0]
+ }
+ prunedCids = append(prunedCids, nameOrID)
}
return prunedCids, nil
}
diff --git a/libpod/image/pull.go b/libpod/image/pull.go
index 326a23f4c..76294ba06 100644
--- a/libpod/image/pull.go
+++ b/libpod/image/pull.go
@@ -407,5 +407,5 @@ func checkRemoteImageForLabel(ctx context.Context, label string, imageInfo pullR
return nil
}
}
- return errors.Errorf("%s has no label %s", imageInfo.image, label)
+ return errors.Errorf("%s has no label %s in %q", imageInfo.image, label, remoteInspect.Labels)
}
diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go
index b09dc3a4f..3b0b7bc7b 100644
--- a/libpod/oci_conmon_linux.go
+++ b/libpod/oci_conmon_linux.go
@@ -14,6 +14,7 @@ import (
"strconv"
"strings"
"syscall"
+ "text/template"
"time"
"github.com/containers/libpod/libpod/config"
@@ -532,7 +533,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options
if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON {
ociLog = c.execOCILog(sessionID)
}
- args := r.sharedConmonArgs(c, sessionID, c.execBundlePath(sessionID), c.execPidPath(sessionID), c.execLogPath(sessionID), c.execExitFileDir(sessionID), ociLog)
+ args := r.sharedConmonArgs(c, sessionID, c.execBundlePath(sessionID), c.execPidPath(sessionID), c.execLogPath(sessionID), c.execExitFileDir(sessionID), ociLog, "")
if options.PreserveFDs > 0 {
args = append(args, formatRuntimeOpts("--preserve-fds", fmt.Sprintf("%d", options.PreserveFDs))...)
@@ -890,6 +891,27 @@ func waitPidStop(pid int, timeout time.Duration) error {
}
}
+func (r *ConmonOCIRuntime) getLogTag(ctr *Container) (string, error) {
+ logTag := ctr.LogTag()
+ if logTag == "" {
+ return "", nil
+ }
+ data, err := ctr.inspectLocked(false)
+ if err != nil {
+ return "", nil
+ }
+ tmpl, err := template.New("container").Parse(logTag)
+ if err != nil {
+ return "", errors.Wrapf(err, "template parsing error %s", logTag)
+ }
+ var b bytes.Buffer
+ err = tmpl.Execute(&b, data)
+ if err != nil {
+ return "", err
+ }
+ return b.String(), nil
+}
+
// createOCIContainer generates this container's main conmon instance and prepares it for starting
func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (err error) {
var stderrBuf bytes.Buffer
@@ -916,7 +938,13 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON {
ociLog = filepath.Join(ctr.state.RunDir, "oci-log")
}
- args := r.sharedConmonArgs(ctr, ctr.ID(), ctr.bundlePath(), filepath.Join(ctr.state.RunDir, "pidfile"), ctr.LogPath(), r.exitsDir, ociLog)
+
+ logTag, err := r.getLogTag(ctr)
+ if err != nil {
+ return err
+ }
+
+ args := r.sharedConmonArgs(ctr, ctr.ID(), ctr.bundlePath(), filepath.Join(ctr.state.RunDir, "pidfile"), ctr.LogPath(), r.exitsDir, ociLog, logTag)
if ctr.config.Spec.Process.Terminal {
args = append(args, "-t")
@@ -1150,7 +1178,7 @@ 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 string) []string {
+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"}
if r.cgroupManager == define.SystemdCgroupsManager && !ctr.config.NoCgroups {
@@ -1197,6 +1225,9 @@ func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, p
if ociLogPath != "" {
args = append(args, "--runtime-arg", "--log-format=json", "--runtime-arg", "--log", fmt.Sprintf("--runtime-arg=%s", ociLogPath))
}
+ if logTag != "" {
+ args = append(args, "--log-tag", logTag)
+ }
if ctr.config.NoCgroups {
logrus.Debugf("Running with no CGroups")
args = append(args, "--runtime-arg", "--cgroup-manager", "--runtime-arg", "disabled")
diff --git a/libpod/options.go b/libpod/options.go
index 031f4f705..1d6863e7b 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -1059,6 +1059,23 @@ func WithLogPath(path string) CtrCreateOption {
}
}
+// WithLogTag sets the tag to the log file.
+func WithLogTag(tag string) CtrCreateOption {
+ return func(ctr *Container) error {
+ if ctr.valid {
+ return define.ErrCtrFinalized
+ }
+ if tag == "" {
+ return errors.Wrapf(define.ErrInvalidArg, "log tag must be set")
+ }
+
+ ctr.config.LogTag = tag
+
+ return nil
+ }
+
+}
+
// WithNoCgroups disables the creation of CGroups for the new container.
func WithNoCgroups() CtrCreateOption {
return func(ctr *Container) error {
diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go
index 9943c4104..bae1c1ed8 100644
--- a/libpod/runtime_img.go
+++ b/libpod/runtime_img.go
@@ -10,6 +10,7 @@ import (
"os"
"github.com/containers/buildah/imagebuildah"
+ "github.com/containers/image/v5/docker/reference"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/util"
@@ -145,9 +146,9 @@ func removeStorageContainers(ctrIDs []string, store storage.Store) error {
}
// Build adds the runtime to the imagebuildah call
-func (r *Runtime) Build(ctx context.Context, options imagebuildah.BuildOptions, dockerfiles ...string) error {
- _, _, err := imagebuildah.BuildDockerfiles(ctx, r.store, options, dockerfiles...)
- return err
+func (r *Runtime) Build(ctx context.Context, options imagebuildah.BuildOptions, dockerfiles ...string) (string, reference.Canonical, error) {
+ id, ref, err := imagebuildah.BuildDockerfiles(ctx, r.store, options, dockerfiles...)
+ return id, ref, err
}
// Import is called as an intermediary to the image library Import
@@ -192,7 +193,7 @@ func (r *Runtime) Import(ctx context.Context, source string, reference string, c
}
// if it's stdin, buffer it, too
if source == "-" {
- file, err := downloadFromFile(os.Stdin)
+ file, err := DownloadFromFile(os.Stdin)
if err != nil {
return "", err
}
@@ -232,9 +233,9 @@ func downloadFromURL(source string) (string, error) {
return outFile.Name(), nil
}
-// donwloadFromFile reads all of the content from the reader and temporarily
+// DownloadFromFile reads all of the content from the reader and temporarily
// saves in it /var/tmp/importxyz, which is deleted after the image is imported
-func downloadFromFile(reader *os.File) (string, error) {
+func DownloadFromFile(reader *os.File) (string, error) {
outFile, err := ioutil.TempFile("/var/tmp", "import")
if err != nil {
return "", errors.Wrap(err, "error creating file")
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go
index 3334e9fa1..fdd9f6ab3 100644
--- a/pkg/adapter/containers.go
+++ b/pkg/adapter/containers.go
@@ -1230,6 +1230,7 @@ func (r *LocalRuntime) generateSystemdgenContainerInfo(c *cliconfig.GenerateSyst
PIDFile: conmonPidFile,
StopTimeout: timeout,
GenerateTimestamp: true,
+ CreateCommand: config.CreateCommand,
}
return info, true, nil
@@ -1237,11 +1238,21 @@ func (r *LocalRuntime) generateSystemdgenContainerInfo(c *cliconfig.GenerateSyst
// GenerateSystemd creates a unit file for a container or pod.
func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (string, error) {
+ opts := systemdgen.Options{
+ Files: c.Files,
+ New: c.New,
+ }
+
// First assume it's a container.
if info, found, err := r.generateSystemdgenContainerInfo(c, c.InputArgs[0], nil); found && err != nil {
return "", err
} else if found && err == nil {
- return systemdgen.CreateContainerSystemdUnit(info, c.Files)
+ return systemdgen.CreateContainerSystemdUnit(info, opts)
+ }
+
+ // --new does not support pods.
+ if c.New {
+ return "", errors.Errorf("error generating systemd unit files: cannot generate generic files for a pod")
}
// We're either having a pod or garbage.
@@ -1312,7 +1323,7 @@ func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (stri
if i > 0 {
builder.WriteByte('\n')
}
- out, err := systemdgen.CreateContainerSystemdUnit(info, c.Files)
+ out, err := systemdgen.CreateContainerSystemdUnit(info, opts)
if err != nil {
return "", err
}
diff --git a/pkg/adapter/images.go b/pkg/adapter/images.go
index c8ea1cdea..762f1a656 100644
--- a/pkg/adapter/images.go
+++ b/pkg/adapter/images.go
@@ -3,14 +3,13 @@
package adapter
import (
- "github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/libpod/image"
"github.com/pkg/errors"
)
// Tree ...
-func (r *LocalRuntime) Tree(c *cliconfig.TreeValues) (*image.InfoImage, map[string]*image.LayerInfo, *ContainerImage, error) {
- img, err := r.NewImageFromLocal(c.InputArgs[0])
+func (r *LocalRuntime) Tree(imageOrID string) (*image.InfoImage, map[string]*image.LayerInfo, *ContainerImage, error) {
+ img, err := r.NewImageFromLocal(imageOrID)
if err != nil {
return nil, nil, nil, err
}
diff --git a/pkg/adapter/images_remote.go b/pkg/adapter/images_remote.go
index 722058d4a..1d4997d9a 100644
--- a/pkg/adapter/images_remote.go
+++ b/pkg/adapter/images_remote.go
@@ -6,7 +6,6 @@ import (
"context"
"encoding/json"
- "github.com/containers/libpod/cmd/podman/cliconfig"
iopodman "github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/inspect"
@@ -27,11 +26,11 @@ func (i *ContainerImage) Inspect(ctx context.Context) (*inspect.ImageData, error
}
// Tree ...
-func (r *LocalRuntime) Tree(c *cliconfig.TreeValues) (*image.InfoImage, map[string]*image.LayerInfo, *ContainerImage, error) {
+func (r *LocalRuntime) Tree(imageOrID string) (*image.InfoImage, map[string]*image.LayerInfo, *ContainerImage, error) {
layerInfoMap := make(map[string]*image.LayerInfo)
imageInfo := &image.InfoImage{}
- img, err := r.NewImageFromLocal(c.InputArgs[0])
+ img, err := r.NewImageFromLocal(imageOrID)
if err != nil {
return nil, nil, nil, err
}
@@ -44,7 +43,7 @@ func (r *LocalRuntime) Tree(c *cliconfig.TreeValues) (*image.InfoImage, map[stri
return nil, nil, nil, errors.Wrap(err, "failed to unmarshal image layers")
}
- reply, err = iopodman.BuildImageHierarchyMap().Call(r.Conn, c.InputArgs[0])
+ reply, err = iopodman.BuildImageHierarchyMap().Call(r.Conn, imageOrID)
if err != nil {
return nil, nil, nil, errors.Wrap(err, "failed to get build image map")
}
diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go
index 8933e826f..5f880e807 100644
--- a/pkg/adapter/runtime.go
+++ b/pkg/adapter/runtime.go
@@ -286,20 +286,20 @@ func libpodVolumeToVolume(volumes []*libpod.Volume) []*Volume {
}
// Build is the wrapper to build images
-func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, options imagebuildah.BuildOptions, dockerfiles []string) error {
+func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, options imagebuildah.BuildOptions, dockerfiles []string) (string, reference.Canonical, error) {
namespaceOptions, networkPolicy, err := parse.NamespaceOptions(c.PodmanCommand.Command)
if err != nil {
- return errors.Wrapf(err, "error parsing namespace-related options")
+ return "", nil, errors.Wrapf(err, "error parsing namespace-related options")
}
usernsOption, idmappingOptions, err := parse.IDMappingOptions(c.PodmanCommand.Command, options.Isolation)
if err != nil {
- return errors.Wrapf(err, "error parsing ID mapping options")
+ return "", nil, errors.Wrapf(err, "error parsing ID mapping options")
}
namespaceOptions.AddOrReplace(usernsOption...)
systemContext, err := parse.SystemContextFromOptions(c.PodmanCommand.Command)
if err != nil {
- return errors.Wrapf(err, "error building system context")
+ return "", nil, errors.Wrapf(err, "error building system context")
}
authfile := c.Authfile
@@ -310,7 +310,7 @@ func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, opti
systemContext.AuthFilePath = authfile
commonOpts, err := parse.CommonBuildOptions(c.PodmanCommand.Command)
if err != nil {
- return err
+ return "", nil, err
}
options.NamespaceOptions = namespaceOptions
diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go
index 9c10b31c0..c908358ff 100644
--- a/pkg/adapter/runtime_remote.go
+++ b/pkg/adapter/runtime_remote.go
@@ -507,7 +507,7 @@ func (r *LocalRuntime) Import(ctx context.Context, source, reference string, cha
return iopodman.ImportImage().Call(r.Conn, strings.TrimRight(tempFile, ":"), reference, history, changes, true)
}
-func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, options imagebuildah.BuildOptions, dockerfiles []string) error {
+func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, options imagebuildah.BuildOptions, dockerfiles []string) (string, reference.Canonical, error) {
buildOptions := iopodman.BuildOptions{
AddHosts: options.CommonBuildOpts.AddHost,
CgroupParent: options.CommonBuildOpts.CgroupParent,
@@ -552,31 +552,31 @@ func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, opti
// tar the file
outputFile, err := ioutil.TempFile("", "varlink_tar_send")
if err != nil {
- return err
+ return "", nil, err
}
defer outputFile.Close()
defer os.Remove(outputFile.Name())
// Create the tarball of the context dir to a tempfile
if err := utils.TarToFilesystem(options.ContextDirectory, outputFile); err != nil {
- return err
+ return "", nil, err
}
// Send the context dir tarball over varlink.
tempFile, err := r.SendFileOverVarlink(outputFile.Name())
if err != nil {
- return err
+ return "", nil, err
}
buildinfo.ContextDir = tempFile
reply, err := iopodman.BuildImage().Send(r.Conn, varlink.More, buildinfo)
if err != nil {
- return err
+ return "", nil, err
}
for {
responses, flags, err := reply()
if err != nil {
- return err
+ return "", nil, err
}
for _, line := range responses.Logs {
fmt.Print(line)
@@ -585,7 +585,7 @@ func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, opti
break
}
}
- return err
+ return "", nil, err
}
// SendFileOverVarlink sends a file over varlink in an upgraded connection
diff --git a/pkg/api/handlers/containers.go b/pkg/api/handlers/containers.go
new file mode 100644
index 000000000..6b09321a0
--- /dev/null
+++ b/pkg/api/handlers/containers.go
@@ -0,0 +1,194 @@
+package handlers
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func StopContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+
+ // /{version}/containers/(name)/stop
+ query := struct {
+ Timeout int `schema:"t"`
+ }{
+ // override any golang type defaults
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ name := getName(r)
+ con, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ state, err := con.State()
+ if err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "unable to get state for Container %s", name))
+ return
+ }
+ // If the Container is stopped already, send a 302
+ if state == define.ContainerStateStopped || state == define.ContainerStateExited {
+ utils.Error(w, http.StatusText(http.StatusNotModified), http.StatusNotModified,
+ errors.Errorf("Container %s is already stopped ", name))
+ return
+ }
+
+ var stopError error
+ if query.Timeout > 0 {
+ stopError = con.StopWithTimeout(uint(query.Timeout))
+ } else {
+ stopError = con.Stop()
+ }
+ if stopError != nil {
+ utils.InternalServerError(w, errors.Wrapf(stopError, "failed to stop %s", name))
+ return
+ }
+
+ // Success
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func UnpauseContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ // /{version}/containers/(name)/unpause
+ name := getName(r)
+ con, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ // the api does not error if the Container is already paused, so just into it
+ if err := con.Unpause(); err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ // Success
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func PauseContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ // /{version}/containers/(name)/pause
+ name := getName(r)
+ con, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ // the api does not error if the Container is already paused, so just into it
+ if err := con.Pause(); err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ // Success
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func StartContainer(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ DetachKeys string `schema:"detachKeys"`
+ }{
+ // Override golang default values for types
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ if len(query.DetachKeys) > 0 {
+ // TODO - start does not support adding detach keys
+ utils.Error(w, "Something went wrong", http.StatusBadRequest, errors.New("the detachKeys parameter is not supported yet"))
+ return
+ }
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := getName(r)
+ con, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ state, err := con.State()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ if state == define.ContainerStateRunning {
+ msg := fmt.Sprintf("Container %s is already running", name)
+ utils.Error(w, msg, http.StatusNotModified, errors.New(msg))
+ return
+ }
+ if err := con.Start(r.Context(), false); err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func RestartContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ // /{version}/containers/(name)/restart
+ query := struct {
+ Timeout int `schema:"t"`
+ }{
+ // Override golang default values for types
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ name := getName(r)
+ con, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ state, err := con.State()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ // FIXME: This is not in the swagger.yml...
+ // If the Container is stopped already, send a 409
+ if state == define.ContainerStateStopped || state == define.ContainerStateExited {
+ msg := fmt.Sprintf("Container %s is not running", name)
+ utils.Error(w, msg, http.StatusConflict, errors.New(msg))
+ return
+ }
+
+ timeout := con.StopTimeout()
+ if _, found := mux.Vars(r)["t"]; found {
+ timeout = uint(query.Timeout)
+ }
+
+ if err := con.RestartWithTimeout(r.Context(), timeout); err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ // Success
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
diff --git a/pkg/api/handlers/containers_top.go b/pkg/api/handlers/containers_top.go
new file mode 100644
index 000000000..bab559da1
--- /dev/null
+++ b/pkg/api/handlers/containers_top.go
@@ -0,0 +1,61 @@
+package handlers
+
+import (
+ "net/http"
+ "strings"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func TopContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+
+ query := struct {
+ PsArgs string `schema:"ps_args"`
+ }{
+ PsArgs: "-ef",
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ name := mux.Vars(r)["name"]
+ ctnr, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ state, err := ctnr.State()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ if state != define.ContainerStateRunning {
+ utils.ContainerNotRunning(w, name, errors.Errorf("Container %s must be running to perform top operation", name))
+ return
+ }
+
+ output, err := ctnr.Top([]string{})
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ var body = ContainerTopOKBody{}
+ if len(output) > 0 {
+ body.Titles = strings.Split(output[0], "\t")
+ for _, line := range output[1:] {
+ body.Processes = append(body.Processes, strings.Split(line, "\t"))
+ }
+ }
+ utils.WriteJSON(w, http.StatusOK, body)
+}
diff --git a/pkg/api/handlers/events.go b/pkg/api/handlers/events.go
new file mode 100644
index 000000000..900efa3da
--- /dev/null
+++ b/pkg/api/handlers/events.go
@@ -0,0 +1,46 @@
+package handlers
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/pkg/errors"
+)
+
+func GetEvents(w http.ResponseWriter, r *http.Request) {
+ query := struct {
+ Since string `json:"since"`
+ Until string `json:"until"`
+ Filters string `json:"filters"`
+ }{}
+ if err := decodeQuery(r, &query); err != nil {
+ utils.Error(w, "Failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ }
+
+ var filters = map[string][]string{}
+ if found := hasVar(r, "filters"); found {
+ if err := json.Unmarshal([]byte(query.Filters), &filters); err != nil {
+ utils.BadRequest(w, "filters", query.Filters, err)
+ return
+ }
+ }
+
+ var libpodFilters = make([]string, len(filters))
+ for k, v := range filters {
+ libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0]))
+ }
+
+ libpodEvents, err := getRuntime(r).GetEvents(libpodFilters)
+ if err != nil {
+ utils.BadRequest(w, "filters", query.Filters, err)
+ return
+ }
+
+ var apiEvents = make([]*Event, len(libpodEvents))
+ for _, v := range libpodEvents {
+ apiEvents = append(apiEvents, EventToApiEvent(v))
+ }
+ utils.WriteJSON(w, http.StatusOK, apiEvents)
+}
diff --git a/pkg/api/handlers/generic/containers.go b/pkg/api/handlers/generic/containers.go
new file mode 100644
index 000000000..5a0a51fd7
--- /dev/null
+++ b/pkg/api/handlers/generic/containers.go
@@ -0,0 +1,306 @@
+package generic
+
+import (
+ "context"
+ "encoding/binary"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/libpod/logs"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/docker/docker/api/types"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ log "github.com/sirupsen/logrus"
+)
+
+func RemoveContainer(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Force bool `schema:"force"`
+ Vols bool `schema:"v"`
+ Link bool `schema:"link"`
+ }{
+ // override any golang type defaults
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ if query.Link {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ utils.ErrLinkNotSupport)
+ return
+ }
+ utils.RemoveContainer(w, r, query.Force, query.Vols)
+}
+
+func ListContainers(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ containers, err := runtime.GetAllContainers()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ infoData, err := runtime.Info()
+ if err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain system info"))
+ return
+ }
+
+ var list = make([]*handlers.Container, len(containers))
+ for i, ctnr := range containers {
+ api, err := handlers.LibpodToContainer(ctnr, infoData)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ list[i] = api
+ }
+ utils.WriteResponse(w, http.StatusOK, list)
+}
+
+func GetContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ name := mux.Vars(r)["name"]
+ ctnr, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+ api, err := handlers.LibpodToContainerJSON(ctnr)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, api)
+}
+
+func KillContainer(w http.ResponseWriter, r *http.Request) {
+ // /{version}/containers/(name)/kill
+ con, err := utils.KillContainer(w, r)
+ if err != nil {
+ return
+ }
+ // the kill behavior for docker differs from podman in that they appear to wait
+ // for the Container to croak so the exit code is accurate immediately after the
+ // kill is sent. libpod does not. but we can add a wait here only for the docker
+ // side of things and mimic that behavior
+ if _, err = con.Wait(); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "failed to wait for Container %s", con.ID()))
+ return
+ }
+ // Success
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func WaitContainer(w http.ResponseWriter, r *http.Request) {
+ var msg string
+ // /{version}/containers/(name)/wait
+ exitCode, err := utils.WaitContainer(w, r)
+ if err != nil {
+ msg = err.Error()
+ }
+ utils.WriteResponse(w, http.StatusOK, handlers.ContainerWaitOKBody{
+ StatusCode: int(exitCode),
+ Error: struct {
+ Message string
+ }{
+ Message: msg,
+ },
+ })
+}
+
+func PruneContainers(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ containers, err := runtime.GetAllContainers()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ deletedContainers := []string{}
+ var spaceReclaimed uint64
+ for _, ctnr := range containers {
+ // Only remove stopped or exit'ed containers.
+ state, err := ctnr.State()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ switch state {
+ case define.ContainerStateStopped, define.ContainerStateExited:
+ default:
+ continue
+ }
+
+ deletedContainers = append(deletedContainers, ctnr.ID())
+ cSize, err := ctnr.RootFsSize()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ spaceReclaimed += uint64(cSize)
+
+ err = runtime.RemoveContainer(context.Background(), ctnr, false, false)
+ if err != nil && !(errors.Cause(err) == define.ErrCtrRemoved) {
+ utils.InternalServerError(w, err)
+ return
+ }
+ }
+ report := types.ContainersPruneReport{
+ ContainersDeleted: deletedContainers,
+ SpaceReclaimed: spaceReclaimed,
+ }
+ utils.WriteResponse(w, http.StatusOK, report)
+}
+
+func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ query := struct {
+ Follow bool `schema:"follow"`
+ Stdout bool `schema:"stdout"`
+ Stderr bool `schema:"stderr"`
+ Since string `schema:"since"`
+ Until string `schema:"until"`
+ Timestamps bool `schema:"timestamps"`
+ Tail string `schema:"tail"`
+ }{
+ Tail: "all",
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ if !(query.Stdout || query.Stderr) {
+ msg := fmt.Sprintf("%s: you must choose at least one stream", http.StatusText(http.StatusBadRequest))
+ utils.Error(w, msg, http.StatusBadRequest, errors.Errorf("%s for %s", msg, r.URL.String()))
+ return
+ }
+
+ name := mux.Vars(r)["name"]
+ ctnr, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ var tail int64 = -1
+ if query.Tail != "all" {
+ tail, err = strconv.ParseInt(query.Tail, 0, 64)
+ if err != nil {
+ utils.BadRequest(w, "tail", query.Tail, err)
+ return
+ }
+ }
+
+ var since time.Time
+ if _, found := mux.Vars(r)["since"]; found {
+ since, err = util.ParseInputTime(query.Since)
+ if err != nil {
+ utils.BadRequest(w, "since", query.Since, err)
+ return
+ }
+ }
+
+ var until time.Time
+ if _, found := mux.Vars(r)["until"]; found {
+ since, err = util.ParseInputTime(query.Until)
+ if err != nil {
+ utils.BadRequest(w, "until", query.Until, err)
+ return
+ }
+ }
+
+ options := &logs.LogOptions{
+ Details: true,
+ Follow: query.Follow,
+ Since: since,
+ Tail: tail,
+ Timestamps: query.Timestamps,
+ }
+
+ var wg sync.WaitGroup
+ options.WaitGroup = &wg
+
+ logChannel := make(chan *logs.LogLine, tail+1)
+ if err := runtime.Log([]*libpod.Container{ctnr}, options, logChannel); err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain logs for Container '%s'", name))
+ return
+ }
+ go func() {
+ wg.Wait()
+ close(logChannel)
+ }()
+
+ w.WriteHeader(http.StatusOK)
+ var builder strings.Builder
+ for ok := true; ok; ok = query.Follow {
+ for line := range logChannel {
+ if _, found := mux.Vars(r)["until"]; found {
+ if line.Time.After(until) {
+ break
+ }
+ }
+
+ // Reset variables we're ready to loop again
+ builder.Reset()
+ header := [8]byte{}
+
+ switch line.Device {
+ case "stdout":
+ if !query.Stdout {
+ continue
+ }
+ header[0] = 1
+ case "stderr":
+ if !query.Stderr {
+ continue
+ }
+ header[0] = 2
+ default:
+ // Logging and moving on is the best we can do here. We may have already sent
+ // a Status and Content-Type to client therefore we can no longer report an error.
+ log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID())
+ continue
+ }
+
+ if query.Timestamps {
+ builder.WriteString(line.Time.Format(time.RFC3339))
+ builder.WriteRune(' ')
+ }
+ builder.WriteString(line.Msg)
+
+ // Build header and output entry
+ binary.BigEndian.PutUint32(header[4:], uint32(len(header)+builder.Len()))
+ if _, err := w.Write(header[:]); err != nil {
+ log.Errorf("unable to write log output header: %q", err)
+ }
+ if _, err := fmt.Fprint(w, builder.String()); err != nil {
+ log.Errorf("unable to write builder string: %q", err)
+ }
+
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
+ }
+ }
+ }
+}
diff --git a/pkg/api/handlers/generic/containers_create.go b/pkg/api/handlers/generic/containers_create.go
new file mode 100644
index 000000000..ef5337abd
--- /dev/null
+++ b/pkg/api/handlers/generic/containers_create.go
@@ -0,0 +1,243 @@
+package generic
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ image2 "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/namespaces"
+ createconfig "github.com/containers/libpod/pkg/spec"
+ "github.com/containers/storage"
+ "github.com/docker/docker/pkg/signal"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ log "github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
+)
+
+func CreateContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ input := handlers.CreateContainerConfig{}
+ query := struct {
+ Name string `schema:"name"`
+ }{
+ // override any golang type defaults
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
+ return
+ }
+
+ newImage, err := runtime.ImageRuntime().NewFromLocal(input.Image)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "NewFromLocal()"))
+ return
+ }
+ cc, err := makeCreateConfig(input, newImage)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()"))
+ return
+ }
+
+ cc.Name = query.Name
+ var pod *libpod.Pod
+ ctr, err := shared.CreateContainerFromCreateConfig(runtime, &cc, r.Context(), pod)
+ if err != nil {
+ if strings.Contains(err.Error(), "invalid log driver") {
+ // this does not quite work yet and needs a little more massaging
+ w.Header().Set("Content-Type", "text/plain; charset=us-ascii")
+ w.WriteHeader(http.StatusInternalServerError)
+ msg := fmt.Sprintf("logger: no log driver named '%s' is registered", input.HostConfig.LogConfig.Type)
+ if _, err := fmt.Fprintln(w, msg); err != nil {
+ log.Errorf("%s: %q", msg, err)
+ }
+ //s.WriteResponse(w, http.StatusInternalServerError, fmt.Sprintf("logger: no log driver named '%s' is registered", input.HostConfig.LogConfig.Type))
+ return
+ }
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "CreateContainerFromCreateConfig()"))
+ return
+ }
+
+ type ctrCreateResponse struct {
+ Id string `json:"Id"`
+ Warnings []string `json:"Warnings"`
+ }
+ response := ctrCreateResponse{
+ Id: ctr.ID(),
+ Warnings: []string{}}
+
+ utils.WriteResponse(w, http.StatusCreated, response)
+}
+
+func makeCreateConfig(input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) {
+ var (
+ err error
+ init bool
+ tmpfs []string
+ volumes []string
+ )
+ env := make(map[string]string)
+ stopSignal := unix.SIGTERM
+ if len(input.StopSignal) > 0 {
+ stopSignal, err = signal.ParseSignal(input.StopSignal)
+ if err != nil {
+ return createconfig.CreateConfig{}, err
+ }
+ }
+
+ workDir := "/"
+ if len(input.WorkingDir) > 0 {
+ workDir = input.WorkingDir
+ }
+
+ stopTimeout := uint(define.CtrRemoveTimeout)
+ if input.StopTimeout != nil {
+ stopTimeout = uint(*input.StopTimeout)
+ }
+ c := createconfig.CgroupConfig{
+ Cgroups: "", // podman
+ Cgroupns: "", // podman
+ CgroupParent: "", // podman
+ CgroupMode: "", // podman
+ }
+ security := createconfig.SecurityConfig{
+ CapAdd: input.HostConfig.CapAdd,
+ CapDrop: input.HostConfig.CapDrop,
+ LabelOpts: nil, // podman
+ NoNewPrivs: false, // podman
+ ApparmorProfile: "", // podman
+ SeccompProfilePath: "",
+ SecurityOpts: input.HostConfig.SecurityOpt,
+ Privileged: input.HostConfig.Privileged,
+ ReadOnlyRootfs: input.HostConfig.ReadonlyRootfs,
+ ReadOnlyTmpfs: false, // podman-only
+ Sysctl: input.HostConfig.Sysctls,
+ }
+
+ network := createconfig.NetworkConfig{
+ DNSOpt: input.HostConfig.DNSOptions,
+ DNSSearch: input.HostConfig.DNSSearch,
+ DNSServers: input.HostConfig.DNS,
+ ExposedPorts: input.ExposedPorts,
+ HTTPProxy: false, // podman
+ IP6Address: "",
+ IPAddress: "",
+ LinkLocalIP: nil, // docker-only
+ MacAddress: input.MacAddress,
+ // NetMode: nil,
+ Network: input.HostConfig.NetworkMode.NetworkName(),
+ NetworkAlias: nil, // docker-only now
+ PortBindings: input.HostConfig.PortBindings,
+ Publish: nil, // podmanseccompPath
+ PublishAll: input.HostConfig.PublishAllPorts,
+ }
+
+ uts := createconfig.UtsConfig{
+ UtsMode: namespaces.UTSMode(input.HostConfig.UTSMode),
+ NoHosts: false, //podman
+ HostAdd: input.HostConfig.ExtraHosts,
+ Hostname: input.Hostname,
+ }
+
+ z := createconfig.UserConfig{
+ GroupAdd: input.HostConfig.GroupAdd,
+ IDMappings: &storage.IDMappingOptions{}, // podman //TODO <--- fix this,
+ UsernsMode: namespaces.UsernsMode(input.HostConfig.UsernsMode),
+ User: input.User,
+ }
+ pidConfig := createconfig.PidConfig{PidMode: namespaces.PidMode(input.HostConfig.PidMode)}
+ for k := range input.Volumes {
+ volumes = append(volumes, k)
+ }
+
+ // Docker is more flexible about its input where podman throws
+ // away incorrectly formatted variables so we cannot reuse the
+ // parsing of the env input
+ // [Foo Other=one Blank=]
+ for _, e := range input.Env {
+ splitEnv := strings.Split(e, "=")
+ switch len(splitEnv) {
+ case 0:
+ continue
+ case 1:
+ env[splitEnv[0]] = ""
+ default:
+ env[splitEnv[0]] = strings.Join(splitEnv[1:], "=")
+ }
+ }
+
+ // format the tmpfs mounts into a []string from map
+ for k, v := range input.HostConfig.Tmpfs {
+ tmpfs = append(tmpfs, fmt.Sprintf("%s:%s", k, v))
+ }
+
+ if input.HostConfig.Init != nil && *input.HostConfig.Init {
+ init = true
+ }
+
+ m := createconfig.CreateConfig{
+ Annotations: nil, // podman
+ Args: nil,
+ Cgroup: c,
+ CidFile: "",
+ ConmonPidFile: "", // podman
+ Command: input.Cmd,
+ UserCommand: input.Cmd, // podman
+ Detach: false, //
+ // Devices: input.HostConfig.Devices,
+ Entrypoint: input.Entrypoint,
+ Env: env,
+ HealthCheck: nil, //
+ Init: init,
+ InitPath: "", // tbd
+ Image: input.Image,
+ ImageID: newImage.ID(),
+ BuiltinImgVolumes: nil, // podman
+ ImageVolumeType: "", // podman
+ Interactive: false,
+ // IpcMode: input.HostConfig.IpcMode,
+ Labels: input.Labels,
+ LogDriver: input.HostConfig.LogConfig.Type, // is this correct
+ // LogDriverOpt: input.HostConfig.LogConfig.Config,
+ Name: input.Name,
+ Network: network,
+ Pod: "", // podman
+ PodmanPath: "", // podman
+ Quiet: false, // front-end only
+ Resources: createconfig.CreateResourceConfig{},
+ RestartPolicy: input.HostConfig.RestartPolicy.Name,
+ Rm: input.HostConfig.AutoRemove,
+ StopSignal: stopSignal,
+ StopTimeout: stopTimeout,
+ Systemd: false, // podman
+ Tmpfs: tmpfs,
+ User: z,
+ Uts: uts,
+ Tty: input.Tty,
+ Mounts: nil, // we populate
+ // MountsFlag: input.HostConfig.Mounts,
+ NamedVolumes: nil, // we populate
+ Volumes: volumes,
+ VolumesFrom: input.HostConfig.VolumesFrom,
+ WorkDir: workDir,
+ Rootfs: "", // podman
+ Security: security,
+ Syslog: false, // podman
+
+ Pid: pidConfig,
+ }
+ return m, nil
+}
diff --git a/pkg/api/handlers/generic/containers_stats.go b/pkg/api/handlers/generic/containers_stats.go
new file mode 100644
index 000000000..0c4efc1df
--- /dev/null
+++ b/pkg/api/handlers/generic/containers_stats.go
@@ -0,0 +1,198 @@
+package generic
+
+import (
+ "encoding/json"
+ "net/http"
+ "time"
+
+ "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/cgroups"
+ docker "github.com/docker/docker/api/types"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+const DefaultStatsPeriod = 5 * time.Second
+
+func StatsContainer(w http.ResponseWriter, r *http.Request) {
+ // 200 no error
+ // 404 no such
+ // 500 internal
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+
+ query := struct {
+ Stream bool `schema:"stream"`
+ }{
+ Stream: true,
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ name := mux.Vars(r)["name"]
+ ctnr, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ state, err := ctnr.State()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ if state != define.ContainerStateRunning && !query.Stream {
+ utils.WriteJSON(w, http.StatusOK, &handlers.Stats{StatsJSON: docker.StatsJSON{
+ Name: ctnr.Name(),
+ ID: ctnr.ID(),
+ }})
+ return
+ }
+
+ var preRead time.Time
+ var preCPUStats docker.CPUStats
+
+ stats, err := ctnr.GetContainerStats(&libpod.ContainerStats{})
+ if err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain Container %s stats", name))
+ return
+ }
+
+ if query.Stream {
+ preRead = time.Now()
+ preCPUStats = docker.CPUStats{
+ CPUUsage: docker.CPUUsage{
+ TotalUsage: stats.CPUNano,
+ PercpuUsage: []uint64{uint64(stats.CPU)},
+ UsageInKernelmode: 0,
+ UsageInUsermode: 0,
+ },
+ SystemUsage: 0,
+ OnlineCPUs: 0,
+ ThrottlingData: docker.ThrottlingData{},
+ }
+ time.Sleep(DefaultStatsPeriod)
+ }
+
+ cgroupPath, _ := ctnr.CGroupPath()
+ cgroup, _ := cgroups.Load(cgroupPath)
+
+ for ok := true; ok; ok = query.Stream {
+ state, _ := ctnr.State()
+ if state != define.ContainerStateRunning {
+ time.Sleep(10 * time.Second)
+ continue
+ }
+
+ stats, _ := ctnr.GetContainerStats(stats)
+ cgroupStat, _ := cgroup.Stat()
+ inspect, _ := ctnr.Inspect(false)
+
+ net := make(map[string]docker.NetworkStats)
+ net[inspect.NetworkSettings.EndpointID] = docker.NetworkStats{
+ RxBytes: stats.NetInput,
+ RxPackets: 0,
+ RxErrors: 0,
+ RxDropped: 0,
+ TxBytes: stats.NetOutput,
+ TxPackets: 0,
+ TxErrors: 0,
+ TxDropped: 0,
+ EndpointID: inspect.NetworkSettings.EndpointID,
+ InstanceID: "",
+ }
+
+ s := handlers.Stats{StatsJSON: docker.StatsJSON{
+ Stats: docker.Stats{
+ Read: time.Now(),
+ PreRead: preRead,
+ PidsStats: docker.PidsStats{
+ Current: cgroupStat.Pids.Current,
+ Limit: 0,
+ },
+ BlkioStats: docker.BlkioStats{
+ IoServiceBytesRecursive: toBlkioStatEntry(cgroupStat.Blkio.IoServiceBytesRecursive),
+ IoServicedRecursive: nil,
+ IoQueuedRecursive: nil,
+ IoServiceTimeRecursive: nil,
+ IoWaitTimeRecursive: nil,
+ IoMergedRecursive: nil,
+ IoTimeRecursive: nil,
+ SectorsRecursive: nil,
+ },
+ NumProcs: 0,
+ StorageStats: docker.StorageStats{
+ ReadCountNormalized: 0,
+ ReadSizeBytes: 0,
+ WriteCountNormalized: 0,
+ WriteSizeBytes: 0,
+ },
+ CPUStats: docker.CPUStats{
+ CPUUsage: docker.CPUUsage{
+ TotalUsage: cgroupStat.CPU.Usage.Total,
+ PercpuUsage: []uint64{uint64(stats.CPU)},
+ UsageInKernelmode: cgroupStat.CPU.Usage.Kernel,
+ UsageInUsermode: cgroupStat.CPU.Usage.Total - cgroupStat.CPU.Usage.Kernel,
+ },
+ SystemUsage: 0,
+ OnlineCPUs: uint32(len(cgroupStat.CPU.Usage.PerCPU)),
+ ThrottlingData: docker.ThrottlingData{
+ Periods: 0,
+ ThrottledPeriods: 0,
+ ThrottledTime: 0,
+ },
+ },
+ PreCPUStats: preCPUStats,
+ MemoryStats: docker.MemoryStats{
+ Usage: cgroupStat.Memory.Usage.Usage,
+ MaxUsage: cgroupStat.Memory.Usage.Limit,
+ Stats: nil,
+ Failcnt: 0,
+ Limit: cgroupStat.Memory.Usage.Limit,
+ Commit: 0,
+ CommitPeak: 0,
+ PrivateWorkingSet: 0,
+ },
+ },
+ Name: stats.Name,
+ ID: stats.ContainerID,
+ Networks: net,
+ }}
+
+ utils.WriteJSON(w, http.StatusOK, s)
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
+ }
+
+ preRead = s.Read
+ bits, err := json.Marshal(s.CPUStats)
+ if err != nil {
+ logrus.Errorf("unable to marshal cpu stats: %q", err)
+ }
+ if err := json.Unmarshal(bits, &preCPUStats); err != nil {
+ logrus.Errorf("unable to unmarshal previous stats: %q", err)
+ }
+ time.Sleep(DefaultStatsPeriod)
+ }
+}
+
+func toBlkioStatEntry(entries []cgroups.BlkIOEntry) []docker.BlkioStatEntry {
+ results := make([]docker.BlkioStatEntry, 0, len(entries))
+ for i, e := range entries {
+ bits, err := json.Marshal(e)
+ if err != nil {
+ logrus.Errorf("unable to marshal blkio stats: %q", err)
+ }
+ if err := json.Unmarshal(bits, &results[i]); err != nil {
+ logrus.Errorf("unable to unmarshal blkio stats: %q", err)
+ }
+ }
+ return results
+}
diff --git a/pkg/api/handlers/generic/images.go b/pkg/api/handlers/generic/images.go
new file mode 100644
index 000000000..8029ee861
--- /dev/null
+++ b/pkg/api/handlers/generic/images.go
@@ -0,0 +1,363 @@
+package generic
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "strconv"
+ "strings"
+
+ "github.com/containers/buildah"
+ "github.com/containers/image/v5/manifest"
+ "github.com/containers/libpod/libpod"
+ image2 "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/containers/storage"
+ "github.com/docker/docker/api/types"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+func ExportImage(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 500 server
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ name := mux.Vars(r)["name"]
+ newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name))
+ return
+ }
+ tmpfile, err := ioutil.TempFile("", "api.tar")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
+ return
+ }
+ if err := tmpfile.Close(); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
+ return
+ }
+ if err := newImage.Save(r.Context(), name, "docker-archive", tmpfile.Name(), []string{}, false, false); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to save image"))
+ return
+ }
+ rdr, err := os.Open(tmpfile.Name())
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile"))
+ return
+ }
+ defer rdr.Close()
+ defer os.Remove(tmpfile.Name())
+ utils.WriteResponse(w, http.StatusOK, rdr)
+}
+
+func PruneImages(w http.ResponseWriter, r *http.Request) {
+ // 200 no error
+ // 500 internal
+ var (
+ dangling bool = true
+ err error
+ )
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ query := struct {
+ filters map[string]string
+ }{
+ // This is where you can override the golang default value for one of fields
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ // FIXME This is likely wrong due to it not being a map[string][]string
+
+ // until ts is not supported on podman prune
+ if len(query.filters["until"]) > 0 {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "until is not supported yet"))
+ return
+ }
+ // labels are not supported on podman prune
+ if len(query.filters["label"]) > 0 {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "labelis not supported yet"))
+ return
+ }
+
+ if len(query.filters["dangling"]) > 0 {
+ dangling, err = strconv.ParseBool(query.filters["dangling"])
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "processing dangling filter"))
+ return
+ }
+ }
+ idr := []types.ImageDeleteResponseItem{}
+ //
+ // This code needs to be migrated to libpod to work correctly. I could not
+ // work my around the information docker needs with the existing prune in libpod.
+ //
+ pruneImages, err := runtime.ImageRuntime().GetPruneImages(!dangling, []image2.ImageFilter{})
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to get images to prune"))
+ return
+ }
+ for _, p := range pruneImages {
+ repotags, err := p.RepoTags()
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to get repotags for image"))
+ return
+ }
+ if err := p.Remove(r.Context(), true); err != nil {
+ if errors.Cause(err) == storage.ErrImageUsedByContainer {
+ logrus.Warnf("Failed to prune image %s as it is in use: %v", p.ID(), err)
+ continue
+ }
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to prune image"))
+ return
+ }
+ // newimageevent is not export therefore we cannot record the event. this will be fixed
+ // when the prune is fixed in libpod
+ // defer p.newImageEvent(events.Prune)
+ response := types.ImageDeleteResponseItem{
+ Deleted: fmt.Sprintf("sha256:%s", p.ID()), // I ack this is not ideal
+ }
+ if len(repotags) > 0 {
+ response.Untagged = repotags[0]
+ }
+ idr = append(idr, response)
+ }
+ ipr := types.ImagesPruneReport{
+ ImagesDeleted: idr,
+ SpaceReclaimed: 1, // TODO we cannot supply this right now
+ }
+ utils.WriteResponse(w, http.StatusOK, handlers.ImagesPruneReport{ImagesPruneReport: ipr})
+}
+
+func CommitContainer(w http.ResponseWriter, r *http.Request) {
+ var (
+ destImage string
+ )
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ query := struct {
+ author string
+ changes string
+ comment string
+ container string
+ //fromSrc string # fromSrc is currently unused
+ pause bool
+ repo string
+ tag string
+ }{
+ // This is where you can override the golang default value for one of fields
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ rtc, err := runtime.GetConfig()
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
+ return
+ }
+ sc := image2.GetSystemContext(rtc.SignaturePolicyPath, "", false)
+ tag := "latest"
+ options := libpod.ContainerCommitOptions{
+ Pause: true,
+ }
+ options.CommitOptions = buildah.CommitOptions{
+ SignaturePolicyPath: rtc.SignaturePolicyPath,
+ ReportWriter: os.Stderr,
+ SystemContext: sc,
+ PreferredManifestType: manifest.DockerV2Schema2MediaType,
+ }
+
+ input := handlers.CreateContainerConfig{}
+ if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
+ return
+ }
+
+ if len(query.tag) > 0 {
+ tag = query.tag
+ }
+ options.Message = query.comment
+ options.Author = query.author
+ options.Pause = query.pause
+ options.Changes = strings.Fields(query.changes)
+ ctr, err := runtime.LookupContainer(query.container)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusNotFound, err)
+ return
+ }
+
+ // I know mitr hates this ... but doing for now
+ if len(query.repo) > 1 {
+ destImage = fmt.Sprintf("%s:%s", query.repo, tag)
+ }
+
+ commitImage, err := ctr.Commit(r.Context(), destImage, options)
+ if err != nil && !strings.Contains(err.Error(), "is not running") {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "CommitFailure"))
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: commitImage.ID()}) // nolint
+}
+
+func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) {
+ // 200 no error
+ // 404 repo does not exist or no read access
+ // 500 internal
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ query := struct {
+ fromSrc string
+ changes []string
+ }{
+ // This is where you can override the golang default value for one of fields
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ // fromSrc – Source to import. The value may be a URL from which the image can be retrieved or - to read the image from the request body. This parameter may only be used when importing an image.
+ source := query.fromSrc
+ if source == "-" {
+ f, err := ioutil.TempFile("", "api_load.tar")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile"))
+ return
+ }
+ source = f.Name()
+ if err := handlers.SaveFromBody(f, r); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file"))
+ }
+ }
+ iid, err := runtime.Import(r.Context(), source, "", query.changes, "", false)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball"))
+ return
+ }
+ tmpfile, err := ioutil.TempFile("", "fromsrc.tar")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
+ return
+ }
+ if err := tmpfile.Close(); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
+ return
+ }
+ // Success
+ utils.WriteResponse(w, http.StatusOK, struct {
+ Status string `json:"status"`
+ Progress string `json:"progress"`
+ ProgressDetail map[string]string `json:"progressDetail"`
+ Id string `json:"id"`
+ }{
+ Status: iid,
+ ProgressDetail: map[string]string{},
+ Id: iid,
+ })
+
+}
+
+func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
+ // 200 no error
+ // 404 repo does not exist or no read access
+ // 500 internal
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ query := struct {
+ fromImage string
+ tag string
+ }{
+ // This is where you can override the golang default value for one of fields
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ /*
+ fromImage – Name of the image to pull. The name may include a tag or digest. This parameter may only be used when pulling an image. The pull is cancelled if the HTTP connection is closed.
+ repo – Repository name given to an image when it is imported. The repo may include a tag. This parameter may only be used when importing an image.
+ tag – Tag or digest. If empty when pulling an image, this causes all tags for the given image to be pulled.
+ */
+ fromImage := query.fromImage
+ if len(query.tag) < 1 {
+ fromImage = fmt.Sprintf("%s:%s", fromImage, query.tag)
+ }
+
+ // TODO
+ // We are eating the output right now because we haven't talked about how to deal with multiple responses yet
+ img, err := runtime.ImageRuntime().New(r.Context(), fromImage, "", "", nil, &image2.DockerRegistryOptions{}, image2.SigningOptions{}, nil, util.PullImageMissing)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+
+ // Success
+ utils.WriteResponse(w, http.StatusOK, struct {
+ Status string `json:"status"`
+ Error string `json:"error"`
+ Progress string `json:"progress"`
+ ProgressDetail map[string]string `json:"progressDetail"`
+ Id string `json:"id"`
+ }{
+ Status: fmt.Sprintf("pulling image (%s) from %s", img.Tag, strings.Join(img.Names(), ", ")),
+ ProgressDetail: map[string]string{},
+ Id: img.ID(),
+ })
+}
+
+func GetImage(w http.ResponseWriter, r *http.Request) {
+ // 200 no error
+ // 404 no such
+ // 500 internal
+ name := mux.Vars(r)["name"]
+ newImage, err := handlers.GetImage(r, name)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name))
+ return
+ }
+ inspect, err := handlers.ImageDataToImageInspect(r.Context(), newImage)
+ if err != nil {
+ utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "Failed to convert ImageData to ImageInspect '%s'", inspect.ID))
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, inspect)
+}
+
+func GetImages(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 500 internal
+ images, err := utils.GetImages(w, r)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images"))
+ return
+ }
+ var summaries = make([]*handlers.ImageSummary, len(images))
+ for j, img := range images {
+ is, err := handlers.ImageToImageSummary(img)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed transform image summaries"))
+ return
+ }
+ summaries[j] = is
+ }
+ utils.WriteResponse(w, http.StatusOK, summaries)
+}
diff --git a/pkg/api/handlers/generic/info.go b/pkg/api/handlers/generic/info.go
new file mode 100644
index 000000000..c9e79233d
--- /dev/null
+++ b/pkg/api/handlers/generic/info.go
@@ -0,0 +1,196 @@
+package generic
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "os"
+ goRuntime "runtime"
+ "strings"
+ "time"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/config"
+ "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/rootless"
+ "github.com/containers/libpod/pkg/sysinfo"
+ docker "github.com/docker/docker/api/types"
+ "github.com/docker/docker/api/types/swarm"
+ "github.com/google/uuid"
+ "github.com/pkg/errors"
+ log "github.com/sirupsen/logrus"
+)
+
+func GetInfo(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 500 internal
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ infoData, err := runtime.Info()
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to obtain system memory info"))
+ return
+ }
+ hostInfo := infoData[0].Data
+ storeInfo := infoData[1].Data
+
+ configInfo, err := runtime.GetConfig()
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to obtain runtime config"))
+ return
+ }
+ versionInfo, err := define.GetVersion()
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to obtain podman versions"))
+ return
+ }
+ stateInfo := getContainersState(runtime)
+ sysInfo := sysinfo.New(true)
+
+ // FIXME: Need to expose if runtime supports Checkpoint'ing
+ // liveRestoreEnabled := criu.CheckForCriu() && configInfo.RuntimeSupportsCheckpoint()
+
+ info := &handlers.Info{Info: docker.Info{
+ Architecture: goRuntime.GOARCH,
+ BridgeNfIP6tables: !sysInfo.BridgeNFCallIP6TablesDisabled,
+ BridgeNfIptables: !sysInfo.BridgeNFCallIPTablesDisabled,
+ CPUCfsPeriod: sysInfo.CPUCfsPeriod,
+ CPUCfsQuota: sysInfo.CPUCfsQuota,
+ CPUSet: sysInfo.Cpuset,
+ CPUShares: sysInfo.CPUShares,
+ CgroupDriver: configInfo.CgroupManager,
+ ClusterAdvertise: "",
+ ClusterStore: "",
+ ContainerdCommit: docker.Commit{},
+ Containers: storeInfo["ContainerStore"].(map[string]interface{})["number"].(int),
+ ContainersPaused: stateInfo[define.ContainerStatePaused],
+ ContainersRunning: stateInfo[define.ContainerStateRunning],
+ ContainersStopped: stateInfo[define.ContainerStateStopped] + stateInfo[define.ContainerStateExited],
+ Debug: log.IsLevelEnabled(log.DebugLevel),
+ DefaultRuntime: configInfo.OCIRuntime,
+ DockerRootDir: storeInfo["GraphRoot"].(string),
+ Driver: storeInfo["GraphDriverName"].(string),
+ DriverStatus: getGraphStatus(storeInfo),
+ ExperimentalBuild: true,
+ GenericResources: nil,
+ HTTPProxy: getEnv("http_proxy"),
+ HTTPSProxy: getEnv("https_proxy"),
+ ID: uuid.New().String(),
+ IPv4Forwarding: !sysInfo.IPv4ForwardingDisabled,
+ Images: storeInfo["ImageStore"].(map[string]interface{})["number"].(int),
+ IndexServerAddress: "",
+ InitBinary: "",
+ InitCommit: docker.Commit{},
+ Isolation: "",
+ KernelMemory: sysInfo.KernelMemory,
+ KernelMemoryTCP: false,
+ KernelVersion: hostInfo["kernel"].(string),
+ Labels: nil,
+ LiveRestoreEnabled: false,
+ LoggingDriver: "",
+ MemTotal: hostInfo["MemTotal"].(int64),
+ MemoryLimit: sysInfo.MemoryLimit,
+ NCPU: goRuntime.NumCPU(),
+ NEventsListener: 0,
+ NFd: getFdCount(),
+ NGoroutines: goRuntime.NumGoroutine(),
+ Name: hostInfo["hostname"].(string),
+ NoProxy: getEnv("no_proxy"),
+ OSType: goRuntime.GOOS,
+ OSVersion: hostInfo["Distribution"].(map[string]interface{})["version"].(string),
+ OomKillDisable: sysInfo.OomKillDisable,
+ OperatingSystem: hostInfo["Distribution"].(map[string]interface{})["distribution"].(string),
+ PidsLimit: sysInfo.PidsLimit,
+ Plugins: docker.PluginsInfo{},
+ ProductLicense: "Apache-2.0",
+ RegistryConfig: nil,
+ RuncCommit: docker.Commit{},
+ Runtimes: getRuntimes(configInfo),
+ SecurityOptions: getSecOpts(sysInfo),
+ ServerVersion: versionInfo.Version,
+ SwapLimit: sysInfo.SwapLimit,
+ Swarm: swarm.Info{
+ LocalNodeState: swarm.LocalNodeStateInactive,
+ },
+ SystemStatus: nil,
+ SystemTime: time.Now().Format(time.RFC3339Nano),
+ Warnings: []string{},
+ },
+ BuildahVersion: hostInfo["BuildahVersion"].(string),
+ CPURealtimePeriod: sysInfo.CPURealtimePeriod,
+ CPURealtimeRuntime: sysInfo.CPURealtimeRuntime,
+ CgroupVersion: hostInfo["CgroupVersion"].(string),
+ Rootless: rootless.IsRootless(),
+ SwapFree: hostInfo["SwapFree"].(int64),
+ SwapTotal: hostInfo["SwapTotal"].(int64),
+ Uptime: hostInfo["uptime"].(string),
+ }
+ utils.WriteResponse(w, http.StatusOK, info)
+}
+
+func getGraphStatus(storeInfo map[string]interface{}) [][2]string {
+ var graphStatus [][2]string
+ for k, v := range storeInfo["GraphStatus"].(map[string]string) {
+ graphStatus = append(graphStatus, [2]string{k, v})
+ }
+ return graphStatus
+}
+
+func getSecOpts(sysInfo *sysinfo.SysInfo) []string {
+ var secOpts []string
+ if sysInfo.AppArmor {
+ secOpts = append(secOpts, "name=apparmor")
+ }
+ if sysInfo.Seccomp {
+ // FIXME: get profile name...
+ secOpts = append(secOpts, fmt.Sprintf("name=seccomp,profile=%s", "default"))
+ }
+ return secOpts
+}
+
+func getRuntimes(configInfo *config.Config) map[string]docker.Runtime {
+ var runtimes = map[string]docker.Runtime{}
+ for name, paths := range configInfo.OCIRuntimes {
+ runtimes[name] = docker.Runtime{
+ Path: paths[0],
+ Args: nil,
+ }
+ }
+ return runtimes
+}
+
+func getFdCount() (count int) {
+ count = -1
+ if entries, err := ioutil.ReadDir("/proc/self/fd"); err == nil {
+ count = len(entries)
+ }
+ return
+}
+
+// Just ignoring Container errors here...
+func getContainersState(r *libpod.Runtime) map[define.ContainerStatus]int {
+ var states = map[define.ContainerStatus]int{}
+ ctnrs, err := r.GetAllContainers()
+ if err == nil {
+ for _, ctnr := range ctnrs {
+ state, err := ctnr.State()
+ if err != nil {
+ continue
+ }
+ states[state] += 1
+ }
+ }
+ return states
+}
+
+func getEnv(value string) string {
+ if v, exists := os.LookupEnv(strings.ToUpper(value)); exists {
+ return v
+ }
+ if v, exists := os.LookupEnv(strings.ToLower(value)); exists {
+ return v
+ }
+ return ""
+}
diff --git a/pkg/api/handlers/generic/ping.go b/pkg/api/handlers/generic/ping.go
new file mode 100644
index 000000000..44a67d53f
--- /dev/null
+++ b/pkg/api/handlers/generic/ping.go
@@ -0,0 +1,25 @@
+package generic
+
+import (
+ "fmt"
+ "net/http"
+)
+
+func PingGET(w http.ResponseWriter, _ *http.Request) {
+ setHeaders(w)
+ fmt.Fprintln(w, "OK")
+}
+
+func PingHEAD(w http.ResponseWriter, _ *http.Request) {
+ setHeaders(w)
+ fmt.Fprintln(w, "")
+}
+
+func setHeaders(w http.ResponseWriter) {
+ w.Header().Set("API-Version", DefaultApiVersion)
+ w.Header().Set("BuildKit-Version", "")
+ w.Header().Set("Docker-Experimental", "true")
+ w.Header().Set("Cache-Control", "no-cache")
+ w.Header().Set("Pragma", "no-cache")
+ w.WriteHeader(http.StatusOK)
+}
diff --git a/pkg/api/handlers/generic/system.go b/pkg/api/handlers/generic/system.go
new file mode 100644
index 000000000..edf1f8522
--- /dev/null
+++ b/pkg/api/handlers/generic/system.go
@@ -0,0 +1,18 @@
+package generic
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ docker "github.com/docker/docker/api/types"
+)
+
+func GetDiskUsage(w http.ResponseWriter, r *http.Request) {
+ utils.WriteResponse(w, http.StatusOK, handlers.DiskUsage{DiskUsage: docker.DiskUsage{
+ LayersSize: 0,
+ Images: nil,
+ Containers: nil,
+ Volumes: nil,
+ }})
+}
diff --git a/pkg/api/handlers/generic/version.go b/pkg/api/handlers/generic/version.go
new file mode 100644
index 000000000..39423914d
--- /dev/null
+++ b/pkg/api/handlers/generic/version.go
@@ -0,0 +1,74 @@
+package generic
+
+import (
+ "fmt"
+ "net/http"
+ goRuntime "runtime"
+ "time"
+
+ "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"
+ docker "github.com/docker/docker/api/types"
+ "github.com/pkg/errors"
+)
+
+const (
+ DefaultApiVersion = "1.40" // See https://docs.docker.com/engine/api/v1.40/
+ MinimalApiVersion = "1.24"
+)
+
+func VersionHandler(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 500 internal
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ versionInfo, err := define.GetVersion()
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+
+ infoData, err := runtime.Info()
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to obtain system memory info"))
+ return
+ }
+ hostInfo := infoData[0].Data
+
+ components := []docker.ComponentVersion{{
+ Name: "Podman Engine",
+ Version: versionInfo.Version,
+ Details: map[string]string{
+ "APIVersion": DefaultApiVersion,
+ "Arch": goRuntime.GOARCH,
+ "BuildTime": time.Unix(versionInfo.Built, 0).Format(time.RFC3339),
+ "Experimental": "true",
+ "GitCommit": versionInfo.GitCommit,
+ "GoVersion": versionInfo.GoVersion,
+ "KernelVersion": hostInfo["kernel"].(string),
+ "MinAPIVersion": MinimalApiVersion,
+ "Os": goRuntime.GOOS,
+ },
+ }}
+
+ utils.WriteResponse(w, http.StatusOK, handlers.Version{Version: docker.Version{
+ Platform: struct {
+ Name string
+ }{
+ Name: fmt.Sprintf("%s/%s/%s", goRuntime.GOOS, goRuntime.GOARCH, hostInfo["Distribution"].(map[string]interface{})["distribution"].(string)),
+ },
+ 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
new file mode 100644
index 000000000..2efeb1379
--- /dev/null
+++ b/pkg/api/handlers/handler.go
@@ -0,0 +1,46 @@
+package handlers
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+// Convenience routines to reduce boiler plate in handlers
+
+func getVar(r *http.Request, k string) string {
+ return mux.Vars(r)[k]
+}
+
+func hasVar(r *http.Request, k string) bool {
+ _, found := mux.Vars(r)[k]
+ return found
+}
+func getName(r *http.Request) string {
+ return getVar(r, "name")
+}
+
+func decodeQuery(r *http.Request, i interface{}) error {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+
+ if err := decoder.Decode(i, r.URL.Query()); err != nil {
+ return errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())
+ }
+ return nil
+}
+
+func getRuntime(r *http.Request) *libpod.Runtime {
+ return r.Context().Value("runtime").(*libpod.Runtime)
+}
+
+func getHeader(r *http.Request, k string) string {
+ return r.Header.Get(k)
+}
+
+func hasHeader(r *http.Request, k string) bool {
+ _, found := r.Header[k]
+ return found
+}
diff --git a/pkg/api/handlers/images.go b/pkg/api/handlers/images.go
new file mode 100644
index 000000000..d4cddbfb2
--- /dev/null
+++ b/pkg/api/handlers/images.go
@@ -0,0 +1,185 @@
+package handlers
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "strconv"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func HistoryImage(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ var allHistory []HistoryResponse
+
+ newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name))
+ return
+
+ }
+ history, err := newImage.History(r.Context())
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ for _, h := range history {
+ l := HistoryResponse{
+ ID: h.ID,
+ Created: h.Created.UnixNano(),
+ CreatedBy: h.CreatedBy,
+ Tags: h.Tags,
+ Size: h.Size,
+ Comment: h.Comment,
+ }
+ allHistory = append(allHistory, l)
+ }
+ utils.WriteResponse(w, http.StatusOK, allHistory)
+}
+
+func TagImage(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ // /v1.xx/images/(name)/tag
+ name := mux.Vars(r)["name"]
+ newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name))
+ return
+ }
+ tag := "latest"
+ if len(r.Form.Get("tag")) > 0 {
+ tag = r.Form.Get("tag")
+ }
+ if len(r.Form.Get("repo")) < 1 {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.New("repo parameter is required to tag an image"))
+ return
+ }
+ repo := r.Form.Get("repo")
+ tagName := fmt.Sprintf("%s:%s", repo, tag)
+ if err := newImage.TagImage(tagName); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusCreated, "")
+}
+
+func RemoveImage(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ name := mux.Vars(r)["name"]
+ newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name))
+ return
+ }
+
+ force := false
+ if len(r.Form.Get("force")) > 0 {
+ force, err = strconv.ParseBool(r.Form.Get("force"))
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, err)
+ return
+ }
+ }
+ _, err = runtime.RemoveImage(r.Context(), newImage, force)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+ // TODO
+ // This will need to be fixed for proper response, like Deleted: and Untagged:
+ m := make(map[string]string)
+ m["Deleted"] = newImage.ID()
+ foo := []map[string]string{}
+ foo = append(foo, m)
+ utils.WriteResponse(w, http.StatusOK, foo)
+
+}
+func GetImage(r *http.Request, name string) (*image.Image, error) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ return runtime.ImageRuntime().NewFromLocal(name)
+}
+
+func LoadImage(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ query := struct {
+ //quiet bool # quiet is currently unused
+ }{
+ // This is where you can override the golang default value for one of fields
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ var (
+ err error
+ writer io.Writer
+ )
+ f, err := ioutil.TempFile("", "api_load.tar")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile"))
+ return
+ }
+ if err := SaveFromBody(f, r); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file"))
+ return
+ }
+ id, err := runtime.LoadImage(r.Context(), "", f.Name(), writer, "")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to load image"))
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, struct {
+ Stream string `json:"stream"`
+ }{
+ Stream: fmt.Sprintf("Loaded image: %s\n", id),
+ })
+}
+
+func SaveFromBody(f *os.File, r *http.Request) error { // nolint
+ if _, err := io.Copy(f, r.Body); err != nil {
+ return err
+ }
+ return f.Close()
+}
+
+func SearchImages(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Term string `json:"term"`
+ Limit int `json:"limit"`
+ Filters map[string][]string `json:"filters"`
+ }{
+ // This is where you can override the golang default value for one of fields
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ // TODO filters are a bit undefined here in terms of what exactly the input looks
+ // like. We need to understand that a bit more.
+ options := image.SearchOptions{
+ Filter: image.SearchFilter{},
+ Limit: query.Limit,
+ }
+ results, err := image.SearchImages(query.Term, options)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusOK, results)
+}
diff --git a/pkg/api/handlers/images_build.go b/pkg/api/handlers/images_build.go
new file mode 100644
index 000000000..c7c746392
--- /dev/null
+++ b/pkg/api/handlers/images_build.go
@@ -0,0 +1,233 @@
+package handlers
+
+import (
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/buildah"
+ "github.com/containers/buildah/imagebuildah"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/storage/pkg/archive"
+ log "github.com/sirupsen/logrus"
+)
+
+func BuildImage(w http.ResponseWriter, r *http.Request) {
+ authConfigs := map[string]AuthConfig{}
+ if hasHeader(r, "X-Registry-Config") {
+ registryHeader := getHeader(r, "X-Registry-Config")
+ authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(registryHeader))
+ if json.NewDecoder(authConfigsJSON).Decode(&authConfigs) != nil {
+ utils.BadRequest(w, "X-Registry-Config", registryHeader, json.NewDecoder(authConfigsJSON).Decode(&authConfigs))
+ return
+ }
+ }
+
+ anchorDir, err := extractTarFile(r, w)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ // defer os.RemoveAll(anchorDir)
+
+ query := struct {
+ Dockerfile string `json:"dockerfile"`
+ Tag string `json:"t"`
+ ExtraHosts string `json:"extrahosts"`
+ Remote string `json:"remote"`
+ Quiet bool `json:"q"`
+ NoCache bool `json:"nocache"`
+ CacheFrom string `json:"cachefrom"`
+ Pull string `json:"pull"`
+ Rm bool `json:"rm"`
+ ForceRm bool `json:"forcerm"`
+ Memory int `json:"memory"`
+ MemSwap int `json:"memswap"`
+ CpuShares int `json:"cpushares"`
+ CpuSetCpus string `json:"cpusetcpus"`
+ CpuPeriod int `json:"cpuperiod"`
+ CpuQuota int `json:"cpuquota"`
+ BuildArgs string `json:"buildargs"`
+ ShmSize int `json:"shmsize"`
+ Squash bool `json:"squash"`
+ Labels string `json:"labels"`
+ NetworkMode string `json:"networkmode"`
+ Platform string `json:"platform"`
+ Target string `json:"target"`
+ Outputs string `json:"outputs"`
+ }{
+ Dockerfile: "Dockerfile",
+ Tag: "",
+ ExtraHosts: "",
+ Remote: "",
+ Quiet: false,
+ NoCache: false,
+ CacheFrom: "",
+ Pull: "",
+ Rm: true,
+ ForceRm: false,
+ Memory: 0,
+ MemSwap: 0,
+ CpuShares: 0,
+ CpuSetCpus: "",
+ CpuPeriod: 0,
+ CpuQuota: 0,
+ BuildArgs: "",
+ ShmSize: 64 * 1024 * 1024,
+ Squash: false,
+ Labels: "",
+ NetworkMode: "",
+ Platform: "",
+ Target: "",
+ Outputs: "",
+ }
+
+ if err := decodeQuery(r, &query); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
+ return
+ }
+
+ // Tag is the name with optional tag...
+ var name = query.Tag
+ var tag string
+ if strings.Contains(query.Tag, ":") {
+ tokens := strings.SplitN(query.Tag, ":", 2)
+ name = tokens[0]
+ tag = tokens[1]
+ }
+
+ var buildArgs = map[string]string{}
+ if found := hasVar(r, "buildargs"); found {
+ if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil {
+ utils.BadRequest(w, "buildargs", query.BuildArgs, err)
+ return
+ }
+ }
+
+ // convert label formats
+ var labels = []string{}
+ if hasVar(r, "labels") {
+ var m = map[string]string{}
+ if err := json.Unmarshal([]byte(query.Labels), &m); err != nil {
+ utils.BadRequest(w, "labels", query.Labels, err)
+ return
+ }
+
+ for k, v := range m {
+ labels = append(labels, fmt.Sprintf("%s=%v", k, v))
+ }
+ }
+
+ buildOptions := imagebuildah.BuildOptions{
+ ContextDirectory: filepath.Join(anchorDir, "build"),
+ PullPolicy: 0,
+ Registry: "",
+ IgnoreUnrecognizedInstructions: false,
+ Quiet: query.Quiet,
+ Isolation: 0,
+ Runtime: "",
+ RuntimeArgs: nil,
+ TransientMounts: nil,
+ Compression: 0,
+ Args: buildArgs,
+ Output: name,
+ AdditionalTags: []string{tag},
+ Log: nil,
+ In: nil,
+ Out: nil,
+ Err: nil,
+ SignaturePolicyPath: "",
+ ReportWriter: nil,
+ OutputFormat: "",
+ SystemContext: nil,
+ NamespaceOptions: nil,
+ ConfigureNetwork: 0,
+ CNIPluginPath: "",
+ CNIConfigDir: "",
+ IDMappingOptions: nil,
+ AddCapabilities: nil,
+ DropCapabilities: nil,
+ CommonBuildOpts: &buildah.CommonBuildOptions{},
+ DefaultMountsFilePath: "",
+ IIDFile: "",
+ Squash: query.Squash,
+ Labels: labels,
+ Annotations: nil,
+ OnBuild: nil,
+ Layers: false,
+ NoCache: query.NoCache,
+ RemoveIntermediateCtrs: query.Rm,
+ ForceRmIntermediateCtrs: query.ForceRm,
+ BlobDirectory: "",
+ Target: query.Target,
+ Devices: nil,
+ }
+
+ id, _, err := getRuntime(r).Build(r.Context(), buildOptions, query.Dockerfile)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+
+ // Find image ID that was built...
+ utils.WriteResponse(w, http.StatusOK,
+ struct {
+ Stream string `json:"stream"`
+ }{
+ Stream: fmt.Sprintf("Successfully built %s\n", id),
+ })
+}
+
+func extractTarFile(r *http.Request, w http.ResponseWriter) (string, error) {
+ var (
+ // length int64
+ // n int64
+ copyErr error
+ )
+
+ // build a home for the request body
+ anchorDir, err := ioutil.TempDir("", "libpod_builder")
+ if err != nil {
+ return "", err
+ }
+ buildDir := filepath.Join(anchorDir, "build")
+
+ path := filepath.Join(anchorDir, "tarBall")
+ tarBall, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+ if err != nil {
+ return "", err
+ }
+ defer tarBall.Close()
+
+ // if hasHeader(r, "Content-Length") {
+ // length, err := strconv.ParseInt(getHeader(r, "Content-Length"), 10, 64)
+ // if err != nil {
+ // return "", errors.New(fmt.Sprintf("Failed request: unable to parse Content-Length of '%s'", getHeader(r, "Content-Length")))
+ // }
+ // n, copyErr = io.CopyN(tarBall, r.Body, length+1)
+ // } else {
+ _, copyErr = io.Copy(tarBall, r.Body)
+ // }
+ r.Body.Close()
+
+ if copyErr != nil {
+ utils.InternalServerError(w,
+ fmt.Errorf("failed Request: Unable to copy tar file from request body %s", r.RequestURI))
+ }
+ log.Debugf("Content-Length: %s", getVar(r, "Content-Length"))
+
+ // if hasHeader(r, "Content-Length") && n != length {
+ // return "", errors.New(fmt.Sprintf("Failed request: Given Content-Length does not match file size %d != %d", n, length))
+ // }
+
+ _, _ = tarBall.Seek(0, 0)
+ if err := archive.Untar(tarBall, buildDir, &archive.TarOptions{}); err != nil {
+ return "", err
+ }
+ return anchorDir, nil
+}
diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go
new file mode 100644
index 000000000..bfb028b1b
--- /dev/null
+++ b/pkg/api/handlers/libpod/containers.go
@@ -0,0 +1,186 @@
+package libpod
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func StopContainer(w http.ResponseWriter, r *http.Request) {
+ handlers.StopContainer(w, r)
+}
+
+func ContainerExists(w http.ResponseWriter, r *http.Request) {
+ // 404 no such container
+ // 200 ok
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ _, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func RemoveContainer(w http.ResponseWriter, r *http.Request) {
+ // 204 no error
+ // 400 bad param
+ // 404 no such container
+ // 409 conflict
+ // 500 internal error
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Force bool `schema:"force"`
+ Vols bool `schema:"v"`
+ }{
+ // override any golang type defaults
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ utils.RemoveContainer(w, r, query.Force, query.Vols)
+}
+func ListContainers(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Filter []string `schema:"filter"`
+ Last int `schema:"last"`
+ Size bool `schema:"size"`
+ Sync bool `schema:"sync"`
+ }{
+ // override any golang type defaults
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ opts := shared.PsOptions{
+ All: true,
+ Last: query.Last,
+ Size: query.Size,
+ Sort: "",
+ Namespace: true,
+ Sync: query.Sync,
+ }
+
+ pss, err := shared.GetPsContainerOutput(runtime, opts, query.Filter, 2)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusOK, pss)
+}
+
+func GetContainer(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Size bool `schema:"size"`
+ }{
+ // override any golang type defaults
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ container, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+ data, err := container.Inspect(query.Size)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, data)
+}
+
+func KillContainer(w http.ResponseWriter, r *http.Request) {
+ // /{version}/containers/(name)/kill
+ _, err := utils.KillContainer(w, r)
+ if err != nil {
+ return
+ }
+ // Success
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func WaitContainer(w http.ResponseWriter, r *http.Request) {
+ _, err := utils.WaitContainer(w, r)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func PruneContainers(w http.ResponseWriter, r *http.Request) {
+ // TODO Needs rebase to get filers; Also would be handy to define
+ // an actual libpod container prune method.
+ // force
+ // filters
+}
+
+func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
+ // follow
+ // since
+ // timestamps
+ // tail string
+}
+func StatsContainer(w http.ResponseWriter, r *http.Request) {
+ //stream
+}
+func CreateContainer(w http.ResponseWriter, r *http.Request) {
+
+}
+
+func MountContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ conn, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+ m, err := conn.Mount()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusOK, m)
+}
+
+func ShowMountedContainers(w http.ResponseWriter, r *http.Request) {
+ response := make(map[string]string)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ conns, err := runtime.GetAllContainers()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ for _, conn := range conns {
+ mounted, mountPoint, err := conn.Mounted()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ if !mounted {
+ continue
+ }
+ response[conn.ID()] = mountPoint
+ }
+ utils.WriteResponse(w, http.StatusOK, response)
+}
diff --git a/pkg/api/handlers/libpod/healthcheck.go b/pkg/api/handlers/libpod/healthcheck.go
new file mode 100644
index 000000000..0d7bf3ea7
--- /dev/null
+++ b/pkg/api/handlers/libpod/healthcheck.go
@@ -0,0 +1,25 @@
+package libpod
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/mux"
+)
+
+func RunHealthCheck(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 404 no such
+ // 500 internal
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ status, err := runtime.HealthCheck(name)
+ if err != nil {
+ if status == libpod.HealthCheckContainerNotFound {
+ utils.ContainerNotFound(w, name, err)
+ }
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusOK, status)
+}
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
new file mode 100644
index 000000000..0d4e220a8
--- /dev/null
+++ b/pkg/api/handlers/libpod/images.go
@@ -0,0 +1,165 @@
+package libpod
+
+import (
+ "io/ioutil"
+ "net/http"
+ "os"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+// Commit
+// author string
+// "container"
+// repo string
+// tag string
+// message
+// pause bool
+// changes []string
+
+// create
+
+func ImageExists(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 404 no such
+ // 500 internal
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+
+ _, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name))
+ return
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func ImageTree(w http.ResponseWriter, r *http.Request) {
+ // tree is a bit of a mess ... logic is in adapter and therefore not callable from here. needs rework
+
+ //name := mux.Vars(r)["name"]
+ //_, layerInfoMap, _, err := s.Runtime.Tree(name)
+ //if err != nil {
+ // Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to find image information for %q", name))
+ // return
+ //}
+ // it is not clear to me how to deal with this given all the processing of the image
+ // is in main. we need to discuss how that really should be and return something useful.
+ handlers.UnsupportedHandler(w, r)
+}
+
+func GetImage(w http.ResponseWriter, r *http.Request) {
+ name := mux.Vars(r)["name"]
+ newImage, err := handlers.GetImage(r, name)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name))
+ return
+ }
+ inspect, err := newImage.Inspect(r.Context())
+ if err != nil {
+ utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "failed in inspect image %s", inspect.ID))
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, inspect)
+
+}
+func GetImages(w http.ResponseWriter, r *http.Request) {
+ images, err := utils.GetImages(w, r)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images"))
+ return
+ }
+ var summaries = make([]*handlers.ImageSummary, len(images))
+ for j, img := range images {
+ is, err := handlers.ImageToImageSummary(img)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed transform image summaries"))
+ return
+ }
+ // libpod has additional fields that we need to populate.
+ is.CreatedTime = img.Created()
+ is.ReadOnly = img.IsReadOnly()
+ summaries[j] = is
+ }
+ utils.WriteResponse(w, http.StatusOK, summaries)
+}
+
+func PruneImages(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 500 internal
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ All bool `schema:"all"`
+ Filters []string `schema:"filters"`
+ }{
+ // override any golang type defaults
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ cids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, query.Filters)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, cids)
+}
+
+func ExportImage(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Compress bool `schema:"compress"`
+ Format string `schema:"format"`
+ }{
+ // override any golang type defaults
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ if len(query.Format) < 1 {
+ utils.InternalServerError(w, errors.New("format parameter cannot be empty."))
+ return
+ }
+
+ tmpfile, err := ioutil.TempFile("", "api.tar")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
+ return
+ }
+ if err := tmpfile.Close(); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
+ return
+ }
+ name := mux.Vars(r)["name"]
+ newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.ImageNotFound(w, name, err)
+ return
+ }
+ if err := newImage.Save(r.Context(), name, query.Format, tmpfile.Name(), []string{}, false, query.Compress); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
+ return
+ }
+ rdr, err := os.Open(tmpfile.Name())
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile"))
+ return
+ }
+ defer rdr.Close()
+ defer os.Remove(tmpfile.Name())
+ utils.WriteResponse(w, http.StatusOK, rdr)
+}
diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go
new file mode 100644
index 000000000..daaf9d018
--- /dev/null
+++ b/pkg/api/handlers/libpod/pods.go
@@ -0,0 +1,440 @@
+package libpod
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/cmd/podman/shared/parse"
+ "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/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func PodCreate(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 500 internal
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ options []libpod.PodCreateOption
+ err error
+ )
+ labels := make(map[string]string)
+ input := handlers.PodCreateConfig{}
+ if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
+ return
+ }
+ if len(input.InfraCommand) > 0 || len(input.InfraImage) > 0 {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError,
+ errors.New("infra-command and infra-image are not implemented yet"))
+ return
+ }
+ // TODO long term we should break the following out of adapter and into libpod proper
+ // so that the cli and api can share the creation of a pod with the same options
+ if len(input.CGroupParent) > 0 {
+ options = append(options, libpod.WithPodCgroupParent(input.CGroupParent))
+ }
+
+ if len(input.Labels) > 0 {
+ if err := parse.ReadKVStrings(labels, []string{}, input.Labels); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+ }
+
+ if len(labels) != 0 {
+ options = append(options, libpod.WithPodLabels(labels))
+ }
+
+ if len(input.Name) > 0 {
+ options = append(options, libpod.WithPodName(input.Name))
+ }
+
+ if len(input.Hostname) > 0 {
+ options = append(options, libpod.WithPodHostname(input.Hostname))
+ }
+
+ if input.Infra {
+ // TODO infra-image and infra-command are not supported in the libpod API yet. Will fix
+ // when implemented in libpod
+ options = append(options, libpod.WithInfraContainer())
+ sharedNamespaces := shared.DefaultKernelNamespaces
+ if len(input.Share) > 0 {
+ sharedNamespaces = input.Share
+ }
+ nsOptions, err := shared.GetNamespaceOptions(strings.Split(sharedNamespaces, ","))
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+ options = append(options, nsOptions...)
+ }
+
+ if len(input.Publish) > 0 {
+ portBindings, err := shared.CreatePortBindings(input.Publish)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+ options = append(options, libpod.WithInfraContainerPorts(portBindings))
+
+ }
+ // always have containers use pod cgroups
+ // User Opt out is not yet supported
+ options = append(options, libpod.WithPodCgroups())
+
+ pod, err := runtime.NewPod(r.Context(), options...)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusCreated, handlers.IDResponse{ID: pod.CgroupParent()})
+}
+
+func Pods(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 500 internal
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ podInspectData []*libpod.PodInspect
+ )
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ filters []string `schema:"filters"`
+ }{
+ // override any golang type defaults
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ if len(query.filters) > 0 {
+ utils.Error(w, "filters are not implemented yet", http.StatusInternalServerError, define.ErrNotImplemented)
+ return
+ }
+ pods, err := runtime.GetAllPods()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ for _, pod := range pods {
+ data, err := pod.Inspect()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ podInspectData = append(podInspectData, data)
+ }
+ utils.WriteResponse(w, http.StatusOK, podInspectData)
+}
+
+func PodInspect(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ pod, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+ podData, err := pod.Inspect()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, podData)
+}
+
+func PodStop(w http.ResponseWriter, r *http.Request) {
+ // 200
+ // 304 not modified
+ // 404 no such
+ // 500 internal
+ var (
+ stopError error
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ )
+ query := struct {
+ timeout int `schema:"t"`
+ }{
+ // override any golang type defaults
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ allContainersStopped := true
+ name := mux.Vars(r)["name"]
+ pod, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+
+ // TODO we need to implement a pod.State/Status in libpod internal so libpod api
+ // users dont have to run through all containers.
+ podContainers, err := pod.AllContainers()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+
+ for _, con := range podContainers {
+ containerState, err := con.State()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ if containerState == define.ContainerStateRunning {
+ allContainersStopped = false
+ break
+ }
+ }
+ if allContainersStopped {
+ alreadyStopped := errors.Errorf("pod %s is already stopped", pod.ID())
+ utils.Error(w, "Something went wrong", http.StatusNotModified, alreadyStopped)
+ return
+ }
+
+ if query.timeout > 0 {
+ _, stopError = pod.StopWithTimeout(r.Context(), false, query.timeout)
+ } else {
+ _, stopError = pod.Stop(r.Context(), false)
+ }
+ if stopError != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, "")
+}
+
+func PodStart(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ allContainersRunning := true
+ name := mux.Vars(r)["name"]
+ pod, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+
+ // TODO we need to implement a pod.State/Status in libpod internal so libpod api
+ // users dont have to run through all containers.
+ podContainers, err := pod.AllContainers()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+
+ for _, con := range podContainers {
+ containerState, err := con.State()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ if containerState != define.ContainerStateRunning {
+ allContainersRunning = false
+ break
+ }
+ }
+ if allContainersRunning {
+ alreadyRunning := errors.Errorf("pod %s is already running", pod.ID())
+ utils.Error(w, "Something went wrong", http.StatusNotModified, alreadyRunning)
+ return
+ }
+ if _, err := pod.Start(r.Context()); err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, "")
+}
+
+func PodDelete(w http.ResponseWriter, r *http.Request) {
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ )
+ query := struct {
+ force bool `schema:"force"`
+ }{
+ // override any golang type defaults
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ name := mux.Vars(r)["name"]
+ pod, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+ if err := runtime.RemovePod(r.Context(), pod, true, query.force); err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func PodRestart(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ pod, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+ _, err = pod.Restart(r.Context())
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, "")
+}
+
+func PodPrune(w http.ResponseWriter, r *http.Request) {
+ var (
+ err error
+ pods []*libpod.Pod
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ )
+ query := struct {
+ force bool `schema:"force"`
+ }{
+ // override any golang type defaults
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ if query.force {
+ pods, err = runtime.GetAllPods()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ } else {
+ // TODO We need to make a libpod.PruneVolumes or this code will be a mess. Volumes
+ // already does this right. It will also help clean this code path up with less
+ // conditionals. We do this when we integrate with libpod again.
+ utils.Error(w, "not implemented", http.StatusInternalServerError, errors.New("not implemented"))
+ return
+ }
+ for _, p := range pods {
+ if err := runtime.RemovePod(r.Context(), p, true, query.force); err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func PodPause(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ pod, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+ _, err = pod.Pause()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func PodUnpause(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 404 no such
+ // 500 internal
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ pod, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+ _, err = pod.Unpause()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, "")
+}
+
+func PodKill(w http.ResponseWriter, r *http.Request) {
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ )
+ query := struct {
+ signal int `schema:"signal"`
+ }{
+ // override any golang type defaults
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ name := mux.Vars(r)["name"]
+ pod, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+ podStates, err := pod.Status()
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+ hasRunning := false
+ for _, s := range podStates {
+ if s == define.ContainerStateRunning {
+ hasRunning = true
+ break
+ }
+ }
+ if !hasRunning {
+ msg := fmt.Sprintf("Container %s is not running", pod.ID())
+ utils.Error(w, msg, http.StatusConflict, errors.Errorf("cannot kill a pod with no running containers: %s", pod.ID()))
+ return
+ }
+ // TODO How do we differentiate if a signal was sent vs accepting the pod/container default?
+ _, err = pod.Kill(uint(query.signal))
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, "")
+}
+
+func PodExists(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ _, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, "")
+}
diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go
new file mode 100644
index 000000000..3e0e597c6
--- /dev/null
+++ b/pkg/api/handlers/libpod/volumes.go
@@ -0,0 +1,144 @@
+package libpod
+
+import (
+ "encoding/json"
+ "net/http"
+
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ log "github.com/sirupsen/logrus"
+)
+
+func CreateVolume(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 500 internal
+ var (
+ volumeOptions []libpod.VolumeCreateOption
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ )
+ query := struct {
+ }{
+ // override any golang type defaults
+ }
+ input := handlers.VolumeCreateConfig{}
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ // decode params from body
+ if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
+ return
+ }
+
+ if len(input.Name) > 0 {
+ volumeOptions = append(volumeOptions, libpod.WithVolumeName(input.Name))
+ }
+ if len(input.Driver) > 0 {
+ volumeOptions = append(volumeOptions, libpod.WithVolumeDriver(input.Driver))
+ }
+ if len(input.Label) > 0 {
+ volumeOptions = append(volumeOptions, libpod.WithVolumeLabels(input.Label))
+ }
+ if len(input.Opts) > 0 {
+ parsedOptions, err := shared.ParseVolumeOptions(input.Opts)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ volumeOptions = append(volumeOptions, parsedOptions...)
+ }
+ vol, err := runtime.NewVolume(r.Context(), volumeOptions...)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusOK, vol.Name())
+}
+
+func InspectVolume(w http.ResponseWriter, r *http.Request) {
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ )
+ name := mux.Vars(r)["name"]
+ vol, err := runtime.GetVolume(name)
+ if err != nil {
+ utils.VolumeNotFound(w, name, err)
+ }
+ inspect, err := vol.Inspect()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusOK, inspect)
+}
+
+func ListVolumes(w http.ResponseWriter, r *http.Request) {
+ //var (
+ // runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ // decoder = r.Context().Value("decoder").(*schema.Decoder)
+ //)
+ //query := struct {
+ // Filter string `json:"filter"`
+ //}{
+ // // override any golang type defaults
+ //}
+ //
+ //if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ // utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ // errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ // return
+ //}
+ /*
+ This is all in main in cmd and needs to be extracted from there first.
+ */
+
+}
+
+func PruneVolumes(w http.ResponseWriter, r *http.Request) {
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ )
+ pruned, errs := runtime.PruneVolumes(r.Context())
+ if errs != nil {
+ if len(errs) > 1 {
+ for _, err := range errs {
+ log.Infof("Request Failed(%s): %s", http.StatusText(http.StatusInternalServerError), err.Error())
+ }
+ }
+ utils.InternalServerError(w, errs[len(errs)-1])
+ }
+ utils.WriteResponse(w, http.StatusOK, pruned)
+}
+
+func RemoveVolume(w http.ResponseWriter, r *http.Request) {
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ )
+ query := struct {
+ Force bool `schema:"force"`
+ }{
+ // override any golang type defaults
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ name := mux.Vars(r)["name"]
+ vol, err := runtime.LookupVolume(name)
+ if err != nil {
+ utils.VolumeNotFound(w, name, err)
+ }
+ if err := runtime.RemoveVolume(r.Context(), vol, query.Force); err != nil {
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
diff --git a/pkg/api/handlers/swagger.go b/pkg/api/handlers/swagger.go
new file mode 100644
index 000000000..b677a5a0b
--- /dev/null
+++ b/pkg/api/handlers/swagger.go
@@ -0,0 +1,126 @@
+package handlers
+
+import (
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/inspect"
+ "github.com/docker/docker/api/types"
+)
+
+// History response
+// swagger:response DocsHistory
+type swagHistory struct {
+ // in:body
+ Body struct {
+ HistoryResponse
+ }
+}
+
+// Inspect response
+// swagger:response DocsImageInspect
+type swagImageInspect struct {
+ // in:body
+ Body struct {
+ ImageInspect
+ }
+}
+
+// Delete response
+// swagger:response DocsImageDeleteResponse
+type swagImageDeleteResponse struct {
+ // in:body
+ Body struct {
+ image.ImageDeleteResponse
+ }
+}
+
+// Search results
+// swagger:response DocsSearchResponse
+type swagSearchResponse struct {
+ // in:body
+ Body struct {
+ image.SearchResult
+ }
+}
+
+// Inspect image
+// swagger:response DocsLibpodInspectImageResponse
+type swagLibpodInspectImageResponse struct {
+ // in:body
+ Body struct {
+ inspect.ImageData
+ }
+}
+
+// Prune containers
+// swagger:response DocsContainerPruneReport
+type swagContainerPruneReport struct {
+ // in: body
+ Body struct {
+ ContainersPruneReport
+ }
+}
+
+// Inspect container
+// swagger:response DocsContainerInspectResponse
+type swagContainerInspectResponse struct {
+ // in:body
+ Body struct {
+ types.ContainerJSON
+ }
+}
+
+// List processes in container
+// swagger:response DockerTopResponse
+type swagDockerTopResponse struct {
+ // in:body
+ Body struct {
+ ContainerTopOKBody
+ }
+}
+
+// List containers
+// swagger:response LibpodListContainersResponse
+type swagLibpodListContainersResponse struct {
+ // in:body
+ Body struct {
+ shared.PsContainerOutput
+ }
+}
+
+// Inspect container
+// swagger:response LibpodInspectContainerResponse
+type swagLibpodInspectContainerResponse struct {
+ // in:body
+ Body struct {
+ libpod.InspectContainerData
+ }
+}
+
+// List pods
+// swagger:response ListPodsResponse
+type swagListPodsResponse struct {
+ // in:body
+ Body struct {
+ libpod.PodInspect
+ }
+}
+
+// Inspect pod
+// swagger:response InspectPodResponse
+type swagInspectPodResponse struct {
+ // in:body
+ Body struct {
+ libpod.PodInspect
+ }
+}
+
+// Inspect volume
+// swagger:response InspectVolumeResponse
+type swagInspectVolumeResponse struct {
+ // in:body
+ Body struct {
+ libpod.InspectVolumeData
+ }
+}
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
new file mode 100644
index 000000000..9edbbdccc
--- /dev/null
+++ b/pkg/api/handlers/types.go
@@ -0,0 +1,534 @@
+package handlers
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/containers/image/v5/manifest"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/libpod/events"
+ libpodImage "github.com/containers/libpod/libpod/image"
+ docker "github.com/docker/docker/api/types"
+ dockerContainer "github.com/docker/docker/api/types/container"
+ dockerEvents "github.com/docker/docker/api/types/events"
+ dockerNetwork "github.com/docker/docker/api/types/network"
+ "github.com/docker/go-connections/nat"
+ "github.com/pkg/errors"
+)
+
+type AuthConfig struct {
+ docker.AuthConfig
+}
+
+type ImageInspect struct {
+ docker.ImageInspect
+}
+
+type ContainerConfig struct {
+ dockerContainer.Config
+}
+
+type ImageSummary struct {
+ docker.ImageSummary
+ CreatedTime time.Time `json:"CreatedTime,omitempty"`
+ ReadOnly bool `json:"ReadOnly,omitempty"`
+}
+
+type ContainersPruneReport struct {
+ docker.ContainersPruneReport
+}
+
+type Info struct {
+ docker.Info
+ BuildahVersion string
+ CPURealtimePeriod bool
+ CPURealtimeRuntime bool
+ CgroupVersion string
+ Rootless bool
+ SwapFree int64
+ SwapTotal int64
+ Uptime string
+}
+
+type Container struct {
+ docker.Container
+ docker.ContainerCreateConfig
+}
+
+type ContainerStats struct {
+ docker.ContainerStats
+}
+
+type Ping struct {
+ docker.Ping
+}
+
+type Version struct {
+ docker.Version
+}
+
+type DiskUsage struct {
+ docker.DiskUsage
+}
+
+type VolumesPruneReport struct {
+ docker.VolumesPruneReport
+}
+
+type ImagesPruneReport struct {
+ docker.ImagesPruneReport
+}
+
+type BuildCachePruneReport struct {
+ docker.BuildCachePruneReport
+}
+
+type NetworkPruneReport struct {
+ docker.NetworksPruneReport
+}
+
+type ConfigCreateResponse struct {
+ docker.ConfigCreateResponse
+}
+
+type PushResult struct {
+ docker.PushResult
+}
+
+type BuildResult struct {
+ docker.BuildResult
+}
+
+type ContainerWaitOKBody struct {
+ StatusCode int
+ Error struct {
+ Message string
+ }
+}
+
+type CreateContainerConfig struct {
+ Name string
+ dockerContainer.Config
+ HostConfig dockerContainer.HostConfig
+ NetworkingConfig dockerNetwork.NetworkingConfig
+}
+
+type VolumeCreateConfig struct {
+ Name string `json:"name"`
+ Driver string `schema:"driver"`
+ Label map[string]string `schema:"label"`
+ Opts map[string]string `schema:"opts"`
+}
+
+type IDResponse struct {
+ ID string `json:"id"`
+}
+
+type Stats struct {
+ docker.StatsJSON
+}
+
+type ContainerTopOKBody struct {
+ dockerContainer.ContainerTopOKBody
+ ID string `json:"Id"`
+}
+
+type PodCreateConfig struct {
+ Name string `json:"name"`
+ CGroupParent string `json:"cgroup-parent"`
+ Hostname string `json:"hostname"`
+ Infra bool `json:"infra"`
+ InfraCommand string `json:"infra-command"`
+ InfraImage string `json:"infra-image"`
+ Labels []string `json:"labels"`
+ Publish []string `json:"publish"`
+ Share string `json:"share"`
+}
+
+type ErrorModel struct {
+ Message string `json:"message"`
+}
+
+type Event struct {
+ dockerEvents.Message
+}
+
+type HistoryResponse struct {
+ ID string `json:"Id"`
+ Created int64 `json:"Created"`
+ CreatedBy string `json:"CreatedBy"`
+ Tags []string `json:"Tags"`
+ Size int64 `json:"Size"`
+ Comment string `json:"Comment"`
+}
+
+type ImageLayer struct{}
+
+type ImageTreeResponse struct {
+ ID string `json:"id"`
+ Tags []string `json:"tags"`
+ Size string `json:"size"`
+ Layers []ImageLayer `json:"layers"`
+}
+
+func EventToApiEvent(e *events.Event) *Event {
+ return &Event{dockerEvents.Message{
+ Type: e.Type.String(),
+ Action: e.Status.String(),
+ Actor: dockerEvents.Actor{
+ ID: e.ID,
+ Attributes: map[string]string{
+ "image": e.Image,
+ "name": e.Name,
+ "containerExitCode": strconv.Itoa(e.ContainerExitCode),
+ },
+ },
+ Scope: "local",
+ Time: e.Time.Unix(),
+ TimeNano: e.Time.UnixNano(),
+ }}
+}
+
+func ImageToImageSummary(l *libpodImage.Image) (*ImageSummary, error) {
+ containers, err := l.Containers()
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed to obtain Containers for image %s", l.ID())
+ }
+ containerCount := len(containers)
+
+ var digests []string
+ for _, d := range l.Digests() {
+ digests = append(digests, string(d))
+ }
+
+ tags, err := l.RepoTags()
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed to obtain RepoTags for image %s", l.ID())
+ }
+
+ // FIXME: GetParent() panics
+ // parent, err := l.GetParent(context.TODO())
+ // if err != nil {
+ // return nil, errors.Wrapf(err, "Failed to obtain ParentID for image %s", l.ID())
+ // }
+
+ labels, err := l.Labels(context.TODO())
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed to obtain Labels for image %s", l.ID())
+ }
+
+ size, err := l.Size(context.TODO())
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed to obtain Size for image %s", l.ID())
+ }
+ dockerSummary := docker.ImageSummary{
+ Containers: int64(containerCount),
+ Created: l.Created().Unix(),
+ ID: l.ID(),
+ Labels: labels,
+ ParentID: l.Parent,
+ RepoDigests: digests,
+ RepoTags: tags,
+ SharedSize: 0,
+ Size: int64(*size),
+ VirtualSize: int64(*size),
+ }
+ is := ImageSummary{
+ ImageSummary: dockerSummary,
+ }
+ return &is, nil
+}
+
+func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageInspect, error) {
+ info, err := l.Inspect(context.Background())
+ if err != nil {
+ return nil, err
+ }
+ ports, err := portsToPortSet(info.Config.ExposedPorts)
+ if err != nil {
+ return nil, err
+ }
+ // TODO the rest of these still need wiring!
+ config := dockerContainer.Config{
+ // Hostname: "",
+ // Domainname: "",
+ User: info.User,
+ // AttachStdin: false,
+ // AttachStdout: false,
+ // AttachStderr: false,
+ ExposedPorts: ports,
+ // Tty: false,
+ // OpenStdin: false,
+ // StdinOnce: false,
+ Env: info.Config.Env,
+ Cmd: info.Config.Cmd,
+ // Healthcheck: nil,
+ // ArgsEscaped: false,
+ // Image: "",
+ // Volumes: nil,
+ // WorkingDir: "",
+ // Entrypoint: nil,
+ // NetworkDisabled: false,
+ // MacAddress: "",
+ // OnBuild: nil,
+ // Labels: nil,
+ // StopSignal: "",
+ // StopTimeout: nil,
+ // Shell: nil,
+ }
+ ic, err := l.ToImageRef(ctx)
+ if err != nil {
+ return nil, err
+ }
+ dockerImageInspect := docker.ImageInspect{
+ Architecture: l.Architecture,
+ Author: l.Author,
+ Comment: info.Comment,
+ Config: &config,
+ Created: l.Created().Format(time.RFC3339Nano),
+ DockerVersion: "",
+ GraphDriver: docker.GraphDriverData{},
+ ID: fmt.Sprintf("sha256:%s", l.ID()),
+ Metadata: docker.ImageMetadata{},
+ Os: l.Os,
+ OsVersion: l.Version,
+ Parent: l.Parent,
+ RepoDigests: info.RepoDigests,
+ RepoTags: info.RepoTags,
+ RootFS: docker.RootFS{},
+ Size: info.Size,
+ Variant: "",
+ VirtualSize: info.VirtualSize,
+ }
+ bi := ic.ConfigInfo()
+ // For docker images, we need to get the Container id and config
+ // and populate the image with it.
+ if bi.MediaType == manifest.DockerV2Schema2ConfigMediaType {
+ d := manifest.Schema2Image{}
+ b, err := ic.ConfigBlob(ctx)
+ if err != nil {
+ return nil, err
+ }
+ if err := json.Unmarshal(b, &d); err != nil {
+ return nil, err
+ }
+ // populate the Container id into the image
+ dockerImageInspect.Container = d.Container
+ containerConfig := dockerContainer.Config{}
+ configBytes, err := json.Marshal(d.ContainerConfig)
+ if err != nil {
+ return nil, err
+ }
+ if err := json.Unmarshal(configBytes, &containerConfig); err != nil {
+ return nil, err
+ }
+ // populate the Container config in the image
+ dockerImageInspect.ContainerConfig = &containerConfig
+ // populate parent
+ dockerImageInspect.Parent = d.Parent.String()
+ }
+ return &ImageInspect{dockerImageInspect}, nil
+
+}
+
+func LibpodToContainer(l *libpod.Container, infoData []define.InfoData) (*Container, error) {
+ imageId, imageName := l.Image()
+ sizeRW, err := l.RWSize()
+ if err != nil {
+ return nil, err
+ }
+
+ SizeRootFs, err := l.RootFsSize()
+ if err != nil {
+ return nil, err
+ }
+
+ state, err := l.State()
+ if err != nil {
+ return nil, err
+ }
+
+ return &Container{docker.Container{
+ ID: l.ID(),
+ Names: []string{l.Name()},
+ Image: imageName,
+ ImageID: imageId,
+ Command: strings.Join(l.Command(), " "),
+ Created: l.CreatedTime().Unix(),
+ Ports: nil,
+ SizeRw: sizeRW,
+ SizeRootFs: SizeRootFs,
+ Labels: l.Labels(),
+ State: string(state),
+ Status: "",
+ HostConfig: struct {
+ NetworkMode string `json:",omitempty"`
+ }{
+ "host"},
+ NetworkSettings: nil,
+ Mounts: nil,
+ },
+ docker.ContainerCreateConfig{},
+ }, nil
+}
+
+func LibpodToContainerJSON(l *libpod.Container) (*docker.ContainerJSON, error) {
+ _, imageName := l.Image()
+ inspect, err := l.Inspect(true)
+ if err != nil {
+ return nil, err
+ }
+ i, err := json.Marshal(inspect.State)
+ if err != nil {
+ return nil, err
+ }
+ state := docker.ContainerState{}
+ if err := json.Unmarshal(i, &state); err != nil {
+ return nil, err
+ }
+
+ // docker considers paused to be running
+ if state.Paused {
+ state.Running = true
+ }
+
+ h, err := json.Marshal(inspect.HostConfig)
+ if err != nil {
+ return nil, err
+ }
+ hc := dockerContainer.HostConfig{}
+ if err := json.Unmarshal(h, &hc); err != nil {
+ return nil, err
+ }
+ g, err := json.Marshal(inspect.GraphDriver)
+ if err != nil {
+ return nil, err
+ }
+ graphDriver := docker.GraphDriverData{}
+ if err := json.Unmarshal(g, &graphDriver); err != nil {
+ return nil, err
+ }
+
+ cb := docker.ContainerJSONBase{
+ ID: l.ID(),
+ Created: l.CreatedTime().String(),
+ Path: "",
+ Args: nil,
+ State: &state,
+ Image: imageName,
+ ResolvConfPath: inspect.ResolvConfPath,
+ HostnamePath: inspect.HostnamePath,
+ HostsPath: inspect.HostsPath,
+ LogPath: l.LogPath(),
+ Node: nil,
+ Name: l.Name(),
+ RestartCount: 0,
+ Driver: inspect.Driver,
+ Platform: "linux",
+ MountLabel: inspect.MountLabel,
+ ProcessLabel: inspect.ProcessLabel,
+ AppArmorProfile: inspect.AppArmorProfile,
+ ExecIDs: inspect.ExecIDs,
+ HostConfig: &hc,
+ GraphDriver: graphDriver,
+ SizeRw: inspect.SizeRw,
+ SizeRootFs: &inspect.SizeRootFs,
+ }
+
+ stopTimeout := int(l.StopTimeout())
+
+ ports := make(nat.PortSet)
+ for p := range inspect.HostConfig.PortBindings {
+ splitp := strings.Split(p, "/")
+ port, err := nat.NewPort(splitp[0], splitp[1])
+ if err != nil {
+ return nil, err
+ }
+ ports[port] = struct{}{}
+ }
+
+ config := dockerContainer.Config{
+ Hostname: l.Hostname(),
+ Domainname: inspect.Config.DomainName,
+ User: l.User(),
+ AttachStdin: inspect.Config.AttachStdin,
+ AttachStdout: inspect.Config.AttachStdout,
+ AttachStderr: inspect.Config.AttachStderr,
+ ExposedPorts: ports,
+ Tty: inspect.Config.Tty,
+ OpenStdin: inspect.Config.OpenStdin,
+ StdinOnce: inspect.Config.StdinOnce,
+ Env: inspect.Config.Env,
+ Cmd: inspect.Config.Cmd,
+ Healthcheck: nil,
+ ArgsEscaped: false,
+ Image: imageName,
+ Volumes: nil,
+ WorkingDir: l.WorkingDir(),
+ Entrypoint: l.Entrypoint(),
+ NetworkDisabled: false,
+ MacAddress: "",
+ OnBuild: nil,
+ Labels: l.Labels(),
+ StopSignal: string(l.StopSignal()),
+ StopTimeout: &stopTimeout,
+ Shell: nil,
+ }
+
+ m, err := json.Marshal(inspect.Mounts)
+ if err != nil {
+ return nil, err
+ }
+ mounts := []docker.MountPoint{}
+ if err := json.Unmarshal(m, &mounts); err != nil {
+ return nil, err
+ }
+
+ networkSettingsDefault := docker.DefaultNetworkSettings{
+ EndpointID: "",
+ Gateway: "",
+ GlobalIPv6Address: "",
+ GlobalIPv6PrefixLen: 0,
+ IPAddress: "",
+ IPPrefixLen: 0,
+ IPv6Gateway: "",
+ MacAddress: l.Config().StaticMAC.String(),
+ }
+
+ networkSettings := docker.NetworkSettings{
+ NetworkSettingsBase: docker.NetworkSettingsBase{},
+ DefaultNetworkSettings: networkSettingsDefault,
+ Networks: nil,
+ }
+
+ c := docker.ContainerJSON{
+ ContainerJSONBase: &cb,
+ Mounts: mounts,
+ Config: &config,
+ NetworkSettings: &networkSettings,
+ }
+ return &c, nil
+}
+
+// portsToPortSet converts libpods exposed ports to dockers structs
+func portsToPortSet(input map[string]struct{}) (nat.PortSet, error) {
+ ports := make(nat.PortSet)
+ for k := range input {
+ npTCP, err := nat.NewPort("tcp", k)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to create tcp port from %s", k)
+ }
+ npUDP, err := nat.NewPort("udp", k)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to create udp port from %s", k)
+ }
+ ports[npTCP] = struct{}{}
+ ports[npUDP] = struct{}{}
+ }
+ return ports, nil
+}
diff --git a/pkg/api/handlers/unsupported.go b/pkg/api/handlers/unsupported.go
new file mode 100644
index 000000000..956d31f8b
--- /dev/null
+++ b/pkg/api/handlers/unsupported.go
@@ -0,0 +1,17 @@
+package handlers
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ log "github.com/sirupsen/logrus"
+)
+
+func UnsupportedHandler(w http.ResponseWriter, r *http.Request) {
+ msg := fmt.Sprintf("Path %s is not supported", r.URL.Path)
+ log.Infof("Request Failed: %s", msg)
+
+ utils.WriteJSON(w, http.StatusInternalServerError,
+ utils.ErrorModel{Message: msg})
+}
diff --git a/pkg/api/handlers/utils/containers.go b/pkg/api/handlers/utils/containers.go
new file mode 100644
index 000000000..64d3d378a
--- /dev/null
+++ b/pkg/api/handlers/utils/containers.go
@@ -0,0 +1,103 @@
+package utils
+
+import (
+ "fmt"
+ "net/http"
+ "syscall"
+ "time"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func KillContainer(w http.ResponseWriter, r *http.Request) (*libpod.Container, error) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decorder").(*schema.Decoder)
+ query := struct {
+ Signal syscall.Signal `schema:"signal"`
+ }{
+ Signal: syscall.SIGKILL,
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return nil, err
+ }
+ name := mux.Vars(r)["name"]
+ con, err := runtime.LookupContainer(name)
+ if err != nil {
+ ContainerNotFound(w, name, err)
+ return nil, err
+ }
+
+ state, err := con.State()
+ if err != nil {
+ InternalServerError(w, err)
+ return con, err
+ }
+
+ // If the Container is stopped already, send a 409
+ if state == define.ContainerStateStopped || state == define.ContainerStateExited {
+ Error(w, fmt.Sprintf("Container %s is not running", name), http.StatusConflict, errors.New(fmt.Sprintf("Cannot kill Container %s, it is not running", name)))
+ return con, err
+ }
+
+ err = con.Kill(uint(query.Signal))
+ if err != nil {
+ Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "unable to kill Container %s", name))
+ }
+ return con, err
+}
+
+func RemoveContainer(w http.ResponseWriter, r *http.Request, force, vols bool) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ con, err := runtime.LookupContainer(name)
+ if err != nil {
+ ContainerNotFound(w, name, err)
+ return
+ }
+
+ if err := runtime.RemoveContainer(r.Context(), con, force, vols); err != nil {
+ InternalServerError(w, err)
+ return
+ }
+ WriteResponse(w, http.StatusNoContent, "")
+}
+
+func WaitContainer(w http.ResponseWriter, r *http.Request) (int32, error) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ // /{version}/containers/(name)/restart
+ query := struct {
+ Interval string `schema:"interval"`
+ Condition string `schema:"condition"`
+ }{
+ // Override golang default values for types
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return 0, err
+ }
+
+ if len(query.Condition) > 0 {
+ return 0, errors.Errorf("the condition parameter is not supported")
+ }
+
+ name := mux.Vars(r)["name"]
+ con, err := runtime.LookupContainer(name)
+ if err != nil {
+ ContainerNotFound(w, name, err)
+ return 0, err
+ }
+ if len(query.Interval) > 0 {
+ d, err := time.ParseDuration(query.Interval)
+ if err != nil {
+ Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %s for interval", query.Interval))
+ }
+ return con.WaitWithInterval(d)
+ }
+ return con.Wait()
+}
diff --git a/pkg/api/handlers/utils/errors.go b/pkg/api/handlers/utils/errors.go
new file mode 100644
index 000000000..3ec0742bd
--- /dev/null
+++ b/pkg/api/handlers/utils/errors.go
@@ -0,0 +1,88 @@
+package utils
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/containers/libpod/libpod/define"
+ "github.com/pkg/errors"
+ log "github.com/sirupsen/logrus"
+)
+
+var (
+ ErrLinkNotSupport = errors.New("Link is not supported")
+)
+
+// Error formats an API response to an error
+//
+// apiMessage and code must match the container API, and are sent to client
+// err is logged on the system running the podman service
+func Error(w http.ResponseWriter, apiMessage string, code int, err error) {
+ // Log detailed message of what happened to machine running podman service
+ log.Infof("Request Failed(%s): %s", http.StatusText(code), err.Error())
+ em := ErrorModel{
+ Because: (errors.Cause(err)).Error(),
+ Message: err.Error(),
+ }
+ WriteJSON(w, code, em)
+}
+
+func VolumeNotFound(w http.ResponseWriter, nameOrId string, err error) {
+ if errors.Cause(err) != define.ErrNoSuchVolume {
+ InternalServerError(w, err)
+ }
+ msg := fmt.Sprintf("No such volume: %s", nameOrId)
+ Error(w, msg, http.StatusNotFound, err)
+}
+func ContainerNotFound(w http.ResponseWriter, nameOrId string, err error) {
+ if errors.Cause(err) != define.ErrNoSuchCtr {
+ InternalServerError(w, err)
+ }
+ msg := fmt.Sprintf("No such container: %s", nameOrId)
+ Error(w, msg, http.StatusNotFound, err)
+}
+
+func ImageNotFound(w http.ResponseWriter, nameOrId string, err error) {
+ if errors.Cause(err) != define.ErrNoSuchImage {
+ InternalServerError(w, err)
+ }
+ msg := fmt.Sprintf("No such image: %s", nameOrId)
+ Error(w, msg, http.StatusNotFound, err)
+}
+
+func PodNotFound(w http.ResponseWriter, nameOrId string, err error) {
+ if errors.Cause(err) != define.ErrNoSuchPod {
+ InternalServerError(w, err)
+ }
+ msg := fmt.Sprintf("No such pod: %s", nameOrId)
+ Error(w, msg, http.StatusNotFound, err)
+}
+
+func ContainerNotRunning(w http.ResponseWriter, containerID string, err error) {
+ msg := fmt.Sprintf("Container %s is not running", containerID)
+ Error(w, msg, http.StatusConflict, err)
+}
+
+func InternalServerError(w http.ResponseWriter, err error) {
+ Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError, err)
+}
+
+func BadRequest(w http.ResponseWriter, key string, value string, err error) {
+ e := errors.Wrapf(err, "Failed to parse query parameter '%s': %q", key, value)
+ Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, e)
+}
+
+type ErrorModel struct {
+ // root cause
+ Because string `json:"cause"`
+ // error message
+ Message string `json:"message"`
+}
+
+func (e ErrorModel) Error() string {
+ return e.Message
+}
+
+func (e ErrorModel) Cause() error {
+ return errors.New(e.Because)
+}
diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go
new file mode 100644
index 000000000..0815e6eca
--- /dev/null
+++ b/pkg/api/handlers/utils/handler.go
@@ -0,0 +1,44 @@
+package utils
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+
+ log "github.com/sirupsen/logrus"
+)
+
+// WriteResponse encodes the given value as JSON or string and renders it for http client
+func WriteResponse(w http.ResponseWriter, code int, value interface{}) {
+ switch value.(type) {
+ case string:
+ w.Header().Set("Content-Type", "text/plain; charset=us-ascii")
+ w.WriteHeader(code)
+
+ if _, err := fmt.Fprintln(w, value); err != nil {
+ log.Errorf("unable to send string response: %q", err)
+ }
+ case *os.File:
+ w.Header().Set("Content-Type", "application/octet; charset=us-ascii")
+ w.WriteHeader(code)
+
+ if _, err := io.Copy(w, value.(*os.File)); err != nil {
+ log.Errorf("unable to copy to response: %q", err)
+ }
+ default:
+ WriteJSON(w, code, value)
+ }
+}
+
+func WriteJSON(w http.ResponseWriter, code int, value interface{}) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(code)
+
+ coder := json.NewEncoder(w)
+ coder.SetEscapeHTML(true)
+ if err := coder.Encode(value); err != nil {
+ log.Errorf("unable to write json: %q", err)
+ }
+}
diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go
new file mode 100644
index 000000000..9445298ca
--- /dev/null
+++ b/pkg/api/handlers/utils/images.go
@@ -0,0 +1,32 @@
+package utils
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/gorilla/schema"
+)
+
+// GetImages is a common function used to get images for libpod and other compatibility
+// mechanisms
+func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ query := struct {
+ //all bool # all is currently unused
+ filters []string
+ //digests bool # digests is currently unused
+ }{
+ // This is where you can override the golang default value for one of fields
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ return nil, err
+ }
+ filters := query.filters
+ if len(filters) < 1 {
+ filters = append(filters, fmt.Sprintf("reference=%s", ""))
+ }
+ return runtime.ImageRuntime().GetImagesWithFilters(filters)
+}
diff --git a/pkg/api/server/handler_api.go b/pkg/api/server/handler_api.go
new file mode 100644
index 000000000..4b93998ee
--- /dev/null
+++ b/pkg/api/server/handler_api.go
@@ -0,0 +1,37 @@
+package server
+
+import (
+ "context"
+ "net/http"
+
+ log "github.com/sirupsen/logrus"
+)
+
+// APIHandler is a wrapper to enhance HandlerFunc's and remove redundant code
+func APIHandler(ctx context.Context, h http.HandlerFunc) http.HandlerFunc {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ log.Debugf("APIHandler -- Method: %s URL: %s", r.Method, r.URL.String())
+ if err := r.ParseForm(); err != nil {
+ log.Infof("Failed Request: unable to parse form: %q", err)
+ }
+
+ // TODO: Use ConnContext when ported to go 1.13
+ c := context.WithValue(r.Context(), "decoder", ctx.Value("decoder"))
+ c = context.WithValue(c, "runtime", ctx.Value("runtime"))
+ c = context.WithValue(c, "shutdownFunc", ctx.Value("shutdownFunc"))
+ r = r.WithContext(c)
+
+ h(w, r)
+
+ shutdownFunc := r.Context().Value("shutdownFunc").(func() error)
+ if err := shutdownFunc(); err != nil {
+ log.Errorf("Failed to shutdown Server in APIHandler(): %s", err.Error())
+ }
+ })
+}
+
+// VersionedPath prepends the version parsing code
+// any handler may override this default when registering URL(s)
+func VersionedPath(p string) string {
+ return "/v{version:[0-9][0-9.]*}" + p
+}
diff --git a/pkg/api/server/register_auth.go b/pkg/api/server/register_auth.go
new file mode 100644
index 000000000..9f312683d
--- /dev/null
+++ b/pkg/api/server/register_auth.go
@@ -0,0 +1,11 @@
+package server
+
+import (
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) RegisterAuthHandlers(r *mux.Router) error {
+ r.Handle(VersionedPath("/auth"), APIHandler(s.Context, handlers.UnsupportedHandler))
+ return nil
+}
diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go
new file mode 100644
index 000000000..af1881624
--- /dev/null
+++ b/pkg/api/server/register_containers.go
@@ -0,0 +1,729 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/generic"
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error {
+ // swagger:operation POST /containers/create containers createContainer
+ //
+ // Create a container
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: query
+ // name: name
+ // type: string
+ // description: container name
+ // responses:
+ // '201':
+ // description: tbd
+ // '400':
+ // "$ref": "#/responses/BadParamError"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '409':
+ // "$ref": "#/types/ConflictError"
+ // '500':
+ // "$ref": "#/types/InternalError"
+ r.HandleFunc(VersionedPath("/containers/create"), APIHandler(s.Context, generic.CreateContainer)).Methods(http.MethodPost)
+ // swagger:operation GET /containers/json containers listContainers
+ //
+ // List containers
+ //
+ // ---
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // type: array
+ // items:
+ // "$ref": "#/responses/Container"
+ // '400':
+ // "$ref": "#/responses/BadParamError"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/json"), APIHandler(s.Context, generic.ListContainers)).Methods(http.MethodGet)
+ // swagger:operation POST /containers/prune containers pruneContainers
+ //
+ // Prune unused containers
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: filters
+ // type: map[string][]string
+ // description: something
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // "$ref": "#/responses/DocsContainerPruneReport"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/prune"), APIHandler(s.Context, generic.PruneContainers)).Methods(http.MethodPost)
+ // swagger:operation DELETE /containers/{nameOrID} containers removeContainer
+ //
+ // Delete container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: force
+ // type: bool
+ // description: need something
+ // - in: query
+ // name: v
+ // type: bool
+ // description: need something
+ // - in: query
+ // name: link
+ // type: bool
+ // description: not supported
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '400':
+ // "$ref": "#/responses/BadParamError"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '409':
+ // "$ref": "#/responses/ConflictError"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}"), APIHandler(s.Context, generic.RemoveContainer)).Methods(http.MethodDelete)
+ // swagger:operation GET /containers/{nameOrID}/json containers getContainer
+ //
+ // Inspect Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // "$ref": "#/responses/DocsContainerInspectResponse"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/json"), APIHandler(s.Context, generic.GetContainer)).Methods(http.MethodGet)
+ // swagger:operation POST /containers/{nameOrID}/kill containers killContainer
+ //
+ // Kill Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: signal
+ // type: int
+ // description: signal to be sent to container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '409':
+ // "$ref": "#/responses/ConflictError"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/kill"), APIHandler(s.Context, generic.KillContainer)).Methods(http.MethodPost)
+ // swagger:operation GET /containers/{nameOrID}/logs containers LogsFromContainer
+ //
+ // Get logs from container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: follow
+ // type: bool
+ // description: needs description
+ // - in: query
+ // name: stdout
+ // type: bool
+ // description: needs description
+ // - in: query
+ // name: stderr
+ // type: bool
+ // description: needs description
+ // - in: query
+ // name: since
+ // type: string
+ // description: needs description
+ // - in: query
+ // name: until
+ // type: string
+ // description: needs description
+ // - in: query
+ // name: timestamps
+ // type: bool
+ // description: needs description
+ // - in: query
+ // name: tail
+ // type: string
+ // description: needs description
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // description: tbd
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/logs"), APIHandler(s.Context, generic.LogsFromContainer)).Methods(http.MethodGet)
+ // swagger:operation POST /containers/{nameOrID}/pause containers pauseContainer
+ //
+ // Pause Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/pause"), APIHandler(s.Context, handlers.PauseContainer)).Methods(http.MethodPost)
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/rename"), APIHandler(s.Context, handlers.UnsupportedHandler)).Methods(http.MethodPost)
+ // swagger:operation POST /containers/{nameOrID}/restart containers restartContainer
+ //
+ // Restart Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: t
+ // type: int
+ // description: timeout before sending kill signal to container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/restart"), APIHandler(s.Context, handlers.RestartContainer)).Methods(http.MethodPost)
+ // swagger:operation POST /containers/{nameOrID}/start containers startContainer
+ //
+ // Start a container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: detachKeys
+ // type: string
+ // description: needs description
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '304':
+ // "$ref": "#/responses/ContainerAlreadyStartedError"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/start"), APIHandler(s.Context, handlers.StartContainer)).Methods(http.MethodPost)
+ // swagger:operation GET /containers/{nameOrID}/stats containers statsContainer
+ //
+ // Get stats for a container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: stream
+ // type: bool
+ // description: needs description
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: tbd
+ // '304':
+ // "$ref": "#/responses/ContainerAlreadyStartedError"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/stats"), APIHandler(s.Context, generic.StatsContainer)).Methods(http.MethodGet)
+ // swagger:operation POST /containers/{nameOrID}/stop containers stopContainer
+ //
+ // Stop a container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: t
+ // type: int
+ // description: number of seconds to wait before killing container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '304':
+ // "$ref": "#/responses/ContainerAlreadyStoppedError"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/stop"), APIHandler(s.Context, handlers.StopContainer)).Methods(http.MethodPost)
+ // swagger:operation GET /containers/{nameOrID}/top containers topContainer
+ //
+ // List processes running inside a container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: ps_args
+ // type: string
+ // description: arguments to pass to ps such as aux
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // "ref": "#/responses/DockerTopResponse"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/top"), APIHandler(s.Context, handlers.TopContainer)).Methods(http.MethodGet)
+ // swagger:operation POST /containers/{nameOrID}/unpause containers unpauseContainer
+ //
+ // Unpause Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // schema:
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/unpause"), APIHandler(s.Context, handlers.UnpauseContainer)).Methods(http.MethodPost)
+ // swagger:operation POST /containers/{nameOrID}/wait containers waitContainer
+ //
+ // Wait on a container to exit
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: condition
+ // type: string
+ // description: Wait until the container reaches the given condition
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/wait"), APIHandler(s.Context, generic.WaitContainer)).Methods(http.MethodPost)
+
+ /*
+ libpod endpoints
+ */
+
+ r.HandleFunc(VersionedPath("/libpod/containers/create"), APIHandler(s.Context, libpod.CreateContainer)).Methods(http.MethodPost)
+ // swagger:operation GET /libpod/containers/json containers listContainers
+ //
+ // List containers
+ //
+ // ---
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // type: array
+ // items:
+ // "$ref": "#/responses/LibpodListContainersResponse"
+ // '400':
+ // "$ref": "#/responses/BadParamError"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/json"), APIHandler(s.Context, libpod.ListContainers)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/containers/prune containers pruneContainers
+ //
+ // Prune unused containers
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: force
+ // type: bool
+ // description: something
+ // - in: query
+ // name: filters
+ // type: map[string][]string
+ // description: something
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // description: TBD
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/prune"), APIHandler(s.Context, libpod.PruneContainers)).Methods(http.MethodPost)
+ // swagger:operation GET /libpod/containers/showmounted containers showMounterContainers
+ //
+ // Show mounted containers
+ //
+ // ---
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // description: mounted containers
+ // schema:
+ // type: object
+ // additionalProperties:
+ // type: string
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/showmounted"), APIHandler(s.Context, libpod.ShowMountedContainers)).Methods(http.MethodGet)
+ // swagger:operation DELETE /libpod/containers/json containers removeContainer
+ //
+ // Delete container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: force
+ // type: bool
+ // description: need something
+ // - in: query
+ // name: v
+ // type: bool
+ // description: need something
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '400':
+ // "$ref": "#/responses/BadParamError"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '409':
+ // "$ref": "#/responses/ConflictError"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}"), APIHandler(s.Context, libpod.RemoveContainer)).Methods(http.MethodDelete)
+ // swagger:operation GET /libpod/containers/{nameOrID}/json containers getContainer
+ //
+ // Inspect Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: size
+ // type: bool
+ // description: display filesystem usage
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // "$ref": "#/responses/LibpodInspectContainerResponse"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/json"), APIHandler(s.Context, libpod.GetContainer)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/containers/{nameOrID}/kill containers killContainer
+ //
+ // Kill Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: signal
+ // type: int
+ // default: 15
+ // description: signal to be sent to container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '409':
+ // "$ref": "#/responses/ConflictError"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/kill"), APIHandler(s.Context, libpod.KillContainer)).Methods(http.MethodGet)
+ // swagger:operation GET /libpod/containers/{nameOrID}/mount containers mountContainer
+ //
+ // Mount a container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // description: mounted container
+ // schema:
+ // description: id
+ // type: string
+ // example: 3c784de79b791b4ebd3ac55e511f97fedc042328499554937a3f8bfd9c1a2cb8
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/mount"), APIHandler(s.Context, libpod.MountContainer)).Methods(http.MethodPost)
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/logs"), APIHandler(s.Context, libpod.LogsFromContainer)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/containers/{nameOrID}/pause containers pauseContainer
+ //
+ // Pause Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/pause"), APIHandler(s.Context, handlers.PauseContainer)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/containers/{nameOrID}/restart containers restartContainer
+ //
+ // Restart Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: t
+ // type: int
+ // description: timeout before sending kill signal to container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/restart"), APIHandler(s.Context, handlers.RestartContainer)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/containers/{nameOrID}/start containers startContainer
+ //
+ // Start a container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: detachKeys
+ // type: string
+ // description: needs description
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '304':
+ // "$ref": "#/responses/ContainerAlreadyStartedError"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/start"), APIHandler(s.Context, handlers.StartContainer)).Methods(http.MethodPost)
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/stats"), APIHandler(s.Context, libpod.StatsContainer)).Methods(http.MethodGet)
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/top"), APIHandler(s.Context, handlers.TopContainer)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/containers/{nameOrID}/unpause containers unpauseContainer
+ //
+ // Unpause Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/unpause"), APIHandler(s.Context, handlers.UnpauseContainer)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/containers/{nameOrID}/wait containers waitContainer
+ //
+ // Wait on a container to exit
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: condition
+ // type: string
+ // description: Wait until the container reaches the given condition
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/wait"), APIHandler(s.Context, libpod.WaitContainer)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/containers/{nameOrID}/exists containers containerExists
+ //
+ // Check if container exists
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: container exists
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/exists"), APIHandler(s.Context, libpod.ContainerExists)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/containers/{nameOrID}/stop containers stopContainer
+ //
+ // Stop a container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: t
+ // type: int
+ // description: number of seconds to wait before killing container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '304':
+ // "$ref": "#/responses/ContainerAlreadyStoppedError"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/stop"), APIHandler(s.Context, handlers.StopContainer)).Methods(http.MethodPost)
+ return nil
+}
diff --git a/pkg/api/server/register_distribution.go b/pkg/api/server/register_distribution.go
new file mode 100644
index 000000000..23820b4a7
--- /dev/null
+++ b/pkg/api/server/register_distribution.go
@@ -0,0 +1,11 @@
+package server
+
+import (
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) RegisterDistributionHandlers(r *mux.Router) error {
+ r.HandleFunc(VersionedPath("/distribution/{name:..*}/json"), handlers.UnsupportedHandler)
+ return nil
+}
diff --git a/pkg/api/server/register_events.go b/pkg/api/server/register_events.go
new file mode 100644
index 000000000..d764fdbb4
--- /dev/null
+++ b/pkg/api/server/register_events.go
@@ -0,0 +1,32 @@
+package server
+
+import (
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) RegisterEventsHandlers(r *mux.Router) error {
+ // swagger:operation GET /events system getEvents
+ // ---
+ // summary: Returns events filtered on query parameters
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: since
+ // in: query
+ // description: start streaming events from this time
+ // - name: until
+ // in: query
+ // description: stop streaming events later than this
+ // - name: filters
+ // in: query
+ // description: JSON encoded map[string][]string of constraints
+ // responses:
+ // "200":
+ // description: OK
+ // "500":
+ // description: Failed
+ // "$ref": "#/types/errorModel"
+ r.Handle(VersionedPath("/events"), APIHandler(s.Context, handlers.GetEvents))
+ return nil
+}
diff --git a/pkg/api/server/register_healthcheck.go b/pkg/api/server/register_healthcheck.go
new file mode 100644
index 000000000..e4cc145d5
--- /dev/null
+++ b/pkg/api/server/register_healthcheck.go
@@ -0,0 +1,13 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerHealthCheckHandlers(r *mux.Router) error {
+ r.Handle(VersionedPath("/libpod/containers/{name:..*}/runhealthcheck"), APIHandler(s.Context, libpod.RunHealthCheck)).Methods(http.MethodGet)
+ return nil
+}
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
new file mode 100644
index 000000000..488427f3c
--- /dev/null
+++ b/pkg/api/server/register_images.go
@@ -0,0 +1,571 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/generic"
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
+ // swagger:operation POST /images/create images createImage
+ //
+ // Create an image from an image
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: query
+ // name: fromImage
+ // type: string
+ // description: needs description
+ // - in: query
+ // name: tag
+ // type: string
+ // description: needs description
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "TBD"
+ // '404':
+ // description: repo or image does not exist
+ // schema:
+ // $ref: "#/responses/generalError"
+ // '500':
+ // description: unexpected error
+ // schema:
+ // $ref: '#/responses/GenericError'
+ r.Handle(VersionedPath("/images/create"), APIHandler(s.Context, generic.CreateImageFromImage)).Methods(http.MethodPost).Queries("fromImage", "{fromImage}")
+ // swagger:operation POST /images/create images createImage
+ //
+ // Create an image from Source
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: query
+ // name: fromSrc
+ // type: string
+ // description: needs description
+ // - in: query
+ // name: changes
+ // type: TBD
+ // description: needs description
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "TBD"
+ // '404':
+ // description: repo or image does not exist
+ // schema:
+ // $ref: "#/responses/generalError"
+ // '500':
+ // description: unexpected error
+ // schema:
+ // $ref: '#/responses/GenericError'
+ r.Handle(VersionedPath("/images/create"), APIHandler(s.Context, generic.CreateImageFromSrc)).Methods(http.MethodPost).Queries("fromSrc", "{fromSrc}")
+ // swagger:operation GET /images/json images listImages
+ //
+ // List Images
+ //
+ // ---
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // type: array
+ // items:
+ // schema:
+ // $ref: "#/responses/DockerImageSummary"
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/images/json"), APIHandler(s.Context, generic.GetImages)).Methods(http.MethodGet)
+ // swagger:operation POST /images/load images loadImage
+ //
+ // Import image
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: quiet
+ // type: bool
+ // description: not supported
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // description: TBD
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/images/load"), APIHandler(s.Context, handlers.LoadImage)).Methods(http.MethodPost)
+ // swagger:operation POST /images/prune images pruneImages
+ //
+ // Prune unused images
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: filters
+ // type: map[string][]string
+ // description: not supported
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "#/responses/DocsImageDeleteResponse"
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/images/prune"), APIHandler(s.Context, generic.PruneImages)).Methods(http.MethodPost)
+ // swagger:operation GET /images/search images searchImages
+ //
+ // Search images
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: term
+ // type: string
+ // description: term to search
+ // - in: query
+ // name: limit
+ // type: int
+ // description: maximum number of results
+ // - in: query
+ // name: filters
+ // type: map[string][]string
+ // description: TBD
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // $ref: "#/responses/DocsSearchResponse"
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/images/search"), APIHandler(s.Context, handlers.SearchImages)).Methods(http.MethodGet)
+ // swagger:operation DELETE /images/{nameOrID} images removeImage
+ //
+ // Remove Image
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: force
+ // type: bool
+ // description: remove the image even if used by containers or has other tags
+ // - in: query
+ // name: noprune
+ // type: bool
+ // description: not supported
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // $ref: "#/responses/DocsImageDeleteResponse"
+ // '404':
+ // $ref: '#/responses/BadParamError'
+ // '409':
+ // $ref: '#/responses/ConflictError'
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/images/{name:..*}"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete)
+ // swagger:operation GET /images/{nameOrID}/get images exportImage
+ //
+ // Export an image
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // $ref: "TBD"
+ // description: TBD
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/images/{name:..*}/get"), APIHandler(s.Context, generic.ExportImage)).Methods(http.MethodGet)
+ // swagger:operation GET /images/{nameOrID}/history images imageHistory
+ //
+ // History of an image
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // $ref: "#/responses/DocsHistory"
+ // '404':
+ // $ref: "#/responses/NoSuchImage"
+ // '500':
+ // $ref: "#/responses/InternalError"
+ r.Handle(VersionedPath("/images/{name:..*}/history"), APIHandler(s.Context, handlers.HistoryImage)).Methods(http.MethodGet)
+ // swagger:operation GET /images/{nameOrID}/json images inspectImage
+ //
+ // Inspect an image
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // $ref: "#/responses/DocsImageInspect"
+ // '404':
+ // $ref: "#/responses/NoSuchImage"
+ // '500':
+ // $ref: "#/responses/InternalError"
+ r.Handle(VersionedPath("/images/{name:..*}/json"), APIHandler(s.Context, generic.GetImage))
+ // swagger:operation POST /images/{nameOrID}/tag images tagImage
+ //
+ // Tag an image
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: repo
+ // type: string
+ // description: the repository to tag in
+ // - in: query
+ // name: tag
+ // type: string
+ // description: the name of the new tag
+ // produces:
+ // - application/json
+ // responses:
+ // 201:
+ // description: no error
+ // 400:
+ // $ref: '#/responses/BadParamError'
+ // 404:
+ // $ref: '#/responses/NoSuchImage'
+ // 409:
+ // $ref: '#/responses/ConflictError'
+ // 500:
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/images/{name:..*}/tag"), APIHandler(s.Context, handlers.TagImage)).Methods(http.MethodPost)
+ // swagger:operation POST /commit/ commit commitContainer
+ //
+ // Create a new image from a container
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: container
+ // type: string
+ // description: the name or ID of a container
+ // - in: query
+ // name: repo
+ // type: string
+ // description: the repository name for the created image
+ // - in: query
+ // name: tag
+ // type: string
+ // description: tag name for the created image
+ // - in: query
+ // name: comment
+ // type: string
+ // description: commit message
+ // - in: query
+ // name: author
+ // type: string
+ // description: author of the image
+ // - in: query
+ // name: pause
+ // type: bool
+ // description: pause the container before committing it
+ // - in: query
+ // name: changes
+ // type: string
+ // description: instructions to apply while committing in Dockerfile format
+ // produces:
+ // - application/json
+ // responses:
+ // '201':
+ // description: no error
+ // '404':
+ // $ref: '#/responses/NoSuchImage'
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/commit"), APIHandler(s.Context, generic.CommitContainer)).Methods(http.MethodPost)
+
+ /*
+ libpod endpoints
+ */
+
+ // swagger:operation POST /libpod/images/{nameOrID}/exists images imageExists
+ //
+ // Check if image exists in local store
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: query
+ // name: fromImage
+ // type: string
+ // description: needs description
+ // - in: query
+ // name: tag
+ // type: string
+ // description: needs description
+ // responses:
+ // '204':
+ // description: image exists
+ // '404':
+ // $ref: '#/responses/NoSuchImage'
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/{name:..*}/exists"), APIHandler(s.Context, libpod.ImageExists))
+ r.Handle(VersionedPath("/libpod/images/{name:..*}/tree"), APIHandler(s.Context, libpod.ImageTree))
+ // swagger:operation GET /libpod/images/{nameOrID}/history images imageHistory
+ //
+ // History of an image
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "#/responses/HistoryResponse"
+ // '404':
+ // $ref: '#/responses/NoSuchImage'
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/history"), APIHandler(s.Context, handlers.HistoryImage)).Methods(http.MethodGet)
+ // swagger:operation GET /libpod/images/json images listImages
+ //
+ // List Images
+ //
+ // ---
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "#/responses/DockerImageSummary"
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/json"), APIHandler(s.Context, libpod.GetImages)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/images/load images loadImage
+ //
+ // Import image
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: quiet
+ // type: bool
+ // description: not supported
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // description: TBD
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/load"), APIHandler(s.Context, handlers.LoadImage)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/images/prune images pruneImages
+ //
+ // Prune unused images
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: filters
+ // type: map[string][]string
+ // description: image filters
+ // - in: query
+ // name: all
+ // type: bool
+ // description: prune all images
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "#/responses/DocsImageDeleteResponse"
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/prune"), APIHandler(s.Context, libpod.PruneImages)).Methods(http.MethodPost)
+ // swagger:operation GET /libpod/images/search images searchImages
+ //
+ // Search images
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: term
+ // type: string
+ // description: term to search
+ // - in: query
+ // name: limit
+ // type: int
+ // description: maximum number of results
+ // - in: query
+ // name: filters
+ // type: map[string][]string
+ // description: TBD
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "#/responses/DocsSearchResponse"
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/search"), APIHandler(s.Context, handlers.SearchImages)).Methods(http.MethodGet)
+ // swagger:operation DELETE /libpod/images/{nameOrID} images removeImage
+ //
+ // Remove Image
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: force
+ // type: bool
+ // description: remove the image even if used by containers or has other tags
+ // - in: query
+ // name: noprune
+ // type: bool
+ // description: not supported
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "#/responses/DocsIageDeleteResponse"
+ // '404':
+ // $ref: '#/responses/NoSuchImage'
+ // '409':
+ // $ref: '#/responses/ConflictError'
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/{name:..*}"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete)
+ // swagger:operation GET /libpod/images/{nameOrID}/get images exportImage
+ //
+ // Export an image
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: format
+ // type: string
+ // description: format for exported image
+ // - in: query
+ // name: compress
+ // type: bool
+ // description: use compression on image
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // description: TBD
+ // '404':
+ // $ref: '#/responses/NoSuchImage'
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/{name:..*}/get"), APIHandler(s.Context, libpod.ExportImage)).Methods(http.MethodGet)
+ // swagger:operation GET /libpod/images/{nameOrID}/json images inspectImage
+ //
+ // Inspect an image
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "#/responses/DocsLibpodInspectImageResponse"
+ // '404':
+ // $ref: '#/responses/NoSuchImage'
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/{name:..*}/json"), APIHandler(s.Context, libpod.GetImage))
+ // swagger:operation POST /libpod/images/{nameOrID}/tag images tagImage
+ //
+ // Tag an image
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: repo
+ // type: string
+ // description: the repository to tag in
+ // - in: query
+ // name: tag
+ // type: string
+ // description: the name of the new tag
+ // produces:
+ // - application/json
+ // responses:
+ // '201':
+ // description: no error
+ // '400':
+ // $ref: '#/responses/BadParamError'
+ // '404':
+ // $ref: '#/responses/NoSuchImage'
+ // '409':
+ // $ref: '#/responses/ConflictError'
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/{name:..*}/tag"), APIHandler(s.Context, handlers.TagImage)).Methods(http.MethodPost)
+
+ r.Handle(VersionedPath("/build"), APIHandler(s.Context, handlers.BuildImage)).Methods(http.MethodPost)
+ return nil
+}
diff --git a/pkg/api/server/register_info.go b/pkg/api/server/register_info.go
new file mode 100644
index 000000000..797158553
--- /dev/null
+++ b/pkg/api/server/register_info.go
@@ -0,0 +1,28 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/generic"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerInfoHandlers(r *mux.Router) error {
+ // swagger:operation GET /info libpod getInfo
+ //
+ // Returns information on the system and libpod configuration
+ //
+ // ---
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // "$ref": "#/types/Info"
+ // '500':
+ // description: unexpected error
+ // schema:
+ // "$ref": "#/types/ErrorModel"
+ r.Handle(VersionedPath("/info"), APIHandler(s.Context, generic.GetInfo)).Methods(http.MethodGet)
+ return nil
+}
diff --git a/pkg/api/server/register_monitor.go b/pkg/api/server/register_monitor.go
new file mode 100644
index 000000000..e6c235419
--- /dev/null
+++ b/pkg/api/server/register_monitor.go
@@ -0,0 +1,11 @@
+package server
+
+import (
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) RegisterMonitorHandlers(r *mux.Router) error {
+ r.Handle(VersionedPath("/monitor"), APIHandler(s.Context, handlers.UnsupportedHandler))
+ return nil
+}
diff --git a/pkg/api/server/register_ping.go b/pkg/api/server/register_ping.go
new file mode 100644
index 000000000..4956f9822
--- /dev/null
+++ b/pkg/api/server/register_ping.go
@@ -0,0 +1,17 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/generic"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerPingHandlers(r *mux.Router) error {
+ r.Handle("/_ping", APIHandler(s.Context, generic.PingGET)).Methods(http.MethodGet)
+ r.Handle("/_ping", APIHandler(s.Context, generic.PingHEAD)).Methods("HEAD")
+
+ // libpod
+ r.Handle("/libpod/_ping", APIHandler(s.Context, generic.PingGET)).Methods(http.MethodGet)
+ return nil
+}
diff --git a/pkg/api/server/register_plugins.go b/pkg/api/server/register_plugins.go
new file mode 100644
index 000000000..7fd6b9c4c
--- /dev/null
+++ b/pkg/api/server/register_plugins.go
@@ -0,0 +1,11 @@
+package server
+
+import (
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) RegisterPluginsHandlers(r *mux.Router) error {
+ r.Handle(VersionedPath("/plugins"), APIHandler(s.Context, handlers.UnsupportedHandler))
+ return nil
+}
diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go
new file mode 100644
index 000000000..c2e10277e
--- /dev/null
+++ b/pkg/api/server/register_pods.go
@@ -0,0 +1,256 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
+ // swagger:operation GET /libpod/pods/json pods ListPods
+ //
+ // List Pods
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: query
+ // name: filters
+ // descriptions: needs description and plumbing for filters
+ // responses:
+ // '200':
+ // $ref: "#/responses/ListPodsResponse"
+ // '400':
+ // $ref: "#/responses/BadParamError"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/json"), APIHandler(s.Context, libpod.Pods)).Methods(http.MethodGet)
+ r.Handle(VersionedPath("/libpod/pods/create"), APIHandler(s.Context, libpod.PodCreate)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/pods/prune pods PrunePods
+ //
+ // Prune unused pods
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: force
+ // description: force delete
+ // type: bool
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '400':
+ // $ref: "#/responses/BadParamError"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/prune"), APIHandler(s.Context, libpod.PodPrune)).Methods(http.MethodPost)
+ // swagger:operation DELETE /libpod/pods/{nameOrID} pods removePod
+ //
+ // Remove Pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // - in: query
+ // name: force
+ // type: bool
+ // description: force delete
+ // responses:
+ // '204':
+ // description: no error
+ // '400':
+ // $ref: "#/responses/BadParamError"
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}"), APIHandler(s.Context, libpod.PodDelete)).Methods(http.MethodDelete)
+ // swagger:operation GET /libpod/pods/{nameOrID}/json pods inspectPod
+ //
+ // Inspect Pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // responses:
+ // '200':
+ // $ref: "#/responses/InspectPodResponse"
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}/json"), APIHandler(s.Context, libpod.PodInspect)).Methods(http.MethodGet)
+ // swagger:operation GET /libpod/pods/{nameOrID}/exists pods podExists
+ //
+ // Inspect Pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // responses:
+ // '204':
+ // description: pod exists
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}/exists"), APIHandler(s.Context, libpod.PodExists)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/pods/{nameOrID}/kill pods killPod
+ //
+ // Kill a pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // - in: query
+ // name: signal
+ // type: int
+ // description: signal to be sent to pod
+ // responses:
+ // '204':
+ // description: no error
+ // '400':
+ // $ref: "#/responses/BadParamError"
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '409':
+ // $ref: "#/responses/ConflictError"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}/kill"), APIHandler(s.Context, libpod.PodKill)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/pods/{nameOrID}/pause pods pausePod
+ //
+ // Pause a pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}/pause"), APIHandler(s.Context, libpod.PodPause)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/pods/{nameOrID}/restart pods restartPod
+ //
+ // Restart a pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}/restart"), APIHandler(s.Context, libpod.PodRestart)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/pods/{nameOrID}/start pods startPod
+ //
+ // Start a pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // responses:
+ // '204':
+ // description: no error
+ // '304':
+ // $ref: "#/responses/PodAlreadyStartedError"
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}/start"), APIHandler(s.Context, libpod.PodStart)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/pods/{nameOrID}/stop pods stopPod
+ //
+ // Stop a pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // - in: query
+ // name: t
+ // type: int
+ // description: timeout
+ // responses:
+ // '204':
+ // description: no error
+ // '304':
+ // $ref: "#/responses/PodAlreadyStoppedError"
+ // '400':
+ // $ref: "#/responses/BadParamError"
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}/stop"), APIHandler(s.Context, libpod.PodStop)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/pods/{nameOrID}/unpause pods unpausePod
+ //
+ // Unpause a pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}/unpause"), APIHandler(s.Context, libpod.PodUnpause)).Methods(http.MethodPost)
+ return nil
+}
diff --git a/pkg/api/server/register_swarm.go b/pkg/api/server/register_swarm.go
new file mode 100644
index 000000000..63d8acfde
--- /dev/null
+++ b/pkg/api/server/register_swarm.go
@@ -0,0 +1,27 @@
+package server
+
+import (
+ "errors"
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/mux"
+ "github.com/sirupsen/logrus"
+)
+
+func (s *APIServer) RegisterSwarmHandlers(r *mux.Router) error {
+ r.PathPrefix("/v{version:[0-9.]+}/configs/").HandlerFunc(noSwarm)
+ r.PathPrefix("/v{version:[0-9.]+}/nodes/").HandlerFunc(noSwarm)
+ r.PathPrefix("/v{version:[0-9.]+}/secrets/").HandlerFunc(noSwarm)
+ r.PathPrefix("/v{version:[0-9.]+}/services/").HandlerFunc(noSwarm)
+ r.PathPrefix("/v{version:[0-9.]+}/swarm/").HandlerFunc(noSwarm)
+ r.PathPrefix("/v{version:[0-9.]+}/tasks/").HandlerFunc(noSwarm)
+ return nil
+}
+
+// noSwarm returns http.StatusServiceUnavailable rather than something like http.StatusInternalServerError,
+// this allows the client to decide if they still can talk to us
+func noSwarm(w http.ResponseWriter, r *http.Request) {
+ logrus.Errorf("%s is not a podman supported service", r.URL.String())
+ utils.Error(w, "node is not part of a swarm", http.StatusServiceUnavailable, errors.New("Podman does not support service: "+r.URL.String()))
+}
diff --git a/pkg/api/server/register_system.go b/pkg/api/server/register_system.go
new file mode 100644
index 000000000..f0eaeffd2
--- /dev/null
+++ b/pkg/api/server/register_system.go
@@ -0,0 +1,11 @@
+package server
+
+import (
+ "github.com/containers/libpod/pkg/api/handlers/generic"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerSystemHandlers(r *mux.Router) error {
+ r.Handle(VersionedPath("/system/df"), APIHandler(s.Context, generic.GetDiskUsage))
+ return nil
+}
diff --git a/pkg/api/server/register_version.go b/pkg/api/server/register_version.go
new file mode 100644
index 000000000..94216b1b6
--- /dev/null
+++ b/pkg/api/server/register_version.go
@@ -0,0 +1,12 @@
+package server
+
+import (
+ "github.com/containers/libpod/pkg/api/handlers/generic"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerVersionHandlers(r *mux.Router) error {
+ r.Handle("/version", APIHandler(s.Context, generic.VersionHandler))
+ r.Handle(VersionedPath("/version"), APIHandler(s.Context, generic.VersionHandler))
+ return nil
+}
diff --git a/pkg/api/server/register_volumes.go b/pkg/api/server/register_volumes.go
new file mode 100644
index 000000000..8fe5a67e4
--- /dev/null
+++ b/pkg/api/server/register_volumes.go
@@ -0,0 +1,85 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerVolumeHandlers(r *mux.Router) error {
+ // swagger:operation POST /libpod/volumes/create volumes createVolume
+ //
+ // Create a volume
+ //
+ // ---
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // description: tbd
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.Handle("/libpod/volumes/create", APIHandler(s.Context, libpod.CreateVolume)).Methods(http.MethodPost)
+ r.Handle("/libpod/volumes/json", APIHandler(s.Context, libpod.ListVolumes)).Methods(http.MethodGet)
+ // swagger:operation POST /volumes/prune volumes pruneVolumes
+ //
+ // Prune volumes
+ //
+ // ---
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.Handle("/libpod/volumes/prune", APIHandler(s.Context, libpod.PruneVolumes)).Methods(http.MethodPost)
+ // swagger:operation GET /volumes/{nameOrID}/json volumes inspectVolume
+ //
+ // Inspect volume
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the volume
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // "$ref": "#/responses/InspectVolumeResponse"
+ // '404':
+ // "$ref": "#/responses/NoSuchVolume"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.Handle("/libpod/volumes/{name:..*}/json", APIHandler(s.Context, libpod.InspectVolume)).Methods(http.MethodGet)
+ // swagger:operation DELETE /volumes/{nameOrID} volumes removeVolume
+ //
+ // Inspect volume
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the volume
+ // - in: query
+ // name: force
+ // type: bool
+ // description: force removal
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '400':
+ // "$ref": "#/responses/BadParamError"
+ // '404':
+ // "$ref": "#/responses/NoSuchVolume"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.Handle("/libpod/volumes/{name:..*}", APIHandler(s.Context, libpod.RemoveVolume)).Methods(http.MethodDelete)
+ return nil
+}
diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go
new file mode 100644
index 000000000..717c7a876
--- /dev/null
+++ b/pkg/api/server/server.go
@@ -0,0 +1,189 @@
+// Package serviceapi Provides a Container compatible interface.
+//
+// This documentation describes the HTTP LibPod interface
+//
+// Schemes: http, https
+// Host: podman.io
+// BasePath: /
+// Version: 0.0.1
+// License: Apache-2.0 https://opensource.org/licenses/Apache-2.0
+// Contact: Podman <podman@lists.podman.io> https://podman.io/community/
+//
+// Consumes:
+// - application/json
+// - application/x-tar
+//
+// Produces:
+// - application/json
+// - text/plain
+// - text/html
+//
+// tags:
+// - name: "Containers"
+// description: manage containers
+// - name: "Images"
+// description: manage images
+// - name: "System"
+// description: manage system resources
+//
+// swagger:meta
+package server
+
+import (
+ "context"
+ "net"
+ "net/http"
+ "os"
+ "os/signal"
+ "strings"
+ "time"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/coreos/go-systemd/activation"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ log "github.com/sirupsen/logrus"
+)
+
+type APIServer struct {
+ http.Server // Where the HTTP work happens
+ *schema.Decoder // Decoder for Query parameters to structs
+ context.Context // Context for graceful server shutdown
+ *libpod.Runtime // Where the real work happens
+ net.Listener // mux for routing HTTP API calls to libpod routines
+ context.CancelFunc // Stop APIServer
+ *time.Timer // Hold timer for sliding window
+ time.Duration // Duration of client access sliding window
+}
+
+// NewServer will create and configure a new API HTTP server
+func NewServer(runtime *libpod.Runtime) (*APIServer, error) {
+ listeners, err := activation.Listeners()
+ if err != nil {
+ return nil, errors.Wrap(err, "Cannot retrieve file descriptors from systemd")
+ }
+ if len(listeners) != 1 {
+ return nil, errors.Errorf("Wrong number of file descriptors from systemd for socket activation (%d != 1)", len(listeners))
+ }
+
+ quit := make(chan os.Signal, 1)
+ signal.Notify(quit)
+
+ router := mux.NewRouter()
+
+ server := APIServer{
+ Server: http.Server{
+ Handler: router,
+ ReadHeaderTimeout: 20 * time.Second,
+ ReadTimeout: 20 * time.Second,
+ WriteTimeout: 2 * time.Minute,
+ },
+ Decoder: schema.NewDecoder(),
+ Context: nil,
+ Runtime: runtime,
+ Listener: listeners[0],
+ CancelFunc: nil,
+ Duration: 300 * time.Second,
+ }
+ server.Timer = time.AfterFunc(server.Duration, func() {
+ if err := server.Shutdown(); err != nil {
+ log.Errorf("unable to shutdown server: %q", err)
+ }
+ })
+
+ ctx, cancelFn := context.WithCancel(context.Background())
+
+ // TODO: Use ConnContext when ported to go 1.13
+ ctx = context.WithValue(ctx, "decoder", server.Decoder)
+ ctx = context.WithValue(ctx, "runtime", runtime)
+ ctx = context.WithValue(ctx, "shutdownFunc", server.Shutdown)
+ server.Context = ctx
+
+ server.CancelFunc = cancelFn
+ server.Decoder.IgnoreUnknownKeys(true)
+
+ router.NotFoundHandler = http.HandlerFunc(
+ func(w http.ResponseWriter, r *http.Request) {
+ // We can track user errors...
+ log.Infof("Failed Request: (%d:%s) for %s:'%s'", http.StatusNotFound, http.StatusText(http.StatusNotFound), r.Method, r.URL.String())
+ http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
+ },
+ )
+
+ for _, fn := range []func(*mux.Router) error{
+ server.RegisterAuthHandlers,
+ server.RegisterContainersHandlers,
+ server.RegisterDistributionHandlers,
+ server.registerHealthCheckHandlers,
+ server.registerImagesHandlers,
+ server.registerInfoHandlers,
+ server.RegisterMonitorHandlers,
+ server.registerPingHandlers,
+ server.RegisterPluginsHandlers,
+ server.registerPodsHandlers,
+ server.RegisterSwarmHandlers,
+ server.registerSystemHandlers,
+ server.registerVersionHandlers,
+ server.registerVolumeHandlers,
+ } {
+ if err := fn(router); err != nil {
+ return nil, err
+ }
+ }
+
+ if log.IsLevelEnabled(log.DebugLevel) {
+ router.Walk(func(route *mux.Route, r *mux.Router, ancestors []*mux.Route) error { // nolint
+ path, err := route.GetPathTemplate()
+ if err != nil {
+ path = ""
+ }
+ methods, err := route.GetMethods()
+ if err != nil {
+ methods = []string{}
+ }
+ log.Debugf("Methods: %s Path: %s", strings.Join(methods, ", "), path)
+ return nil
+ })
+ }
+
+ return &server, nil
+}
+
+// Serve starts responding to HTTP requests
+func (s *APIServer) Serve() error {
+ defer s.CancelFunc()
+
+ err := s.Server.Serve(s.Listener)
+ if err != nil && err != http.ErrServerClosed {
+ return errors.Wrap(err, "Failed to start APIServer")
+ }
+
+ return nil
+}
+
+// Shutdown is a clean shutdown waiting on existing clients
+func (s *APIServer) Shutdown() error {
+ // We're still in the sliding service window
+ if s.Timer.Stop() {
+ s.Timer.Reset(s.Duration)
+ return nil
+ }
+
+ // We've been idle for the service window, really shutdown
+ go func() {
+ err := s.Server.Shutdown(s.Context)
+ if err != nil && err != context.Canceled {
+ log.Errorf("Failed to cleanly shutdown APIServer: %s", err.Error())
+ }
+ }()
+
+ // Wait for graceful shutdown vs. just killing connections and dropping data
+ <-s.Context.Done()
+ return nil
+}
+
+// Close immediately stops responding to clients and exits
+func (s *APIServer) Close() error {
+ return s.Server.Close()
+}
diff --git a/pkg/api/server/swagger.go b/pkg/api/server/swagger.go
new file mode 100644
index 000000000..0eb57ebab
--- /dev/null
+++ b/pkg/api/server/swagger.go
@@ -0,0 +1,133 @@
+package server
+
+import (
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+)
+
+// No such image
+// swagger:response NoSuchImage
+type swagErrNoSuchImage struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// No such container
+// swagger:response NoSuchContainer
+type swagErrNoSuchContainer struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// No such volume
+// swagger:response NoSuchVolume
+type swagErrNoSuchVolume struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// No such pod
+// swagger:response NoSuchPod
+type swagErrNoSuchPod struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Internal error
+// swagger:response InternalError
+type swagInternalError struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Generic error
+// swagger:response GenericError
+type swagGenericError struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Conflict error
+// swagger:response ConflictError
+type swagConflictError struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Bad parameter
+// swagger:response BadParamError
+type swagBadParamError struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Container already started
+// swagger:response ContainerAlreadyStartedError
+type swagContainerAlreadyStartedError struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Container already stopped
+// swagger:response ContainerAlreadyStoppedError
+type swagContainerAlreadyStopped struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Pod already started
+// swagger:response PodAlreadyStartedError
+type swagPodAlreadyStartedError struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Pod already stopped
+// swagger:response PodAlreadyStoppedError
+type swagPodAlreadyStopped struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Image summary
+// swagger:response DockerImageSummary
+type swagImageSummary struct {
+ // in:body
+ Body struct {
+ handlers.ImageSummary
+ }
+}
+
+// List Containers
+// swagger:response DocsListContainer
+type swagListContainers struct {
+ // in:body
+ Body struct {
+ // This causes go-swagger to crash
+ //handlers.Container
+ }
+}
diff --git a/pkg/bindings/containers.go b/pkg/bindings/containers.go
new file mode 100644
index 000000000..cd0b09767
--- /dev/null
+++ b/pkg/bindings/containers.go
@@ -0,0 +1,137 @@
+package bindings
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/libpod"
+)
+
+func (c Connection) ListContainers(filter []string, last int, size, sync bool) ([]shared.PsContainerOutput, error) { // nolint:typecheck
+ images := []shared.PsContainerOutput{}
+ params := make(map[string]string)
+ params["last"] = strconv.Itoa(last)
+ params["size"] = strconv.FormatBool(size)
+ params["sync"] = strconv.FormatBool(sync)
+ response, err := c.newRequest(http.MethodGet, "/containers/json", nil, params)
+ if err != nil {
+ return images, err
+ }
+ return images, response.Process(nil)
+}
+
+func (c Connection) PruneContainers() ([]string, error) {
+ var (
+ pruned []string
+ )
+ response, err := c.newRequest(http.MethodPost, "/containers/prune", nil, nil)
+ if err != nil {
+ return pruned, err
+ }
+ return pruned, response.Process(nil)
+}
+
+func (c Connection) RemoveContainer(nameOrID string, force, volumes bool) error {
+ params := make(map[string]string)
+ params["force"] = strconv.FormatBool(force)
+ params["vols"] = strconv.FormatBool(volumes)
+ response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/containers/%s", nameOrID), nil, params)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func (c Connection) InspectContainer(nameOrID string, size bool) (*libpod.InspectContainerData, error) {
+ params := make(map[string]string)
+ params["size"] = strconv.FormatBool(size)
+ response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/containers/%s/json", nameOrID), nil, params)
+ if err != nil {
+ return nil, err
+ }
+ inspect := libpod.InspectContainerData{}
+ return &inspect, response.Process(&inspect)
+}
+
+func (c Connection) KillContainer(nameOrID string, signal int) error {
+ params := make(map[string]string)
+ params["signal"] = strconv.Itoa(signal)
+ response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/kill", nameOrID), nil, params)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+
+}
+func (c Connection) ContainerLogs() {}
+func (c Connection) PauseContainer(nameOrID string) error {
+ response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/pause", nameOrID), nil, nil)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func (c Connection) RestartContainer(nameOrID string, timeout int) error {
+ // TODO how do we distinguish between an actual zero value and not wanting to change the timeout value
+ params := make(map[string]string)
+ params["timeout"] = strconv.Itoa(timeout)
+ response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/restart", nameOrID), nil, params)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func (c Connection) StartContainer(nameOrID, detachKeys string) error {
+ params := make(map[string]string)
+ if len(detachKeys) > 0 {
+ params["detachKeys"] = detachKeys
+ }
+ response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/start", nameOrID), nil, params)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func (c Connection) ContainerStats() {}
+func (c Connection) ContainerTop() {}
+
+func (c Connection) UnpauseContainer(nameOrID string) error {
+ response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/unpause", nameOrID), nil, nil)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func (c Connection) WaitContainer(nameOrID string) error {
+ _, err := http.Post(c.makeEndpoint(fmt.Sprintf("containers/%s/wait", nameOrID)), "application/json", nil)
+ return err
+}
+
+func (c Connection) ContainerExists(nameOrID string) (bool, error) {
+ response, err := http.Get(c.makeEndpoint(fmt.Sprintf("/containers/%s/exists", nameOrID)))
+ if err != nil {
+ return false, err
+ }
+ if response.StatusCode == http.StatusOK {
+ return true, nil
+ }
+ return false, nil
+}
+
+func (c Connection) StopContainer(nameOrID string, timeout int) error {
+ // TODO we might need to distinguish whether a timeout is desired; a zero, the int
+ // zero value is valid; what do folks want to do?
+ params := make(map[string]string)
+ params["t"] = strconv.Itoa(timeout)
+ response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/stop", nameOrID), nil, params)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
diff --git a/pkg/bindings/generate.go b/pkg/bindings/generate.go
new file mode 100644
index 000000000..534909062
--- /dev/null
+++ b/pkg/bindings/generate.go
@@ -0,0 +1,4 @@
+package bindings
+
+func (c Connection) GenerateKube() {}
+func (c Connection) GenerateSystemd() {}
diff --git a/pkg/bindings/healthcheck.go b/pkg/bindings/healthcheck.go
new file mode 100644
index 000000000..32515e332
--- /dev/null
+++ b/pkg/bindings/healthcheck.go
@@ -0,0 +1,19 @@
+package bindings
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+)
+
+func (c Connection) RunHealthCheck(nameOrID string) (*libpod.HealthCheckStatus, error) {
+ var (
+ status libpod.HealthCheckStatus
+ )
+ response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/containers/%s/runhealthcheck", nameOrID), nil, nil)
+ if err != nil {
+ return nil, err
+ }
+ return &status, response.Process(&status)
+}
diff --git a/pkg/bindings/info.go b/pkg/bindings/info.go
new file mode 100644
index 000000000..5f318d652
--- /dev/null
+++ b/pkg/bindings/info.go
@@ -0,0 +1,3 @@
+package bindings
+
+func (c Connection) Info() {}
diff --git a/pkg/bindings/mount.go b/pkg/bindings/mount.go
new file mode 100644
index 000000000..2e3d6d7f6
--- /dev/null
+++ b/pkg/bindings/mount.go
@@ -0,0 +1,26 @@
+package bindings
+
+import (
+ "fmt"
+ "net/http"
+)
+
+func (c Connection) MountContainer(nameOrID string) (string, error) {
+ var (
+ path string
+ )
+ response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/mount", nameOrID), nil, nil)
+ if err != nil {
+ return path, err
+ }
+ return path, response.Process(&path)
+}
+
+func (c Connection) GetMountedContainerPaths() (map[string]string, error) {
+ mounts := make(map[string]string)
+ response, err := c.newRequest(http.MethodGet, "/containers/showmounted", nil, nil)
+ if err != nil {
+ return mounts, err
+ }
+ return mounts, response.Process(&mounts)
+}
diff --git a/pkg/bindings/network.go b/pkg/bindings/network.go
new file mode 100644
index 000000000..383615e5d
--- /dev/null
+++ b/pkg/bindings/network.go
@@ -0,0 +1,37 @@
+package bindings
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/containernetworking/cni/libcni"
+)
+
+func (c Connection) CreateNetwork() {}
+func (c Connection) InspectNetwork(nameOrID string) (map[string]interface{}, error) {
+ n := make(map[string]interface{})
+ response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/networks/%s/json", nameOrID), nil, nil)
+ if err != nil {
+ return n, err
+ }
+ return n, response.Process(&n)
+}
+
+func (c Connection) RemoveNetwork(nameOrID string) error {
+ response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/networks/%s", nameOrID), nil, nil)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func (c Connection) ListNetworks() ([]*libcni.NetworkConfigList, error) {
+ var (
+ netList []*libcni.NetworkConfigList
+ )
+ response, err := c.newRequest(http.MethodGet, "/networks/json", nil, nil)
+ if err != nil {
+ return netList, err
+ }
+ return netList, response.Process(&netList)
+}
diff --git a/pkg/bindings/play.go b/pkg/bindings/play.go
new file mode 100644
index 000000000..a9dee82b1
--- /dev/null
+++ b/pkg/bindings/play.go
@@ -0,0 +1,3 @@
+package bindings
+
+func (c Connection) PlayKube() {}
diff --git a/pkg/bindings/pods.go b/pkg/bindings/pods.go
new file mode 100644
index 000000000..eac9d2ef5
--- /dev/null
+++ b/pkg/bindings/pods.go
@@ -0,0 +1,128 @@
+package bindings
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/containers/libpod/libpod"
+)
+
+func (c Connection) CreatePod() error {
+ // TODO
+ return ErrNotImplemented
+}
+
+func (c Connection) PodExists(nameOrID string) (bool, error) {
+ response, err := http.Get(c.makeEndpoint(fmt.Sprintf("/pods/%s/exists", nameOrID)))
+ if err != nil {
+ return false, err
+ }
+ return response.StatusCode == http.StatusOK, err
+}
+
+func (c Connection) InspectPod(nameOrID string) (*libpod.PodInspect, error) {
+ inspect := libpod.PodInspect{}
+ response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/pods/%s/json", nameOrID), nil, nil)
+ if err != nil {
+ return &inspect, err
+ }
+ return &inspect, response.Process(&inspect)
+}
+
+func (c Connection) KillPod(nameOrID string, signal int) error {
+ params := make(map[string]string)
+ params["signal"] = strconv.Itoa(signal)
+ response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/kill", nameOrID), nil, params)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func (c Connection) PausePod(nameOrID string) error {
+ response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/pause", nameOrID), nil, nil)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func (c Connection) PrunePods(force bool) error {
+ params := make(map[string]string)
+ params["force"] = strconv.FormatBool(force)
+ response, err := c.newRequest(http.MethodPost, "/pods/prune", nil, params)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func (c Connection) ListPods(filters []string) (*[]libpod.PodInspect, error) {
+ var (
+ inspect []libpod.PodInspect
+ )
+ params := make(map[string]string)
+ // TODO I dont remember how to do this for []string{}
+ // FIXME
+ //params["filters"] = strconv.FormatBool(force)
+ response, err := c.newRequest(http.MethodPost, "/pods/json", nil, params)
+ if err != nil {
+ return &inspect, err
+ }
+ return &inspect, response.Process(&inspect)
+}
+
+func (c Connection) RestartPod(nameOrID string) error {
+ response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/restart", nameOrID), nil, nil)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func (c Connection) RemovePod(nameOrID string, force bool) error {
+ params := make(map[string]string)
+ params["force"] = strconv.FormatBool(force)
+ response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/pods/%s", nameOrID), nil, params)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func (c Connection) StartPod(nameOrID string) error {
+ response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/pods/%s/start", nameOrID), nil, nil)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func (c Connection) PodStats() error {
+ // TODO
+ return ErrNotImplemented
+}
+
+func (c Connection) StopPod(nameOrID string, timeout int) error {
+ params := make(map[string]string)
+ params["t"] = strconv.Itoa(timeout)
+ response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/stop", nameOrID), nil, params)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func (c Connection) PodTop() error {
+ // TODO
+ return ErrNotImplemented // nolint:typecheck
+}
+
+func (c Connection) UnpausePod(nameOrID string) error {
+ response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/unpause", nameOrID), nil, nil)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
diff --git a/pkg/bindings/search.go b/pkg/bindings/search.go
new file mode 100644
index 000000000..0f462357c
--- /dev/null
+++ b/pkg/bindings/search.go
@@ -0,0 +1,39 @@
+package bindings
+
+import (
+ "net/http"
+ "strconv"
+
+ "github.com/containers/libpod/libpod/image"
+)
+
+type ImageSearchFilters struct {
+ Automated bool `json:"automated"`
+ Official bool `json:"official"`
+ Stars int `json:"stars"`
+}
+
+// TODO This method can be concluded when we determine how we want the filters to work on the
+// API end
+func (i *ImageSearchFilters) ToMapJSON() string {
+ return ""
+}
+
+func (c Connection) SearchImages(term string, limit int, filters *ImageSearchFilters) ([]image.SearchResult, error) {
+ var (
+ searchResults []image.SearchResult
+ )
+ params := make(map[string]string)
+ params["term"] = term
+ if limit > 0 {
+ params["limit"] = strconv.Itoa(limit)
+ }
+ if filters != nil {
+ params["filters"] = filters.ToMapJSON()
+ }
+ response, err := c.newRequest(http.MethodGet, "/images/search", nil, params)
+ if err != nil {
+ return searchResults, nil
+ }
+ return searchResults, response.Process(&searchResults)
+}
diff --git a/pkg/bindings/version.go b/pkg/bindings/version.go
new file mode 100644
index 000000000..c833a644c
--- /dev/null
+++ b/pkg/bindings/version.go
@@ -0,0 +1,3 @@
+package bindings
+
+func (c Connection) Version() {}
diff --git a/pkg/bindings/volumes.go b/pkg/bindings/volumes.go
new file mode 100644
index 000000000..27e6f9efa
--- /dev/null
+++ b/pkg/bindings/volumes.go
@@ -0,0 +1,60 @@
+package bindings
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers"
+)
+
+func (c Connection) CreateVolume(config handlers.VolumeCreateConfig) (string, error) {
+ var (
+ volumeID string
+ )
+ response, err := c.newRequest(http.MethodPost, "/volumes/create", nil, nil)
+ if err != nil {
+ return volumeID, err
+ }
+ return volumeID, response.Process(&volumeID)
+}
+
+func (c Connection) InspectVolume(nameOrID string) (*libpod.InspectVolumeData, error) {
+ var (
+ inspect libpod.InspectVolumeData
+ )
+ response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/volumes/%s/json", nameOrID), nil, nil)
+ if err != nil {
+ return &inspect, err
+ }
+ return &inspect, response.Process(&inspect)
+}
+
+func (c Connection) ListVolumes() error {
+ // TODO
+ // The API side of things for this one does a lot in main and therefore
+ // is not implemented yet.
+ return ErrNotImplemented // nolint:typecheck
+}
+
+func (c Connection) PruneVolumes() ([]string, error) {
+ var (
+ pruned []string
+ )
+ response, err := c.newRequest(http.MethodPost, "/volumes/prune", nil, nil)
+ if err != nil {
+ return pruned, err
+ }
+ return pruned, response.Process(&pruned)
+}
+
+func (c Connection) RemoveVolume(nameOrID string, force bool) error {
+ params := make(map[string]string)
+ params["force"] = strconv.FormatBool(force)
+ response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/volumes/prune", nameOrID), nil, params)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
diff --git a/pkg/network/config.go b/pkg/network/config.go
index a41455f68..e5c981419 100644
--- a/pkg/network/config.go
+++ b/pkg/network/config.go
@@ -2,6 +2,7 @@ package network
import (
"encoding/json"
+ "errors"
"net"
)
@@ -19,6 +20,10 @@ const (
DefaultPodmanDomainName = "dns.podman"
)
+var (
+ ErrNetworkNotFound = errors.New("network not found")
+)
+
// GetDefaultPodmanNetwork outputs the default network for podman
func GetDefaultPodmanNetwork() (*net.IPNet, error) {
_, n, err := net.ParseCIDR("10.88.1.0/24")
diff --git a/pkg/network/files.go b/pkg/network/files.go
index 2f3932974..92cadcf0c 100644
--- a/pkg/network/files.go
+++ b/pkg/network/files.go
@@ -2,6 +2,7 @@ package network
import (
"encoding/json"
+ "fmt"
"io/ioutil"
"sort"
"strings"
@@ -46,7 +47,7 @@ func GetCNIConfigPathByName(name string) (string, error) {
return confFile, nil
}
}
- return "", errors.Errorf("unable to find network configuration for %s", name)
+ return "", errors.Wrap(ErrNetworkNotFound, fmt.Sprintf("unable to find network configuration for %s", name))
}
// ReadRawCNIConfByName reads the raw CNI configuration for a CNI
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index 244a8d1cd..6d058229b 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -282,10 +282,13 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l
options = append(options, libpod.WithStopSignal(c.StopSignal))
options = append(options, libpod.WithStopTimeout(c.StopTimeout))
- logPath := getLoggingPath(c.LogDriverOpt)
+ logPath, logTag := getLoggingOpts(c.LogDriverOpt)
if logPath != "" {
options = append(options, libpod.WithLogPath(logPath))
}
+ if logTag != "" {
+ options = append(options, libpod.WithLogTag(logTag))
+ }
if c.LogDriver != "" {
options = append(options, libpod.WithLogDriver(c.LogDriver))
diff --git a/pkg/spec/parse.go b/pkg/spec/parse.go
index c2572a033..6fa0b0636 100644
--- a/pkg/spec/parse.go
+++ b/pkg/spec/parse.go
@@ -132,16 +132,23 @@ func validateIOpsDevice(val string) (*throttleDevice, error) { //nolint
}, nil
}
-func getLoggingPath(opts []string) string {
+// getLoggingOpts splits the path= and tag= options provided to --log-opt.
+func getLoggingOpts(opts []string) (string, string) {
+ var path, tag string
for _, opt := range opts {
arr := strings.SplitN(opt, "=", 2)
if len(arr) == 2 {
if strings.TrimSpace(arr[0]) == "path" {
- return strings.TrimSpace(arr[1])
+ path = strings.TrimSpace(arr[1])
+ } else if strings.TrimSpace(arr[0]) == "tag" {
+ tag = strings.TrimSpace(arr[1])
}
}
+ if path != "" && tag != "" {
+ break
+ }
}
- return ""
+ return path, tag
}
// ParseDevice parses device mapping string to a src, dest & permissions string
diff --git a/pkg/systemdgen/systemdgen.go b/pkg/systemdgen/systemdgen.go
index 09d3c6fd5..b6167a23e 100644
--- a/pkg/systemdgen/systemdgen.go
+++ b/pkg/systemdgen/systemdgen.go
@@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"sort"
+ "strings"
"text/template"
"time"
@@ -48,6 +49,14 @@ type ContainerInfo struct {
Executable string
// TimeStamp at the time of creating the unit file. Will be set internally.
TimeStamp string
+ // New controls if a new container is created or if an existing one is started.
+ New bool
+ // CreateCommand is the full command plus arguments of the process the
+ // container has been created with.
+ CreateCommand []string
+ // RunCommand is a post-processed variant of CreateCommand and used for
+ // the ExecStart field in generic unit files.
+ RunCommand string
}
var restartPolicies = []string{"no", "on-success", "on-failure", "on-abnormal", "on-watchdog", "on-abort", "always"}
@@ -84,17 +93,35 @@ Before={{- range $index, $value := .RequiredServices -}}{{if $index}} {{end}}{{
[Service]
Restart={{.RestartPolicy}}
+{{- if .New}}
+ExecStartPre=/usr/bin/rm -f /%t/%n-pid /%t/%n-cid
+ExecStart={{.RunCommand}}
+ExecStop={{.Executable}} stop --cidfile /%t/%n-cid {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}}
+ExecStopPost={{.Executable}} rm -f --cidfile /%t/%n-cid
+PIDFile=/%t/%n-pid
+{{- else}}
ExecStart={{.Executable}} start {{.ContainerName}}
ExecStop={{.Executable}} stop {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}} {{.ContainerName}}
+PIDFile={{.PIDFile}}
+{{- end}}
KillMode=none
Type=forking
-PIDFile={{.PIDFile}}
[Install]
WantedBy=multi-user.target`
+// Options include different options to control the unit file generation.
+type Options struct {
+ // When set, generate service files in the current working directory and
+ // return the paths to these files instead of returning all contents in one
+ // big string.
+ Files bool
+ // New controls if a new container is created or if an existing one is started.
+ New bool
+}
+
// CreateContainerSystemdUnit creates a systemd unit file for a container.
-func CreateContainerSystemdUnit(info *ContainerInfo, generateFiles bool) (string, error) {
+func CreateContainerSystemdUnit(info *ContainerInfo, opts Options) (string, error) {
if err := validateRestartPolicy(info.RestartPolicy); err != nil {
return "", err
}
@@ -109,6 +136,36 @@ func CreateContainerSystemdUnit(info *ContainerInfo, generateFiles bool) (string
info.Executable = executable
}
+ // Assemble the ExecStart command when creating a new container.
+ //
+ // Note that we cannot catch all corner cases here such that users
+ // *must* manually check the generated files. A container might have
+ // been created via a Python script, which would certainly yield an
+ // invalid `info.CreateCommand`. Hence, we're doing a best effort unit
+ // generation and don't try aiming at completeness.
+ if opts.New {
+ // The create command must at least have three arguments:
+ // /usr/bin/podman run $IMAGE
+ index := 2
+ if info.CreateCommand[1] == "container" {
+ index = 3
+ }
+ if len(info.CreateCommand) < index+1 {
+ return "", errors.Errorf("container's create command is too short or invalid: %v", info.CreateCommand)
+ }
+ // We're hard-coding the first four arguments and append the
+ // CreatCommand with a stripped command and subcomand.
+ command := []string{
+ info.Executable,
+ "run",
+ "--conmon-pidfile", "/%t/%n-pid",
+ "--cidfile", "/%t/%n-cid",
+ }
+ command = append(command, info.CreateCommand[index:]...)
+ info.RunCommand = strings.Join(command, " ")
+ info.New = true
+ }
+
if info.PodmanVersion == "" {
info.PodmanVersion = version.Version
}
@@ -131,7 +188,7 @@ func CreateContainerSystemdUnit(info *ContainerInfo, generateFiles bool) (string
return "", err
}
- if !generateFiles {
+ if !opts.Files {
return buf.String(), nil
}
diff --git a/pkg/systemdgen/systemdgen_test.go b/pkg/systemdgen/systemdgen_test.go
index 1ddb0c514..e1da7e8e0 100644
--- a/pkg/systemdgen/systemdgen_test.go
+++ b/pkg/systemdgen/systemdgen_test.go
@@ -44,9 +44,9 @@ Documentation=man:podman-generate-systemd(1)
Restart=always
ExecStart=/usr/bin/podman start 639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401
ExecStop=/usr/bin/podman stop -t 10 639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401
+PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid
KillMode=none
Type=forking
-PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid
[Install]
WantedBy=multi-user.target`
@@ -62,9 +62,9 @@ Documentation=man:podman-generate-systemd(1)
Restart=always
ExecStart=/usr/bin/podman start foobar
ExecStop=/usr/bin/podman stop -t 10 foobar
+PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid
KillMode=none
Type=forking
-PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid
[Install]
WantedBy=multi-user.target`
@@ -84,9 +84,9 @@ After=a.service b.service c.service pod.service
Restart=always
ExecStart=/usr/bin/podman start foobar
ExecStop=/usr/bin/podman stop -t 10 foobar
+PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid
KillMode=none
Type=forking
-PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid
[Install]
WantedBy=multi-user.target`
@@ -104,9 +104,29 @@ Before=container-1.service container-2.service
Restart=always
ExecStart=/usr/bin/podman start jadda-jadda-infra
ExecStop=/usr/bin/podman stop -t 10 jadda-jadda-infra
+PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid
+KillMode=none
+Type=forking
+
+[Install]
+WantedBy=multi-user.target`
+
+ goodNameNew := `# jadda-jadda.service
+# autogenerated by Podman CI
+
+[Unit]
+Description=Podman jadda-jadda.service
+Documentation=man:podman-generate-systemd(1)
+
+[Service]
+Restart=always
+ExecStartPre=/usr/bin/rm -f /%t/%n-pid /%t/%n-cid
+ExecStart=/usr/bin/podman run --conmon-pidfile /%t/%n-pid --cidfile /%t/%n-cid --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN
+ExecStop=/usr/bin/podman stop --cidfile /%t/%n-cid -t 42
+ExecStopPost=/usr/bin/podman rm -f --cidfile /%t/%n-cid
+PIDFile=/%t/%n-pid
KillMode=none
Type=forking
-PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid
[Install]
WantedBy=multi-user.target`
@@ -184,16 +204,35 @@ WantedBy=multi-user.target`
"",
true,
},
+ {"good with name and generic",
+ ContainerInfo{
+ Executable: "/usr/bin/podman",
+ ServiceName: "jadda-jadda",
+ ContainerName: "jadda-jadda",
+ RestartPolicy: "always",
+ PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
+ StopTimeout: 42,
+ PodmanVersion: "CI",
+ New: true,
+ CreateCommand: []string{"I'll get stripped", "container", "run", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"},
+ },
+ goodNameNew,
+ false,
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- got, err := CreateContainerSystemdUnit(&tt.info, false)
+ opts := Options{
+ Files: false,
+ New: tt.info.New,
+ }
+ got, err := CreateContainerSystemdUnit(&tt.info, opts)
if (err != nil) != tt.wantErr {
t.Errorf("CreateContainerSystemdUnit() error = \n%v, wantErr \n%v", err, tt.wantErr)
return
}
if got != tt.want {
- t.Errorf("CreateContainerSystemdUnit() = \n%v, want \n%v", got, tt.want)
+ t.Errorf("CreateContainerSystemdUnit() = \n%v\n---------> want\n%v", got, tt.want)
}
})
}
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index c9d09b8b5..9269f6115 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -335,6 +335,13 @@ func ParseIDMapping(mode namespaces.UsernsMode, UIDMapSlice, GIDMapSlice []strin
return nil, errors.New("cannot specify subuidmap or subgidmap with --userns=keep-id")
}
if rootless.IsRootless() {
+ min := func(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+ }
+
uid := rootless.GetRootlessUID()
gid := rootless.GetRootlessGID()
@@ -352,13 +359,17 @@ func ParseIDMapping(mode namespaces.UsernsMode, UIDMapSlice, GIDMapSlice []strin
options.UIDMap, options.GIDMap = nil, nil
- options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: uid})
+ options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)})
options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1})
- options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid})
+ if maxUID > uid {
+ options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid})
+ }
- options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: gid})
+ options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)})
options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1})
- options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid})
+ if maxGID > gid {
+ options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid})
+ }
options.HostUIDMapping = false
options.HostGIDMapping = false
diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go
index bc644f87c..333595a96 100644
--- a/pkg/varlinkapi/images.go
+++ b/pkg/varlinkapi/images.go
@@ -249,7 +249,7 @@ func (i *LibpodAPI) BuildImage(call iopodman.VarlinkCall, config iopodman.BuildI
c := make(chan error)
go func() {
- err := i.Runtime.Build(getContext(), options, newPathDockerFiles...)
+ _, _, err := i.Runtime.Build(getContext(), options, newPathDockerFiles...)
c <- err
close(c)
}()
diff --git a/test/e2e/generate_systemd_test.go b/test/e2e/generate_systemd_test.go
index 91072b023..f0fef41a4 100644
--- a/test/e2e/generate_systemd_test.go
+++ b/test/e2e/generate_systemd_test.go
@@ -177,4 +177,32 @@ var _ = Describe("Podman generate systemd", func() {
found, _ = session.GrepString("/container-foo-1.service")
Expect(found).To(BeTrue())
})
+
+ It("podman generate systemd --new", func() {
+ n := podmanTest.Podman([]string{"create", "--name", "foo", "alpine", "top"})
+ n.WaitWithDefaultTimeout()
+ Expect(n.ExitCode()).To(Equal(0))
+
+ session := podmanTest.Podman([]string{"generate", "systemd", "--timeout", "42", "--name", "--new", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ // Grepping the output (in addition to unit tests)
+ found, _ := session.GrepString("# container-foo.service")
+ Expect(found).To(BeTrue())
+
+ found, _ = session.GrepString("stop --cidfile /%t/%n-cid -t 42")
+ Expect(found).To(BeTrue())
+ })
+
+ It("podman generate systemd --new pod", func() {
+ n := podmanTest.Podman([]string{"pod", "create", "--name", "foo"})
+ n.WaitWithDefaultTimeout()
+ Expect(n.ExitCode()).To(Equal(0))
+
+ session := podmanTest.Podman([]string{"generate", "systemd", "--timeout", "42", "--name", "--new", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ })
+
})
diff --git a/test/e2e/libpod_suite_remoteclient_test.go b/test/e2e/libpod_suite_remoteclient_test.go
index 2cd485114..c87ff016a 100644
--- a/test/e2e/libpod_suite_remoteclient_test.go
+++ b/test/e2e/libpod_suite_remoteclient_test.go
@@ -24,7 +24,7 @@ func SkipIfRemote() {
func SkipIfRootless() {
if os.Geteuid() != 0 {
- ginkgo.Skip("This function is not enabled for remote podman")
+ ginkgo.Skip("This function is not enabled for rootless podman")
}
}
diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go
index e25364695..0438a31cb 100644
--- a/test/e2e/logs_test.go
+++ b/test/e2e/logs_test.go
@@ -1,7 +1,9 @@
package integration
import (
+ "fmt"
"os"
+ "os/exec"
"strings"
. "github.com/containers/libpod/test/utils"
@@ -153,6 +155,23 @@ var _ = Describe("Podman logs", func() {
Expect(results.ExitCode()).To(BeZero())
})
+ It("podman journald logs for container with container tag", func() {
+ Skip("need to verify images have correct packages for journald")
+ logc := podmanTest.Podman([]string{"run", "--log-driver", "journald", "--log-opt=tag={{.ImageName}}", "-d", 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_TAG", "-u", fmt.Sprintf("libpod-conmon-%s.scope", cid))
+ out, err := cmd.CombinedOutput()
+ Expect(err).To(BeNil())
+ Expect(string(out)).To(ContainSubstring("alpine"))
+ })
+
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/vendor/github.com/google/uuid/.travis.yml b/vendor/github.com/google/uuid/.travis.yml
new file mode 100644
index 000000000..d8156a60b
--- /dev/null
+++ b/vendor/github.com/google/uuid/.travis.yml
@@ -0,0 +1,9 @@
+language: go
+
+go:
+ - 1.4.3
+ - 1.5.3
+ - tip
+
+script:
+ - go test -v ./...
diff --git a/vendor/github.com/google/uuid/CONTRIBUTING.md b/vendor/github.com/google/uuid/CONTRIBUTING.md
new file mode 100644
index 000000000..04fdf09f1
--- /dev/null
+++ b/vendor/github.com/google/uuid/CONTRIBUTING.md
@@ -0,0 +1,10 @@
+# How to contribute
+
+We definitely welcome patches and contribution to this project!
+
+### Legal requirements
+
+In order to protect both you and ourselves, you will need to sign the
+[Contributor License Agreement](https://cla.developers.google.com/clas).
+
+You may have already signed it for other Google projects.
diff --git a/vendor/github.com/google/uuid/CONTRIBUTORS b/vendor/github.com/google/uuid/CONTRIBUTORS
new file mode 100644
index 000000000..b4bb97f6b
--- /dev/null
+++ b/vendor/github.com/google/uuid/CONTRIBUTORS
@@ -0,0 +1,9 @@
+Paul Borman <borman@google.com>
+bmatsuo
+shawnps
+theory
+jboverfelt
+dsymonds
+cd1
+wallclockbuilder
+dansouza
diff --git a/vendor/github.com/google/uuid/LICENSE b/vendor/github.com/google/uuid/LICENSE
new file mode 100644
index 000000000..5dc68268d
--- /dev/null
+++ b/vendor/github.com/google/uuid/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009,2014 Google Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/google/uuid/README.md b/vendor/github.com/google/uuid/README.md
new file mode 100644
index 000000000..9d92c11f1
--- /dev/null
+++ b/vendor/github.com/google/uuid/README.md
@@ -0,0 +1,19 @@
+# uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master)
+The uuid package generates and inspects UUIDs based on
+[RFC 4122](http://tools.ietf.org/html/rfc4122)
+and DCE 1.1: Authentication and Security Services.
+
+This package is based on the github.com/pborman/uuid package (previously named
+code.google.com/p/go-uuid). It differs from these earlier packages in that
+a UUID is a 16 byte array rather than a byte slice. One loss due to this
+change is the ability to represent an invalid UUID (vs a NIL UUID).
+
+###### Install
+`go get github.com/google/uuid`
+
+###### Documentation
+[![GoDoc](https://godoc.org/github.com/google/uuid?status.svg)](http://godoc.org/github.com/google/uuid)
+
+Full `go doc` style documentation for the package can be viewed online without
+installing this package by using the GoDoc site here:
+http://godoc.org/github.com/google/uuid
diff --git a/vendor/github.com/google/uuid/dce.go b/vendor/github.com/google/uuid/dce.go
new file mode 100644
index 000000000..fa820b9d3
--- /dev/null
+++ b/vendor/github.com/google/uuid/dce.go
@@ -0,0 +1,80 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "encoding/binary"
+ "fmt"
+ "os"
+)
+
+// A Domain represents a Version 2 domain
+type Domain byte
+
+// Domain constants for DCE Security (Version 2) UUIDs.
+const (
+ Person = Domain(0)
+ Group = Domain(1)
+ Org = Domain(2)
+)
+
+// NewDCESecurity returns a DCE Security (Version 2) UUID.
+//
+// The domain should be one of Person, Group or Org.
+// On a POSIX system the id should be the users UID for the Person
+// domain and the users GID for the Group. The meaning of id for
+// the domain Org or on non-POSIX systems is site defined.
+//
+// For a given domain/id pair the same token may be returned for up to
+// 7 minutes and 10 seconds.
+func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
+ uuid, err := NewUUID()
+ if err == nil {
+ uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
+ uuid[9] = byte(domain)
+ binary.BigEndian.PutUint32(uuid[0:], id)
+ }
+ return uuid, err
+}
+
+// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
+// domain with the id returned by os.Getuid.
+//
+// NewDCESecurity(Person, uint32(os.Getuid()))
+func NewDCEPerson() (UUID, error) {
+ return NewDCESecurity(Person, uint32(os.Getuid()))
+}
+
+// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
+// domain with the id returned by os.Getgid.
+//
+// NewDCESecurity(Group, uint32(os.Getgid()))
+func NewDCEGroup() (UUID, error) {
+ return NewDCESecurity(Group, uint32(os.Getgid()))
+}
+
+// Domain returns the domain for a Version 2 UUID. Domains are only defined
+// for Version 2 UUIDs.
+func (uuid UUID) Domain() Domain {
+ return Domain(uuid[9])
+}
+
+// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
+// UUIDs.
+func (uuid UUID) ID() uint32 {
+ return binary.BigEndian.Uint32(uuid[0:4])
+}
+
+func (d Domain) String() string {
+ switch d {
+ case Person:
+ return "Person"
+ case Group:
+ return "Group"
+ case Org:
+ return "Org"
+ }
+ return fmt.Sprintf("Domain%d", int(d))
+}
diff --git a/vendor/github.com/google/uuid/doc.go b/vendor/github.com/google/uuid/doc.go
new file mode 100644
index 000000000..5b8a4b9af
--- /dev/null
+++ b/vendor/github.com/google/uuid/doc.go
@@ -0,0 +1,12 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package uuid generates and inspects UUIDs.
+//
+// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
+// Services.
+//
+// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
+// maps or compared directly.
+package uuid
diff --git a/vendor/github.com/google/uuid/go.mod b/vendor/github.com/google/uuid/go.mod
new file mode 100644
index 000000000..fc84cd79d
--- /dev/null
+++ b/vendor/github.com/google/uuid/go.mod
@@ -0,0 +1 @@
+module github.com/google/uuid
diff --git a/vendor/github.com/google/uuid/hash.go b/vendor/github.com/google/uuid/hash.go
new file mode 100644
index 000000000..b17461631
--- /dev/null
+++ b/vendor/github.com/google/uuid/hash.go
@@ -0,0 +1,53 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "crypto/md5"
+ "crypto/sha1"
+ "hash"
+)
+
+// Well known namespace IDs and UUIDs
+var (
+ NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
+ NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
+ NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
+ NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
+ Nil UUID // empty UUID, all zeros
+)
+
+// NewHash returns a new UUID derived from the hash of space concatenated with
+// data generated by h. The hash should be at least 16 byte in length. The
+// first 16 bytes of the hash are used to form the UUID. The version of the
+// UUID will be the lower 4 bits of version. NewHash is used to implement
+// NewMD5 and NewSHA1.
+func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
+ h.Reset()
+ h.Write(space[:])
+ h.Write(data)
+ s := h.Sum(nil)
+ var uuid UUID
+ copy(uuid[:], s)
+ uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
+ uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
+ return uuid
+}
+
+// NewMD5 returns a new MD5 (Version 3) UUID based on the
+// supplied name space and data. It is the same as calling:
+//
+// NewHash(md5.New(), space, data, 3)
+func NewMD5(space UUID, data []byte) UUID {
+ return NewHash(md5.New(), space, data, 3)
+}
+
+// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
+// supplied name space and data. It is the same as calling:
+//
+// NewHash(sha1.New(), space, data, 5)
+func NewSHA1(space UUID, data []byte) UUID {
+ return NewHash(sha1.New(), space, data, 5)
+}
diff --git a/vendor/github.com/google/uuid/marshal.go b/vendor/github.com/google/uuid/marshal.go
new file mode 100644
index 000000000..7f9e0c6c0
--- /dev/null
+++ b/vendor/github.com/google/uuid/marshal.go
@@ -0,0 +1,37 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import "fmt"
+
+// MarshalText implements encoding.TextMarshaler.
+func (uuid UUID) MarshalText() ([]byte, error) {
+ var js [36]byte
+ encodeHex(js[:], uuid)
+ return js[:], nil
+}
+
+// UnmarshalText implements encoding.TextUnmarshaler.
+func (uuid *UUID) UnmarshalText(data []byte) error {
+ id, err := ParseBytes(data)
+ if err == nil {
+ *uuid = id
+ }
+ return err
+}
+
+// MarshalBinary implements encoding.BinaryMarshaler.
+func (uuid UUID) MarshalBinary() ([]byte, error) {
+ return uuid[:], nil
+}
+
+// UnmarshalBinary implements encoding.BinaryUnmarshaler.
+func (uuid *UUID) UnmarshalBinary(data []byte) error {
+ if len(data) != 16 {
+ return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
+ }
+ copy(uuid[:], data)
+ return nil
+}
diff --git a/vendor/github.com/google/uuid/node.go b/vendor/github.com/google/uuid/node.go
new file mode 100644
index 000000000..d651a2b06
--- /dev/null
+++ b/vendor/github.com/google/uuid/node.go
@@ -0,0 +1,90 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "sync"
+)
+
+var (
+ nodeMu sync.Mutex
+ ifname string // name of interface being used
+ nodeID [6]byte // hardware for version 1 UUIDs
+ zeroID [6]byte // nodeID with only 0's
+)
+
+// NodeInterface returns the name of the interface from which the NodeID was
+// derived. The interface "user" is returned if the NodeID was set by
+// SetNodeID.
+func NodeInterface() string {
+ defer nodeMu.Unlock()
+ nodeMu.Lock()
+ return ifname
+}
+
+// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
+// If name is "" then the first usable interface found will be used or a random
+// Node ID will be generated. If a named interface cannot be found then false
+// is returned.
+//
+// SetNodeInterface never fails when name is "".
+func SetNodeInterface(name string) bool {
+ defer nodeMu.Unlock()
+ nodeMu.Lock()
+ return setNodeInterface(name)
+}
+
+func setNodeInterface(name string) bool {
+ iname, addr := getHardwareInterface(name) // null implementation for js
+ if iname != "" && addr != nil {
+ ifname = iname
+ copy(nodeID[:], addr)
+ return true
+ }
+
+ // We found no interfaces with a valid hardware address. If name
+ // does not specify a specific interface generate a random Node ID
+ // (section 4.1.6)
+ if name == "" {
+ ifname = "random"
+ randomBits(nodeID[:])
+ return true
+ }
+ return false
+}
+
+// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
+// if not already set.
+func NodeID() []byte {
+ defer nodeMu.Unlock()
+ nodeMu.Lock()
+ if nodeID == zeroID {
+ setNodeInterface("")
+ }
+ nid := nodeID
+ return nid[:]
+}
+
+// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
+// of id are used. If id is less than 6 bytes then false is returned and the
+// Node ID is not set.
+func SetNodeID(id []byte) bool {
+ if len(id) < 6 {
+ return false
+ }
+ defer nodeMu.Unlock()
+ nodeMu.Lock()
+ copy(nodeID[:], id)
+ ifname = "user"
+ return true
+}
+
+// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
+// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
+func (uuid UUID) NodeID() []byte {
+ var node [6]byte
+ copy(node[:], uuid[10:])
+ return node[:]
+}
diff --git a/vendor/github.com/google/uuid/node_js.go b/vendor/github.com/google/uuid/node_js.go
new file mode 100644
index 000000000..24b78edc9
--- /dev/null
+++ b/vendor/github.com/google/uuid/node_js.go
@@ -0,0 +1,12 @@
+// Copyright 2017 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build js
+
+package uuid
+
+// getHardwareInterface returns nil values for the JS version of the code.
+// This remvoves the "net" dependency, because it is not used in the browser.
+// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
+func getHardwareInterface(name string) (string, []byte) { return "", nil }
diff --git a/vendor/github.com/google/uuid/node_net.go b/vendor/github.com/google/uuid/node_net.go
new file mode 100644
index 000000000..0cbbcddbd
--- /dev/null
+++ b/vendor/github.com/google/uuid/node_net.go
@@ -0,0 +1,33 @@
+// Copyright 2017 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !js
+
+package uuid
+
+import "net"
+
+var interfaces []net.Interface // cached list of interfaces
+
+// getHardwareInterface returns the name and hardware address of interface name.
+// If name is "" then the name and hardware address of one of the system's
+// interfaces is returned. If no interfaces are found (name does not exist or
+// there are no interfaces) then "", nil is returned.
+//
+// Only addresses of at least 6 bytes are returned.
+func getHardwareInterface(name string) (string, []byte) {
+ if interfaces == nil {
+ var err error
+ interfaces, err = net.Interfaces()
+ if err != nil {
+ return "", nil
+ }
+ }
+ for _, ifs := range interfaces {
+ if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
+ return ifs.Name, ifs.HardwareAddr
+ }
+ }
+ return "", nil
+}
diff --git a/vendor/github.com/google/uuid/sql.go b/vendor/github.com/google/uuid/sql.go
new file mode 100644
index 000000000..f326b54db
--- /dev/null
+++ b/vendor/github.com/google/uuid/sql.go
@@ -0,0 +1,59 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "database/sql/driver"
+ "fmt"
+)
+
+// Scan implements sql.Scanner so UUIDs can be read from databases transparently
+// Currently, database types that map to string and []byte are supported. Please
+// consult database-specific driver documentation for matching types.
+func (uuid *UUID) Scan(src interface{}) error {
+ switch src := src.(type) {
+ case nil:
+ return nil
+
+ case string:
+ // if an empty UUID comes from a table, we return a null UUID
+ if src == "" {
+ return nil
+ }
+
+ // see Parse for required string format
+ u, err := Parse(src)
+ if err != nil {
+ return fmt.Errorf("Scan: %v", err)
+ }
+
+ *uuid = u
+
+ case []byte:
+ // if an empty UUID comes from a table, we return a null UUID
+ if len(src) == 0 {
+ return nil
+ }
+
+ // assumes a simple slice of bytes if 16 bytes
+ // otherwise attempts to parse
+ if len(src) != 16 {
+ return uuid.Scan(string(src))
+ }
+ copy((*uuid)[:], src)
+
+ default:
+ return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
+ }
+
+ return nil
+}
+
+// Value implements sql.Valuer so that UUIDs can be written to databases
+// transparently. Currently, UUIDs map to strings. Please consult
+// database-specific driver documentation for matching types.
+func (uuid UUID) Value() (driver.Value, error) {
+ return uuid.String(), nil
+}
diff --git a/vendor/github.com/google/uuid/time.go b/vendor/github.com/google/uuid/time.go
new file mode 100644
index 000000000..e6ef06cdc
--- /dev/null
+++ b/vendor/github.com/google/uuid/time.go
@@ -0,0 +1,123 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "encoding/binary"
+ "sync"
+ "time"
+)
+
+// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
+// 1582.
+type Time int64
+
+const (
+ lillian = 2299160 // Julian day of 15 Oct 1582
+ unix = 2440587 // Julian day of 1 Jan 1970
+ epoch = unix - lillian // Days between epochs
+ g1582 = epoch * 86400 // seconds between epochs
+ g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
+)
+
+var (
+ timeMu sync.Mutex
+ lasttime uint64 // last time we returned
+ clockSeq uint16 // clock sequence for this run
+
+ timeNow = time.Now // for testing
+)
+
+// UnixTime converts t the number of seconds and nanoseconds using the Unix
+// epoch of 1 Jan 1970.
+func (t Time) UnixTime() (sec, nsec int64) {
+ sec = int64(t - g1582ns100)
+ nsec = (sec % 10000000) * 100
+ sec /= 10000000
+ return sec, nsec
+}
+
+// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
+// clock sequence as well as adjusting the clock sequence as needed. An error
+// is returned if the current time cannot be determined.
+func GetTime() (Time, uint16, error) {
+ defer timeMu.Unlock()
+ timeMu.Lock()
+ return getTime()
+}
+
+func getTime() (Time, uint16, error) {
+ t := timeNow()
+
+ // If we don't have a clock sequence already, set one.
+ if clockSeq == 0 {
+ setClockSequence(-1)
+ }
+ now := uint64(t.UnixNano()/100) + g1582ns100
+
+ // If time has gone backwards with this clock sequence then we
+ // increment the clock sequence
+ if now <= lasttime {
+ clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
+ }
+ lasttime = now
+ return Time(now), clockSeq, nil
+}
+
+// ClockSequence returns the current clock sequence, generating one if not
+// already set. The clock sequence is only used for Version 1 UUIDs.
+//
+// The uuid package does not use global static storage for the clock sequence or
+// the last time a UUID was generated. Unless SetClockSequence is used, a new
+// random clock sequence is generated the first time a clock sequence is
+// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
+func ClockSequence() int {
+ defer timeMu.Unlock()
+ timeMu.Lock()
+ return clockSequence()
+}
+
+func clockSequence() int {
+ if clockSeq == 0 {
+ setClockSequence(-1)
+ }
+ return int(clockSeq & 0x3fff)
+}
+
+// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to
+// -1 causes a new sequence to be generated.
+func SetClockSequence(seq int) {
+ defer timeMu.Unlock()
+ timeMu.Lock()
+ setClockSequence(seq)
+}
+
+func setClockSequence(seq int) {
+ if seq == -1 {
+ var b [2]byte
+ randomBits(b[:]) // clock sequence
+ seq = int(b[0])<<8 | int(b[1])
+ }
+ oldSeq := clockSeq
+ clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
+ if oldSeq != clockSeq {
+ lasttime = 0
+ }
+}
+
+// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
+// uuid. The time is only defined for version 1 and 2 UUIDs.
+func (uuid UUID) Time() Time {
+ time := int64(binary.BigEndian.Uint32(uuid[0:4]))
+ time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
+ time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
+ return Time(time)
+}
+
+// ClockSequence returns the clock sequence encoded in uuid.
+// The clock sequence is only well defined for version 1 and 2 UUIDs.
+func (uuid UUID) ClockSequence() int {
+ return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
+}
diff --git a/vendor/github.com/google/uuid/util.go b/vendor/github.com/google/uuid/util.go
new file mode 100644
index 000000000..5ea6c7378
--- /dev/null
+++ b/vendor/github.com/google/uuid/util.go
@@ -0,0 +1,43 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "io"
+)
+
+// randomBits completely fills slice b with random data.
+func randomBits(b []byte) {
+ if _, err := io.ReadFull(rander, b); err != nil {
+ panic(err.Error()) // rand should never fail
+ }
+}
+
+// xvalues returns the value of a byte as a hexadecimal digit or 255.
+var xvalues = [256]byte{
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
+ 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+}
+
+// xtob converts hex characters x1 and x2 into a byte.
+func xtob(x1, x2 byte) (byte, bool) {
+ b1 := xvalues[x1]
+ b2 := xvalues[x2]
+ return (b1 << 4) | b2, b1 != 255 && b2 != 255
+}
diff --git a/vendor/github.com/google/uuid/uuid.go b/vendor/github.com/google/uuid/uuid.go
new file mode 100644
index 000000000..524404cc5
--- /dev/null
+++ b/vendor/github.com/google/uuid/uuid.go
@@ -0,0 +1,245 @@
+// Copyright 2018 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "io"
+ "strings"
+)
+
+// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
+// 4122.
+type UUID [16]byte
+
+// A Version represents a UUID's version.
+type Version byte
+
+// A Variant represents a UUID's variant.
+type Variant byte
+
+// Constants returned by Variant.
+const (
+ Invalid = Variant(iota) // Invalid UUID
+ RFC4122 // The variant specified in RFC4122
+ Reserved // Reserved, NCS backward compatibility.
+ Microsoft // Reserved, Microsoft Corporation backward compatibility.
+ Future // Reserved for future definition.
+)
+
+var rander = rand.Reader // random function
+
+// Parse decodes s into a UUID or returns an error. Both the standard UUID
+// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
+// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the
+// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex
+// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
+func Parse(s string) (UUID, error) {
+ var uuid UUID
+ switch len(s) {
+ // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+ case 36:
+
+ // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+ case 36 + 9:
+ if strings.ToLower(s[:9]) != "urn:uuid:" {
+ return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
+ }
+ s = s[9:]
+
+ // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
+ case 36 + 2:
+ s = s[1:]
+
+ // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ case 32:
+ var ok bool
+ for i := range uuid {
+ uuid[i], ok = xtob(s[i*2], s[i*2+1])
+ if !ok {
+ return uuid, errors.New("invalid UUID format")
+ }
+ }
+ return uuid, nil
+ default:
+ return uuid, fmt.Errorf("invalid UUID length: %d", len(s))
+ }
+ // s is now at least 36 bytes long
+ // it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+ if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
+ return uuid, errors.New("invalid UUID format")
+ }
+ for i, x := range [16]int{
+ 0, 2, 4, 6,
+ 9, 11,
+ 14, 16,
+ 19, 21,
+ 24, 26, 28, 30, 32, 34} {
+ v, ok := xtob(s[x], s[x+1])
+ if !ok {
+ return uuid, errors.New("invalid UUID format")
+ }
+ uuid[i] = v
+ }
+ return uuid, nil
+}
+
+// ParseBytes is like Parse, except it parses a byte slice instead of a string.
+func ParseBytes(b []byte) (UUID, error) {
+ var uuid UUID
+ switch len(b) {
+ case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+ case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+ if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) {
+ return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
+ }
+ b = b[9:]
+ case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
+ b = b[1:]
+ case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ var ok bool
+ for i := 0; i < 32; i += 2 {
+ uuid[i/2], ok = xtob(b[i], b[i+1])
+ if !ok {
+ return uuid, errors.New("invalid UUID format")
+ }
+ }
+ return uuid, nil
+ default:
+ return uuid, fmt.Errorf("invalid UUID length: %d", len(b))
+ }
+ // s is now at least 36 bytes long
+ // it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+ if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
+ return uuid, errors.New("invalid UUID format")
+ }
+ for i, x := range [16]int{
+ 0, 2, 4, 6,
+ 9, 11,
+ 14, 16,
+ 19, 21,
+ 24, 26, 28, 30, 32, 34} {
+ v, ok := xtob(b[x], b[x+1])
+ if !ok {
+ return uuid, errors.New("invalid UUID format")
+ }
+ uuid[i] = v
+ }
+ return uuid, nil
+}
+
+// MustParse is like Parse but panics if the string cannot be parsed.
+// It simplifies safe initialization of global variables holding compiled UUIDs.
+func MustParse(s string) UUID {
+ uuid, err := Parse(s)
+ if err != nil {
+ panic(`uuid: Parse(` + s + `): ` + err.Error())
+ }
+ return uuid
+}
+
+// FromBytes creates a new UUID from a byte slice. Returns an error if the slice
+// does not have a length of 16. The bytes are copied from the slice.
+func FromBytes(b []byte) (uuid UUID, err error) {
+ err = uuid.UnmarshalBinary(b)
+ return uuid, err
+}
+
+// Must returns uuid if err is nil and panics otherwise.
+func Must(uuid UUID, err error) UUID {
+ if err != nil {
+ panic(err)
+ }
+ return uuid
+}
+
+// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+// , or "" if uuid is invalid.
+func (uuid UUID) String() string {
+ var buf [36]byte
+ encodeHex(buf[:], uuid)
+ return string(buf[:])
+}
+
+// URN returns the RFC 2141 URN form of uuid,
+// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
+func (uuid UUID) URN() string {
+ var buf [36 + 9]byte
+ copy(buf[:], "urn:uuid:")
+ encodeHex(buf[9:], uuid)
+ return string(buf[:])
+}
+
+func encodeHex(dst []byte, uuid UUID) {
+ hex.Encode(dst, uuid[:4])
+ dst[8] = '-'
+ hex.Encode(dst[9:13], uuid[4:6])
+ dst[13] = '-'
+ hex.Encode(dst[14:18], uuid[6:8])
+ dst[18] = '-'
+ hex.Encode(dst[19:23], uuid[8:10])
+ dst[23] = '-'
+ hex.Encode(dst[24:], uuid[10:])
+}
+
+// Variant returns the variant encoded in uuid.
+func (uuid UUID) Variant() Variant {
+ switch {
+ case (uuid[8] & 0xc0) == 0x80:
+ return RFC4122
+ case (uuid[8] & 0xe0) == 0xc0:
+ return Microsoft
+ case (uuid[8] & 0xe0) == 0xe0:
+ return Future
+ default:
+ return Reserved
+ }
+}
+
+// Version returns the version of uuid.
+func (uuid UUID) Version() Version {
+ return Version(uuid[6] >> 4)
+}
+
+func (v Version) String() string {
+ if v > 15 {
+ return fmt.Sprintf("BAD_VERSION_%d", v)
+ }
+ return fmt.Sprintf("VERSION_%d", v)
+}
+
+func (v Variant) String() string {
+ switch v {
+ case RFC4122:
+ return "RFC4122"
+ case Reserved:
+ return "Reserved"
+ case Microsoft:
+ return "Microsoft"
+ case Future:
+ return "Future"
+ case Invalid:
+ return "Invalid"
+ }
+ return fmt.Sprintf("BadVariant%d", int(v))
+}
+
+// SetRand sets the random number generator to r, which implements io.Reader.
+// If r.Read returns an error when the package requests random data then
+// a panic will be issued.
+//
+// Calling SetRand with nil sets the random number generator to the default
+// generator.
+func SetRand(r io.Reader) {
+ if r == nil {
+ rander = rand.Reader
+ return
+ }
+ rander = r
+}
diff --git a/vendor/github.com/google/uuid/version1.go b/vendor/github.com/google/uuid/version1.go
new file mode 100644
index 000000000..199a1ac65
--- /dev/null
+++ b/vendor/github.com/google/uuid/version1.go
@@ -0,0 +1,44 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "encoding/binary"
+)
+
+// NewUUID returns a Version 1 UUID based on the current NodeID and clock
+// sequence, and the current time. If the NodeID has not been set by SetNodeID
+// or SetNodeInterface then it will be set automatically. If the NodeID cannot
+// be set NewUUID returns nil. If clock sequence has not been set by
+// SetClockSequence then it will be set automatically. If GetTime fails to
+// return the current NewUUID returns nil and an error.
+//
+// In most cases, New should be used.
+func NewUUID() (UUID, error) {
+ nodeMu.Lock()
+ if nodeID == zeroID {
+ setNodeInterface("")
+ }
+ nodeMu.Unlock()
+
+ var uuid UUID
+ now, seq, err := GetTime()
+ if err != nil {
+ return uuid, err
+ }
+
+ timeLow := uint32(now & 0xffffffff)
+ timeMid := uint16((now >> 32) & 0xffff)
+ timeHi := uint16((now >> 48) & 0x0fff)
+ timeHi |= 0x1000 // Version 1
+
+ binary.BigEndian.PutUint32(uuid[0:], timeLow)
+ binary.BigEndian.PutUint16(uuid[4:], timeMid)
+ binary.BigEndian.PutUint16(uuid[6:], timeHi)
+ binary.BigEndian.PutUint16(uuid[8:], seq)
+ copy(uuid[10:], nodeID[:])
+
+ return uuid, nil
+}
diff --git a/vendor/github.com/google/uuid/version4.go b/vendor/github.com/google/uuid/version4.go
new file mode 100644
index 000000000..84af91c9f
--- /dev/null
+++ b/vendor/github.com/google/uuid/version4.go
@@ -0,0 +1,38 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import "io"
+
+// New creates a new random UUID or panics. New is equivalent to
+// the expression
+//
+// uuid.Must(uuid.NewRandom())
+func New() UUID {
+ return Must(NewRandom())
+}
+
+// NewRandom returns a Random (Version 4) UUID.
+//
+// The strength of the UUIDs is based on the strength of the crypto/rand
+// package.
+//
+// A note about uniqueness derived from the UUID Wikipedia entry:
+//
+// Randomly generated UUIDs have 122 random bits. One's annual risk of being
+// hit by a meteorite is estimated to be one chance in 17 billion, that
+// means the probability is about 0.00000000006 (6 × 10−11),
+// equivalent to the odds of creating a few tens of trillions of UUIDs in a
+// year and having one duplicate.
+func NewRandom() (UUID, error) {
+ var uuid UUID
+ _, err := io.ReadFull(rander, uuid[:])
+ if err != nil {
+ return Nil, err
+ }
+ uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
+ uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
+ return uuid, nil
+}
diff --git a/vendor/github.com/gorilla/schema/.travis.yml b/vendor/github.com/gorilla/schema/.travis.yml
new file mode 100644
index 000000000..5f51dce4e
--- /dev/null
+++ b/vendor/github.com/gorilla/schema/.travis.yml
@@ -0,0 +1,18 @@
+language: go
+sudo: false
+
+matrix:
+ include:
+ - go: 1.5
+ - go: 1.6
+ - go: 1.7
+ - go: 1.8
+ - go: tip
+ allow_failures:
+ - go: tip
+
+script:
+ - go get -t -v ./...
+ - diff -u <(echo -n) <(gofmt -d .)
+ - go vet $(go list ./... | grep -v /vendor/)
+ - go test -v -race ./...
diff --git a/vendor/github.com/gorilla/schema/LICENSE b/vendor/github.com/gorilla/schema/LICENSE
new file mode 100644
index 000000000..0e5fb8728
--- /dev/null
+++ b/vendor/github.com/gorilla/schema/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/gorilla/schema/README.md b/vendor/github.com/gorilla/schema/README.md
new file mode 100644
index 000000000..aefdd6699
--- /dev/null
+++ b/vendor/github.com/gorilla/schema/README.md
@@ -0,0 +1,90 @@
+schema
+======
+[![GoDoc](https://godoc.org/github.com/gorilla/schema?status.svg)](https://godoc.org/github.com/gorilla/schema) [![Build Status](https://travis-ci.org/gorilla/schema.png?branch=master)](https://travis-ci.org/gorilla/schema)
+[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/schema/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/schema?badge)
+
+
+Package gorilla/schema converts structs to and from form values.
+
+## Example
+
+Here's a quick example: we parse POST form values and then decode them into a struct:
+
+```go
+// Set a Decoder instance as a package global, because it caches
+// meta-data about structs, and an instance can be shared safely.
+var decoder = schema.NewDecoder()
+
+type Person struct {
+ Name string
+ Phone string
+}
+
+func MyHandler(w http.ResponseWriter, r *http.Request) {
+ err := r.ParseForm()
+ if err != nil {
+ // Handle error
+ }
+
+ var person Person
+
+ // r.PostForm is a map of our POST form values
+ err = decoder.Decode(&person, r.PostForm)
+ if err != nil {
+ // Handle error
+ }
+
+ // Do something with person.Name or person.Phone
+}
+```
+
+Conversely, contents of a struct can be encoded into form values. Here's a variant of the previous example using the Encoder:
+
+```go
+var encoder = schema.NewEncoder()
+
+func MyHttpRequest() {
+ person := Person{"Jane Doe", "555-5555"}
+ form := url.Values{}
+
+ err := encoder.Encode(person, form)
+
+ if err != nil {
+ // Handle error
+ }
+
+ // Use form values, for example, with an http client
+ client := new(http.Client)
+ res, err := client.PostForm("http://my-api.test", form)
+}
+
+```
+
+To define custom names for fields, use a struct tag "schema". To not populate certain fields, use a dash for the name and it will be ignored:
+
+```go
+type Person struct {
+ Name string `schema:"name,required"` // custom name, must be supplied
+ Phone string `schema:"phone"` // custom name
+ Admin bool `schema:"-"` // this field is never set
+}
+```
+
+The supported field types in the struct are:
+
+* bool
+* float variants (float32, float64)
+* int variants (int, int8, int16, int32, int64)
+* string
+* uint variants (uint, uint8, uint16, uint32, uint64)
+* struct
+* a pointer to one of the above types
+* a slice or a pointer to a slice of one of the above types
+
+Unsupported types are simply ignored, however custom types can be registered to be converted.
+
+More examples are available on the Gorilla website: https://www.gorillatoolkit.org/pkg/schema
+
+## License
+
+BSD licensed. See the LICENSE file for details.
diff --git a/vendor/github.com/gorilla/schema/cache.go b/vendor/github.com/gorilla/schema/cache.go
new file mode 100644
index 000000000..0746c1202
--- /dev/null
+++ b/vendor/github.com/gorilla/schema/cache.go
@@ -0,0 +1,305 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package schema
+
+import (
+ "errors"
+ "reflect"
+ "strconv"
+ "strings"
+ "sync"
+)
+
+var invalidPath = errors.New("schema: invalid path")
+
+// newCache returns a new cache.
+func newCache() *cache {
+ c := cache{
+ m: make(map[reflect.Type]*structInfo),
+ regconv: make(map[reflect.Type]Converter),
+ tag: "schema",
+ }
+ return &c
+}
+
+// cache caches meta-data about a struct.
+type cache struct {
+ l sync.RWMutex
+ m map[reflect.Type]*structInfo
+ regconv map[reflect.Type]Converter
+ tag string
+}
+
+// registerConverter registers a converter function for a custom type.
+func (c *cache) registerConverter(value interface{}, converterFunc Converter) {
+ c.regconv[reflect.TypeOf(value)] = converterFunc
+}
+
+// parsePath parses a path in dotted notation verifying that it is a valid
+// path to a struct field.
+//
+// It returns "path parts" which contain indices to fields to be used by
+// reflect.Value.FieldByString(). Multiple parts are required for slices of
+// structs.
+func (c *cache) parsePath(p string, t reflect.Type) ([]pathPart, error) {
+ var struc *structInfo
+ var field *fieldInfo
+ var index64 int64
+ var err error
+ parts := make([]pathPart, 0)
+ path := make([]string, 0)
+ keys := strings.Split(p, ".")
+ for i := 0; i < len(keys); i++ {
+ if t.Kind() != reflect.Struct {
+ return nil, invalidPath
+ }
+ if struc = c.get(t); struc == nil {
+ return nil, invalidPath
+ }
+ if field = struc.get(keys[i]); field == nil {
+ return nil, invalidPath
+ }
+ // Valid field. Append index.
+ path = append(path, field.name)
+ if field.isSliceOfStructs && (!field.unmarshalerInfo.IsValid || (field.unmarshalerInfo.IsValid && field.unmarshalerInfo.IsSliceElement)) {
+ // Parse a special case: slices of structs.
+ // i+1 must be the slice index.
+ //
+ // Now that struct can implements TextUnmarshaler interface,
+ // we don't need to force the struct's fields to appear in the path.
+ // So checking i+2 is not necessary anymore.
+ i++
+ if i+1 > len(keys) {
+ return nil, invalidPath
+ }
+ if index64, err = strconv.ParseInt(keys[i], 10, 0); err != nil {
+ return nil, invalidPath
+ }
+ parts = append(parts, pathPart{
+ path: path,
+ field: field,
+ index: int(index64),
+ })
+ path = make([]string, 0)
+
+ // Get the next struct type, dropping ptrs.
+ if field.typ.Kind() == reflect.Ptr {
+ t = field.typ.Elem()
+ } else {
+ t = field.typ
+ }
+ if t.Kind() == reflect.Slice {
+ t = t.Elem()
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+ }
+ } else if field.typ.Kind() == reflect.Ptr {
+ t = field.typ.Elem()
+ } else {
+ t = field.typ
+ }
+ }
+ // Add the remaining.
+ parts = append(parts, pathPart{
+ path: path,
+ field: field,
+ index: -1,
+ })
+ return parts, nil
+}
+
+// get returns a cached structInfo, creating it if necessary.
+func (c *cache) get(t reflect.Type) *structInfo {
+ c.l.RLock()
+ info := c.m[t]
+ c.l.RUnlock()
+ if info == nil {
+ info = c.create(t, "")
+ c.l.Lock()
+ c.m[t] = info
+ c.l.Unlock()
+ }
+ return info
+}
+
+// create creates a structInfo with meta-data about a struct.
+func (c *cache) create(t reflect.Type, parentAlias string) *structInfo {
+ info := &structInfo{}
+ var anonymousInfos []*structInfo
+ for i := 0; i < t.NumField(); i++ {
+ if f := c.createField(t.Field(i), parentAlias); f != nil {
+ info.fields = append(info.fields, f)
+ if ft := indirectType(f.typ); ft.Kind() == reflect.Struct && f.isAnonymous {
+ anonymousInfos = append(anonymousInfos, c.create(ft, f.canonicalAlias))
+ }
+ }
+ }
+ for i, a := range anonymousInfos {
+ others := []*structInfo{info}
+ others = append(others, anonymousInfos[:i]...)
+ others = append(others, anonymousInfos[i+1:]...)
+ for _, f := range a.fields {
+ if !containsAlias(others, f.alias) {
+ info.fields = append(info.fields, f)
+ }
+ }
+ }
+ return info
+}
+
+// createField creates a fieldInfo for the given field.
+func (c *cache) createField(field reflect.StructField, parentAlias string) *fieldInfo {
+ alias, options := fieldAlias(field, c.tag)
+ if alias == "-" {
+ // Ignore this field.
+ return nil
+ }
+ canonicalAlias := alias
+ if parentAlias != "" {
+ canonicalAlias = parentAlias + "." + alias
+ }
+ // Check if the type is supported and don't cache it if not.
+ // First let's get the basic type.
+ isSlice, isStruct := false, false
+ ft := field.Type
+ m := isTextUnmarshaler(reflect.Zero(ft))
+ if ft.Kind() == reflect.Ptr {
+ ft = ft.Elem()
+ }
+ if isSlice = ft.Kind() == reflect.Slice; isSlice {
+ ft = ft.Elem()
+ if ft.Kind() == reflect.Ptr {
+ ft = ft.Elem()
+ }
+ }
+ if ft.Kind() == reflect.Array {
+ ft = ft.Elem()
+ if ft.Kind() == reflect.Ptr {
+ ft = ft.Elem()
+ }
+ }
+ if isStruct = ft.Kind() == reflect.Struct; !isStruct {
+ if c.converter(ft) == nil && builtinConverters[ft.Kind()] == nil {
+ // Type is not supported.
+ return nil
+ }
+ }
+
+ return &fieldInfo{
+ typ: field.Type,
+ name: field.Name,
+ alias: alias,
+ canonicalAlias: canonicalAlias,
+ unmarshalerInfo: m,
+ isSliceOfStructs: isSlice && isStruct,
+ isAnonymous: field.Anonymous,
+ isRequired: options.Contains("required"),
+ }
+}
+
+// converter returns the converter for a type.
+func (c *cache) converter(t reflect.Type) Converter {
+ return c.regconv[t]
+}
+
+// ----------------------------------------------------------------------------
+
+type structInfo struct {
+ fields []*fieldInfo
+}
+
+func (i *structInfo) get(alias string) *fieldInfo {
+ for _, field := range i.fields {
+ if strings.EqualFold(field.alias, alias) {
+ return field
+ }
+ }
+ return nil
+}
+
+func containsAlias(infos []*structInfo, alias string) bool {
+ for _, info := range infos {
+ if info.get(alias) != nil {
+ return true
+ }
+ }
+ return false
+}
+
+type fieldInfo struct {
+ typ reflect.Type
+ // name is the field name in the struct.
+ name string
+ alias string
+ // canonicalAlias is almost the same as the alias, but is prefixed with
+ // an embedded struct field alias in dotted notation if this field is
+ // promoted from the struct.
+ // For instance, if the alias is "N" and this field is an embedded field
+ // in a struct "X", canonicalAlias will be "X.N".
+ canonicalAlias string
+ // unmarshalerInfo contains information regarding the
+ // encoding.TextUnmarshaler implementation of the field type.
+ unmarshalerInfo unmarshaler
+ // isSliceOfStructs indicates if the field type is a slice of structs.
+ isSliceOfStructs bool
+ // isAnonymous indicates whether the field is embedded in the struct.
+ isAnonymous bool
+ isRequired bool
+}
+
+func (f *fieldInfo) paths(prefix string) []string {
+ if f.alias == f.canonicalAlias {
+ return []string{prefix + f.alias}
+ }
+ return []string{prefix + f.alias, prefix + f.canonicalAlias}
+}
+
+type pathPart struct {
+ field *fieldInfo
+ path []string // path to the field: walks structs using field names.
+ index int // struct index in slices of structs.
+}
+
+// ----------------------------------------------------------------------------
+
+func indirectType(typ reflect.Type) reflect.Type {
+ if typ.Kind() == reflect.Ptr {
+ return typ.Elem()
+ }
+ return typ
+}
+
+// fieldAlias parses a field tag to get a field alias.
+func fieldAlias(field reflect.StructField, tagName string) (alias string, options tagOptions) {
+ if tag := field.Tag.Get(tagName); tag != "" {
+ alias, options = parseTag(tag)
+ }
+ if alias == "" {
+ alias = field.Name
+ }
+ return alias, options
+}
+
+// tagOptions is the string following a comma in a struct field's tag, or
+// the empty string. It does not include the leading comma.
+type tagOptions []string
+
+// parseTag splits a struct field's url tag into its name and comma-separated
+// options.
+func parseTag(tag string) (string, tagOptions) {
+ s := strings.Split(tag, ",")
+ return s[0], s[1:]
+}
+
+// Contains checks whether the tagOptions contains the specified option.
+func (o tagOptions) Contains(option string) bool {
+ for _, s := range o {
+ if s == option {
+ return true
+ }
+ }
+ return false
+}
diff --git a/vendor/github.com/gorilla/schema/converter.go b/vendor/github.com/gorilla/schema/converter.go
new file mode 100644
index 000000000..4f2116a15
--- /dev/null
+++ b/vendor/github.com/gorilla/schema/converter.go
@@ -0,0 +1,145 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package schema
+
+import (
+ "reflect"
+ "strconv"
+)
+
+type Converter func(string) reflect.Value
+
+var (
+ invalidValue = reflect.Value{}
+ boolType = reflect.Bool
+ float32Type = reflect.Float32
+ float64Type = reflect.Float64
+ intType = reflect.Int
+ int8Type = reflect.Int8
+ int16Type = reflect.Int16
+ int32Type = reflect.Int32
+ int64Type = reflect.Int64
+ stringType = reflect.String
+ uintType = reflect.Uint
+ uint8Type = reflect.Uint8
+ uint16Type = reflect.Uint16
+ uint32Type = reflect.Uint32
+ uint64Type = reflect.Uint64
+)
+
+// Default converters for basic types.
+var builtinConverters = map[reflect.Kind]Converter{
+ boolType: convertBool,
+ float32Type: convertFloat32,
+ float64Type: convertFloat64,
+ intType: convertInt,
+ int8Type: convertInt8,
+ int16Type: convertInt16,
+ int32Type: convertInt32,
+ int64Type: convertInt64,
+ stringType: convertString,
+ uintType: convertUint,
+ uint8Type: convertUint8,
+ uint16Type: convertUint16,
+ uint32Type: convertUint32,
+ uint64Type: convertUint64,
+}
+
+func convertBool(value string) reflect.Value {
+ if value == "on" {
+ return reflect.ValueOf(true)
+ } else if v, err := strconv.ParseBool(value); err == nil {
+ return reflect.ValueOf(v)
+ }
+ return invalidValue
+}
+
+func convertFloat32(value string) reflect.Value {
+ if v, err := strconv.ParseFloat(value, 32); err == nil {
+ return reflect.ValueOf(float32(v))
+ }
+ return invalidValue
+}
+
+func convertFloat64(value string) reflect.Value {
+ if v, err := strconv.ParseFloat(value, 64); err == nil {
+ return reflect.ValueOf(v)
+ }
+ return invalidValue
+}
+
+func convertInt(value string) reflect.Value {
+ if v, err := strconv.ParseInt(value, 10, 0); err == nil {
+ return reflect.ValueOf(int(v))
+ }
+ return invalidValue
+}
+
+func convertInt8(value string) reflect.Value {
+ if v, err := strconv.ParseInt(value, 10, 8); err == nil {
+ return reflect.ValueOf(int8(v))
+ }
+ return invalidValue
+}
+
+func convertInt16(value string) reflect.Value {
+ if v, err := strconv.ParseInt(value, 10, 16); err == nil {
+ return reflect.ValueOf(int16(v))
+ }
+ return invalidValue
+}
+
+func convertInt32(value string) reflect.Value {
+ if v, err := strconv.ParseInt(value, 10, 32); err == nil {
+ return reflect.ValueOf(int32(v))
+ }
+ return invalidValue
+}
+
+func convertInt64(value string) reflect.Value {
+ if v, err := strconv.ParseInt(value, 10, 64); err == nil {
+ return reflect.ValueOf(v)
+ }
+ return invalidValue
+}
+
+func convertString(value string) reflect.Value {
+ return reflect.ValueOf(value)
+}
+
+func convertUint(value string) reflect.Value {
+ if v, err := strconv.ParseUint(value, 10, 0); err == nil {
+ return reflect.ValueOf(uint(v))
+ }
+ return invalidValue
+}
+
+func convertUint8(value string) reflect.Value {
+ if v, err := strconv.ParseUint(value, 10, 8); err == nil {
+ return reflect.ValueOf(uint8(v))
+ }
+ return invalidValue
+}
+
+func convertUint16(value string) reflect.Value {
+ if v, err := strconv.ParseUint(value, 10, 16); err == nil {
+ return reflect.ValueOf(uint16(v))
+ }
+ return invalidValue
+}
+
+func convertUint32(value string) reflect.Value {
+ if v, err := strconv.ParseUint(value, 10, 32); err == nil {
+ return reflect.ValueOf(uint32(v))
+ }
+ return invalidValue
+}
+
+func convertUint64(value string) reflect.Value {
+ if v, err := strconv.ParseUint(value, 10, 64); err == nil {
+ return reflect.ValueOf(v)
+ }
+ return invalidValue
+}
diff --git a/vendor/github.com/gorilla/schema/decoder.go b/vendor/github.com/gorilla/schema/decoder.go
new file mode 100644
index 000000000..5afbd921f
--- /dev/null
+++ b/vendor/github.com/gorilla/schema/decoder.go
@@ -0,0 +1,504 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package schema
+
+import (
+ "encoding"
+ "errors"
+ "fmt"
+ "reflect"
+ "strings"
+)
+
+// NewDecoder returns a new Decoder.
+func NewDecoder() *Decoder {
+ return &Decoder{cache: newCache()}
+}
+
+// Decoder decodes values from a map[string][]string to a struct.
+type Decoder struct {
+ cache *cache
+ zeroEmpty bool
+ ignoreUnknownKeys bool
+}
+
+// SetAliasTag changes the tag used to locate custom field aliases.
+// The default tag is "schema".
+func (d *Decoder) SetAliasTag(tag string) {
+ d.cache.tag = tag
+}
+
+// ZeroEmpty controls the behaviour when the decoder encounters empty values
+// in a map.
+// If z is true and a key in the map has the empty string as a value
+// then the corresponding struct field is set to the zero value.
+// If z is false then empty strings are ignored.
+//
+// The default value is false, that is empty values do not change
+// the value of the struct field.
+func (d *Decoder) ZeroEmpty(z bool) {
+ d.zeroEmpty = z
+}
+
+// IgnoreUnknownKeys controls the behaviour when the decoder encounters unknown
+// keys in the map.
+// If i is true and an unknown field is encountered, it is ignored. This is
+// similar to how unknown keys are handled by encoding/json.
+// If i is false then Decode will return an error. Note that any valid keys
+// will still be decoded in to the target struct.
+//
+// To preserve backwards compatibility, the default value is false.
+func (d *Decoder) IgnoreUnknownKeys(i bool) {
+ d.ignoreUnknownKeys = i
+}
+
+// RegisterConverter registers a converter function for a custom type.
+func (d *Decoder) RegisterConverter(value interface{}, converterFunc Converter) {
+ d.cache.registerConverter(value, converterFunc)
+}
+
+// Decode decodes a map[string][]string to a struct.
+//
+// The first parameter must be a pointer to a struct.
+//
+// The second parameter is a map, typically url.Values from an HTTP request.
+// Keys are "paths" in dotted notation to the struct fields and nested structs.
+//
+// See the package documentation for a full explanation of the mechanics.
+func (d *Decoder) Decode(dst interface{}, src map[string][]string) error {
+ v := reflect.ValueOf(dst)
+ if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
+ return errors.New("schema: interface must be a pointer to struct")
+ }
+ v = v.Elem()
+ t := v.Type()
+ errors := MultiError{}
+ for path, values := range src {
+ if parts, err := d.cache.parsePath(path, t); err == nil {
+ if err = d.decode(v, path, parts, values); err != nil {
+ errors[path] = err
+ }
+ } else if !d.ignoreUnknownKeys {
+ errors[path] = UnknownKeyError{Key: path}
+ }
+ }
+ errors.merge(d.checkRequired(t, src))
+ if len(errors) > 0 {
+ return errors
+ }
+ return nil
+}
+
+// checkRequired checks whether required fields are empty
+//
+// check type t recursively if t has struct fields.
+//
+// src is the source map for decoding, we use it here to see if those required fields are included in src
+func (d *Decoder) checkRequired(t reflect.Type, src map[string][]string) MultiError {
+ m, errs := d.findRequiredFields(t, "", "")
+ for key, fields := range m {
+ if isEmptyFields(fields, src) {
+ errs[key] = EmptyFieldError{Key: key}
+ }
+ }
+ return errs
+}
+
+// findRequiredFields recursively searches the struct type t for required fields.
+//
+// canonicalPrefix and searchPrefix are used to resolve full paths in dotted notation
+// for nested struct fields. canonicalPrefix is a complete path which never omits
+// any embedded struct fields. searchPrefix is a user-friendly path which may omit
+// some embedded struct fields to point promoted fields.
+func (d *Decoder) findRequiredFields(t reflect.Type, canonicalPrefix, searchPrefix string) (map[string][]fieldWithPrefix, MultiError) {
+ struc := d.cache.get(t)
+ if struc == nil {
+ // unexpect, cache.get never return nil
+ return nil, MultiError{canonicalPrefix + "*": errors.New("cache fail")}
+ }
+
+ m := map[string][]fieldWithPrefix{}
+ errs := MultiError{}
+ for _, f := range struc.fields {
+ if f.typ.Kind() == reflect.Struct {
+ fcprefix := canonicalPrefix + f.canonicalAlias + "."
+ for _, fspath := range f.paths(searchPrefix) {
+ fm, ferrs := d.findRequiredFields(f.typ, fcprefix, fspath+".")
+ for key, fields := range fm {
+ m[key] = append(m[key], fields...)
+ }
+ errs.merge(ferrs)
+ }
+ }
+ if f.isRequired {
+ key := canonicalPrefix + f.canonicalAlias
+ m[key] = append(m[key], fieldWithPrefix{
+ fieldInfo: f,
+ prefix: searchPrefix,
+ })
+ }
+ }
+ return m, errs
+}
+
+type fieldWithPrefix struct {
+ *fieldInfo
+ prefix string
+}
+
+// isEmptyFields returns true if all of specified fields are empty.
+func isEmptyFields(fields []fieldWithPrefix, src map[string][]string) bool {
+ for _, f := range fields {
+ for _, path := range f.paths(f.prefix) {
+ if !isEmpty(f.typ, src[path]) {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+// isEmpty returns true if value is empty for specific type
+func isEmpty(t reflect.Type, value []string) bool {
+ if len(value) == 0 {
+ return true
+ }
+ switch t.Kind() {
+ case boolType, float32Type, float64Type, intType, int8Type, int32Type, int64Type, stringType, uint8Type, uint16Type, uint32Type, uint64Type:
+ return len(value[0]) == 0
+ }
+ return false
+}
+
+// decode fills a struct field using a parsed path.
+func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values []string) error {
+ // Get the field walking the struct fields by index.
+ for _, name := range parts[0].path {
+ if v.Type().Kind() == reflect.Ptr {
+ if v.IsNil() {
+ v.Set(reflect.New(v.Type().Elem()))
+ }
+ v = v.Elem()
+ }
+ v = v.FieldByName(name)
+ }
+ // Don't even bother for unexported fields.
+ if !v.CanSet() {
+ return nil
+ }
+
+ // Dereference if needed.
+ t := v.Type()
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ if v.IsNil() {
+ v.Set(reflect.New(t))
+ }
+ v = v.Elem()
+ }
+
+ // Slice of structs. Let's go recursive.
+ if len(parts) > 1 {
+ idx := parts[0].index
+ if v.IsNil() || v.Len() < idx+1 {
+ value := reflect.MakeSlice(t, idx+1, idx+1)
+ if v.Len() < idx+1 {
+ // Resize it.
+ reflect.Copy(value, v)
+ }
+ v.Set(value)
+ }
+ return d.decode(v.Index(idx), path, parts[1:], values)
+ }
+
+ // Get the converter early in case there is one for a slice type.
+ conv := d.cache.converter(t)
+ m := isTextUnmarshaler(v)
+ if conv == nil && t.Kind() == reflect.Slice && m.IsSliceElement {
+ var items []reflect.Value
+ elemT := t.Elem()
+ isPtrElem := elemT.Kind() == reflect.Ptr
+ if isPtrElem {
+ elemT = elemT.Elem()
+ }
+
+ // Try to get a converter for the element type.
+ conv := d.cache.converter(elemT)
+ if conv == nil {
+ conv = builtinConverters[elemT.Kind()]
+ if conv == nil {
+ // As we are not dealing with slice of structs here, we don't need to check if the type
+ // implements TextUnmarshaler interface
+ return fmt.Errorf("schema: converter not found for %v", elemT)
+ }
+ }
+
+ for key, value := range values {
+ if value == "" {
+ if d.zeroEmpty {
+ items = append(items, reflect.Zero(elemT))
+ }
+ } else if m.IsValid {
+ u := reflect.New(elemT)
+ if m.IsSliceElementPtr {
+ u = reflect.New(reflect.PtrTo(elemT).Elem())
+ }
+ if err := u.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)); err != nil {
+ return ConversionError{
+ Key: path,
+ Type: t,
+ Index: key,
+ Err: err,
+ }
+ }
+ if m.IsSliceElementPtr {
+ items = append(items, u.Elem().Addr())
+ } else if u.Kind() == reflect.Ptr {
+ items = append(items, u.Elem())
+ } else {
+ items = append(items, u)
+ }
+ } else if item := conv(value); item.IsValid() {
+ if isPtrElem {
+ ptr := reflect.New(elemT)
+ ptr.Elem().Set(item)
+ item = ptr
+ }
+ if item.Type() != elemT && !isPtrElem {
+ item = item.Convert(elemT)
+ }
+ items = append(items, item)
+ } else {
+ if strings.Contains(value, ",") {
+ values := strings.Split(value, ",")
+ for _, value := range values {
+ if value == "" {
+ if d.zeroEmpty {
+ items = append(items, reflect.Zero(elemT))
+ }
+ } else if item := conv(value); item.IsValid() {
+ if isPtrElem {
+ ptr := reflect.New(elemT)
+ ptr.Elem().Set(item)
+ item = ptr
+ }
+ if item.Type() != elemT && !isPtrElem {
+ item = item.Convert(elemT)
+ }
+ items = append(items, item)
+ } else {
+ return ConversionError{
+ Key: path,
+ Type: elemT,
+ Index: key,
+ }
+ }
+ }
+ } else {
+ return ConversionError{
+ Key: path,
+ Type: elemT,
+ Index: key,
+ }
+ }
+ }
+ }
+ value := reflect.Append(reflect.MakeSlice(t, 0, 0), items...)
+ v.Set(value)
+ } else {
+ val := ""
+ // Use the last value provided if any values were provided
+ if len(values) > 0 {
+ val = values[len(values)-1]
+ }
+
+ if conv != nil {
+ if value := conv(val); value.IsValid() {
+ v.Set(value.Convert(t))
+ } else {
+ return ConversionError{
+ Key: path,
+ Type: t,
+ Index: -1,
+ }
+ }
+ } else if m.IsValid {
+ if m.IsPtr {
+ u := reflect.New(v.Type())
+ if err := u.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(val)); err != nil {
+ return ConversionError{
+ Key: path,
+ Type: t,
+ Index: -1,
+ Err: err,
+ }
+ }
+ v.Set(reflect.Indirect(u))
+ } else {
+ // If the value implements the encoding.TextUnmarshaler interface
+ // apply UnmarshalText as the converter
+ if err := m.Unmarshaler.UnmarshalText([]byte(val)); err != nil {
+ return ConversionError{
+ Key: path,
+ Type: t,
+ Index: -1,
+ Err: err,
+ }
+ }
+ }
+ } else if val == "" {
+ if d.zeroEmpty {
+ v.Set(reflect.Zero(t))
+ }
+ } else if conv := builtinConverters[t.Kind()]; conv != nil {
+ if value := conv(val); value.IsValid() {
+ v.Set(value.Convert(t))
+ } else {
+ return ConversionError{
+ Key: path,
+ Type: t,
+ Index: -1,
+ }
+ }
+ } else {
+ return fmt.Errorf("schema: converter not found for %v", t)
+ }
+ }
+ return nil
+}
+
+func isTextUnmarshaler(v reflect.Value) unmarshaler {
+ // Create a new unmarshaller instance
+ m := unmarshaler{}
+ if m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler); m.IsValid {
+ return m
+ }
+ // As the UnmarshalText function should be applied to the pointer of the
+ // type, we check that type to see if it implements the necessary
+ // method.
+ if m.Unmarshaler, m.IsValid = reflect.New(v.Type()).Interface().(encoding.TextUnmarshaler); m.IsValid {
+ m.IsPtr = true
+ return m
+ }
+
+ // if v is []T or *[]T create new T
+ t := v.Type()
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+ if t.Kind() == reflect.Slice {
+ // Check if the slice implements encoding.TextUnmarshaller
+ if m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler); m.IsValid {
+ return m
+ }
+ // If t is a pointer slice, check if its elements implement
+ // encoding.TextUnmarshaler
+ m.IsSliceElement = true
+ if t = t.Elem(); t.Kind() == reflect.Ptr {
+ t = reflect.PtrTo(t.Elem())
+ v = reflect.Zero(t)
+ m.IsSliceElementPtr = true
+ m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler)
+ return m
+ }
+ }
+
+ v = reflect.New(t)
+ m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler)
+ return m
+}
+
+// TextUnmarshaler helpers ----------------------------------------------------
+// unmarshaller contains information about a TextUnmarshaler type
+type unmarshaler struct {
+ Unmarshaler encoding.TextUnmarshaler
+ // IsValid indicates whether the resolved type indicated by the other
+ // flags implements the encoding.TextUnmarshaler interface.
+ IsValid bool
+ // IsPtr indicates that the resolved type is the pointer of the original
+ // type.
+ IsPtr bool
+ // IsSliceElement indicates that the resolved type is a slice element of
+ // the original type.
+ IsSliceElement bool
+ // IsSliceElementPtr indicates that the resolved type is a pointer to a
+ // slice element of the original type.
+ IsSliceElementPtr bool
+}
+
+// Errors ---------------------------------------------------------------------
+
+// ConversionError stores information about a failed conversion.
+type ConversionError struct {
+ Key string // key from the source map.
+ Type reflect.Type // expected type of elem
+ Index int // index for multi-value fields; -1 for single-value fields.
+ Err error // low-level error (when it exists)
+}
+
+func (e ConversionError) Error() string {
+ var output string
+
+ if e.Index < 0 {
+ output = fmt.Sprintf("schema: error converting value for %q", e.Key)
+ } else {
+ output = fmt.Sprintf("schema: error converting value for index %d of %q",
+ e.Index, e.Key)
+ }
+
+ if e.Err != nil {
+ output = fmt.Sprintf("%s. Details: %s", output, e.Err)
+ }
+
+ return output
+}
+
+// UnknownKeyError stores information about an unknown key in the source map.
+type UnknownKeyError struct {
+ Key string // key from the source map.
+}
+
+func (e UnknownKeyError) Error() string {
+ return fmt.Sprintf("schema: invalid path %q", e.Key)
+}
+
+// EmptyFieldError stores information about an empty required field.
+type EmptyFieldError struct {
+ Key string // required key in the source map.
+}
+
+func (e EmptyFieldError) Error() string {
+ return fmt.Sprintf("%v is empty", e.Key)
+}
+
+// MultiError stores multiple decoding errors.
+//
+// Borrowed from the App Engine SDK.
+type MultiError map[string]error
+
+func (e MultiError) Error() string {
+ s := ""
+ for _, err := range e {
+ s = err.Error()
+ break
+ }
+ switch len(e) {
+ case 0:
+ return "(0 errors)"
+ case 1:
+ return s
+ case 2:
+ return s + " (and 1 other error)"
+ }
+ return fmt.Sprintf("%s (and %d other errors)", s, len(e)-1)
+}
+
+func (e MultiError) merge(errors MultiError) {
+ for key, err := range errors {
+ if e[key] == nil {
+ e[key] = err
+ }
+ }
+}
diff --git a/vendor/github.com/gorilla/schema/doc.go b/vendor/github.com/gorilla/schema/doc.go
new file mode 100644
index 000000000..aae9f33f9
--- /dev/null
+++ b/vendor/github.com/gorilla/schema/doc.go
@@ -0,0 +1,148 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package gorilla/schema fills a struct with form values.
+
+The basic usage is really simple. Given this struct:
+
+ type Person struct {
+ Name string
+ Phone string
+ }
+
+...we can fill it passing a map to the Decode() function:
+
+ values := map[string][]string{
+ "Name": {"John"},
+ "Phone": {"999-999-999"},
+ }
+ person := new(Person)
+ decoder := schema.NewDecoder()
+ decoder.Decode(person, values)
+
+This is just a simple example and it doesn't make a lot of sense to create
+the map manually. Typically it will come from a http.Request object and
+will be of type url.Values, http.Request.Form, or http.Request.MultipartForm:
+
+ func MyHandler(w http.ResponseWriter, r *http.Request) {
+ err := r.ParseForm()
+
+ if err != nil {
+ // Handle error
+ }
+
+ decoder := schema.NewDecoder()
+ // r.PostForm is a map of our POST form values
+ err := decoder.Decode(person, r.PostForm)
+
+ if err != nil {
+ // Handle error
+ }
+
+ // Do something with person.Name or person.Phone
+ }
+
+Note: it is a good idea to set a Decoder instance as a package global,
+because it caches meta-data about structs, and an instance can be shared safely:
+
+ var decoder = schema.NewDecoder()
+
+To define custom names for fields, use a struct tag "schema". To not populate
+certain fields, use a dash for the name and it will be ignored:
+
+ type Person struct {
+ Name string `schema:"name"` // custom name
+ Phone string `schema:"phone"` // custom name
+ Admin bool `schema:"-"` // this field is never set
+ }
+
+The supported field types in the destination struct are:
+
+ * bool
+ * float variants (float32, float64)
+ * int variants (int, int8, int16, int32, int64)
+ * string
+ * uint variants (uint, uint8, uint16, uint32, uint64)
+ * struct
+ * a pointer to one of the above types
+ * a slice or a pointer to a slice of one of the above types
+
+Non-supported types are simply ignored, however custom types can be registered
+to be converted.
+
+To fill nested structs, keys must use a dotted notation as the "path" for the
+field. So for example, to fill the struct Person below:
+
+ type Phone struct {
+ Label string
+ Number string
+ }
+
+ type Person struct {
+ Name string
+ Phone Phone
+ }
+
+...the source map must have the keys "Name", "Phone.Label" and "Phone.Number".
+This means that an HTML form to fill a Person struct must look like this:
+
+ <form>
+ <input type="text" name="Name">
+ <input type="text" name="Phone.Label">
+ <input type="text" name="Phone.Number">
+ </form>
+
+Single values are filled using the first value for a key from the source map.
+Slices are filled using all values for a key from the source map. So to fill
+a Person with multiple Phone values, like:
+
+ type Person struct {
+ Name string
+ Phones []Phone
+ }
+
+...an HTML form that accepts three Phone values would look like this:
+
+ <form>
+ <input type="text" name="Name">
+ <input type="text" name="Phones.0.Label">
+ <input type="text" name="Phones.0.Number">
+ <input type="text" name="Phones.1.Label">
+ <input type="text" name="Phones.1.Number">
+ <input type="text" name="Phones.2.Label">
+ <input type="text" name="Phones.2.Number">
+ </form>
+
+Notice that only for slices of structs the slice index is required.
+This is needed for disambiguation: if the nested struct also had a slice
+field, we could not translate multiple values to it if we did not use an
+index for the parent struct.
+
+There's also the possibility to create a custom type that implements the
+TextUnmarshaler interface, and in this case there's no need to register
+a converter, like:
+
+ type Person struct {
+ Emails []Email
+ }
+
+ type Email struct {
+ *mail.Address
+ }
+
+ func (e *Email) UnmarshalText(text []byte) (err error) {
+ e.Address, err = mail.ParseAddress(string(text))
+ return
+ }
+
+...an HTML form that accepts three Email values would look like this:
+
+ <form>
+ <input type="email" name="Emails.0">
+ <input type="email" name="Emails.1">
+ <input type="email" name="Emails.2">
+ </form>
+*/
+package schema
diff --git a/vendor/github.com/gorilla/schema/encoder.go b/vendor/github.com/gorilla/schema/encoder.go
new file mode 100644
index 000000000..bf1d511e6
--- /dev/null
+++ b/vendor/github.com/gorilla/schema/encoder.go
@@ -0,0 +1,195 @@
+package schema
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+ "strconv"
+)
+
+type encoderFunc func(reflect.Value) string
+
+// Encoder encodes values from a struct into url.Values.
+type Encoder struct {
+ cache *cache
+ regenc map[reflect.Type]encoderFunc
+}
+
+// NewEncoder returns a new Encoder with defaults.
+func NewEncoder() *Encoder {
+ return &Encoder{cache: newCache(), regenc: make(map[reflect.Type]encoderFunc)}
+}
+
+// Encode encodes a struct into map[string][]string.
+//
+// Intended for use with url.Values.
+func (e *Encoder) Encode(src interface{}, dst map[string][]string) error {
+ v := reflect.ValueOf(src)
+
+ return e.encode(v, dst)
+}
+
+// RegisterEncoder registers a converter for encoding a custom type.
+func (e *Encoder) RegisterEncoder(value interface{}, encoder func(reflect.Value) string) {
+ e.regenc[reflect.TypeOf(value)] = encoder
+}
+
+// SetAliasTag changes the tag used to locate custom field aliases.
+// The default tag is "schema".
+func (e *Encoder) SetAliasTag(tag string) {
+ e.cache.tag = tag
+}
+
+// isValidStructPointer test if input value is a valid struct pointer.
+func isValidStructPointer(v reflect.Value) bool {
+ return v.Type().Kind() == reflect.Ptr && v.Elem().IsValid() && v.Elem().Type().Kind() == reflect.Struct
+}
+
+func isZero(v reflect.Value) bool {
+ switch v.Kind() {
+ case reflect.Func:
+ case reflect.Map, reflect.Slice:
+ return v.IsNil() || v.Len() == 0
+ case reflect.Array:
+ z := true
+ for i := 0; i < v.Len(); i++ {
+ z = z && isZero(v.Index(i))
+ }
+ return z
+ case reflect.Struct:
+ z := true
+ for i := 0; i < v.NumField(); i++ {
+ z = z && isZero(v.Field(i))
+ }
+ return z
+ }
+ // Compare other types directly:
+ z := reflect.Zero(v.Type())
+ return v.Interface() == z.Interface()
+}
+
+func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error {
+ if v.Kind() == reflect.Ptr {
+ v = v.Elem()
+ }
+ if v.Kind() != reflect.Struct {
+ return errors.New("schema: interface must be a struct")
+ }
+ t := v.Type()
+
+ errors := MultiError{}
+
+ for i := 0; i < v.NumField(); i++ {
+ name, opts := fieldAlias(t.Field(i), e.cache.tag)
+ if name == "-" {
+ continue
+ }
+
+ // Encode struct pointer types if the field is a valid pointer and a struct.
+ if isValidStructPointer(v.Field(i)) {
+ e.encode(v.Field(i).Elem(), dst)
+ continue
+ }
+
+ encFunc := typeEncoder(v.Field(i).Type(), e.regenc)
+
+ // Encode non-slice types and custom implementations immediately.
+ if encFunc != nil {
+ value := encFunc(v.Field(i))
+ if opts.Contains("omitempty") && isZero(v.Field(i)) {
+ continue
+ }
+
+ dst[name] = append(dst[name], value)
+ continue
+ }
+
+ if v.Field(i).Type().Kind() == reflect.Struct {
+ e.encode(v.Field(i), dst)
+ continue
+ }
+
+ if v.Field(i).Type().Kind() == reflect.Slice {
+ encFunc = typeEncoder(v.Field(i).Type().Elem(), e.regenc)
+ }
+
+ if encFunc == nil {
+ errors[v.Field(i).Type().String()] = fmt.Errorf("schema: encoder not found for %v", v.Field(i))
+ continue
+ }
+
+ // Encode a slice.
+ if v.Field(i).Len() == 0 && opts.Contains("omitempty") {
+ continue
+ }
+
+ dst[name] = []string{}
+ for j := 0; j < v.Field(i).Len(); j++ {
+ dst[name] = append(dst[name], encFunc(v.Field(i).Index(j)))
+ }
+ }
+
+ if len(errors) > 0 {
+ return errors
+ }
+ return nil
+}
+
+func typeEncoder(t reflect.Type, reg map[reflect.Type]encoderFunc) encoderFunc {
+ if f, ok := reg[t]; ok {
+ return f
+ }
+
+ switch t.Kind() {
+ case reflect.Bool:
+ return encodeBool
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return encodeInt
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ return encodeUint
+ case reflect.Float32:
+ return encodeFloat32
+ case reflect.Float64:
+ return encodeFloat64
+ case reflect.Ptr:
+ f := typeEncoder(t.Elem(), reg)
+ return func(v reflect.Value) string {
+ if v.IsNil() {
+ return "null"
+ }
+ return f(v.Elem())
+ }
+ case reflect.String:
+ return encodeString
+ default:
+ return nil
+ }
+}
+
+func encodeBool(v reflect.Value) string {
+ return strconv.FormatBool(v.Bool())
+}
+
+func encodeInt(v reflect.Value) string {
+ return strconv.FormatInt(int64(v.Int()), 10)
+}
+
+func encodeUint(v reflect.Value) string {
+ return strconv.FormatUint(uint64(v.Uint()), 10)
+}
+
+func encodeFloat(v reflect.Value, bits int) string {
+ return strconv.FormatFloat(v.Float(), 'f', 6, bits)
+}
+
+func encodeFloat32(v reflect.Value) string {
+ return encodeFloat(v, 32)
+}
+
+func encodeFloat64(v reflect.Value) string {
+ return encodeFloat(v, 64)
+}
+
+func encodeString(v reflect.Value) string {
+ return v.String()
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 5a2d4ab81..3f6454f9d 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -286,8 +286,12 @@ github.com/golang/protobuf/ptypes/timestamp
github.com/google/gofuzz
# github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
github.com/google/shlex
+# github.com/google/uuid v1.1.1
+github.com/google/uuid
# github.com/gorilla/mux v1.7.3
github.com/gorilla/mux
+# github.com/gorilla/schema v1.1.0
+github.com/gorilla/schema
# github.com/hashicorp/errwrap v1.0.0
github.com/hashicorp/errwrap
# github.com/hashicorp/go-multierror v1.0.0