summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md4
-rw-r--r--cmd/podman/common.go4
-rw-r--r--cmd/podman/images.go59
-rw-r--r--cmd/podman/ps.go10
-rw-r--r--cmd/podman/shared/create.go1
-rw-r--r--cmd/podman/shared/intermediate.go1
-rw-r--r--completions/bash/podman1
-rw-r--r--docs/source/markdown/podman-create.1.md7
-rw-r--r--docs/source/markdown/podman-images.1.md12
-rw-r--r--go.mod6
-rw-r--r--go.sum6
-rw-r--r--libpod/image/image_test.go2
-rw-r--r--libpod/stats.go2
-rw-r--r--libpod/stats_config.go28
-rw-r--r--pkg/adapter/pods.go22
-rw-r--r--pkg/api/handlers/generic/containers_stats.go15
-rw-r--r--pkg/api/server/register_swagger.go26
-rw-r--r--pkg/api/server/server.go1
-rw-r--r--pkg/cgroups/cgroups.go15
-rw-r--r--pkg/cgroups/cpu.go39
-rw-r--r--pkg/rootlessport/rootlessport_linux.go29
-rw-r--r--pkg/spec/config_linux.go37
-rw-r--r--pkg/spec/config_unsupported.go4
-rw-r--r--pkg/spec/createconfig.go1
-rw-r--r--pkg/spec/parse.go18
-rw-r--r--pkg/spec/spec.go6
-rw-r--r--test/e2e/config.go2
-rw-r--r--test/e2e/images_test.go5
-rw-r--r--test/e2e/load_test.go2
-rw-r--r--test/e2e/play_kube_test.go43
-rw-r--r--test/e2e/ps_test.go13
-rw-r--r--test/e2e/run_test.go12
-rw-r--r--test/endpoint/endpoint.go2
-rw-r--r--vendor/github.com/gorilla/mux/README.md91
-rw-r--r--vendor/github.com/gorilla/mux/context.go18
-rw-r--r--vendor/github.com/gorilla/mux/go.mod2
-rw-r--r--vendor/github.com/gorilla/mux/middleware.go25
-rw-r--r--vendor/github.com/gorilla/mux/mux.go28
-rw-r--r--vendor/github.com/gorilla/mux/regexp.go65
-rw-r--r--vendor/github.com/gorilla/mux/route.go38
-rw-r--r--vendor/github.com/gorilla/mux/test_helpers.go2
-rw-r--r--vendor/modules.txt6
42 files changed, 566 insertions, 144 deletions
diff --git a/README.md b/README.md
index 4fbe2ecea..950602e0a 100644
--- a/README.md
+++ b/README.md
@@ -73,9 +73,9 @@ A little configuration by an administrator is required before rootless Podman ca
standard for composing Pods and for orchestrating containers, making
Kubernetes YAML a defacto standard file format. Hence, Podman allows the
creation and execution of Pods from a Kubernetes YAML file (see
- [podman-play-kube](https://github.com/containers/libpod/blob/master/docs/podman-play-kube.1.md)).
+ [podman-play-kube](https://github.com/containers/libpod/blob/master/docs/source/markdown/podman-play-kube.1.md)).
Podman can also generate Kubernetes YAML based on a container or Pod (see
- [podman-generate-kube](https://github.com/containers/libpod/blob/master/docs/podman-generate-kube.1.md)),
+ [podman-generate-kube](https://github.com/containers/libpod/blob/master/docs/source/markdown/podman-generate-kube.1.md)),
which allows for an easy transition from a local development environment
to a production Kubernetes cluster. If Kubernetes does not fit your requirements,
there are other third-party tools that support the docker-compose format such as
diff --git a/cmd/podman/common.go b/cmd/podman/common.go
index 7610edbc0..6fa2b3c71 100644
--- a/cmd/podman/common.go
+++ b/cmd/podman/common.go
@@ -257,6 +257,10 @@ func getCreateFlags(c *cliconfig.PodmanCommand) {
"Add a host device to the container (default [])",
)
createFlags.StringSlice(
+ "device-cgroup-rule", []string{},
+ "Add a rule to the cgroup allowed devices list",
+ )
+ createFlags.StringSlice(
"device-read-bps", []string{},
"Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)",
)
diff --git a/cmd/podman/images.go b/cmd/podman/images.go
index 115f30d9b..de61690ae 100644
--- a/cmd/podman/images.go
+++ b/cmd/podman/images.go
@@ -21,16 +21,16 @@ import (
)
type imagesTemplateParams struct {
- Repository string
- Tag string
- ID string
- Digest digest.Digest
- Digests []digest.Digest
- Created string
- CreatedTime time.Time
- Size string
- ReadOnly bool
- History string
+ Repository string
+ Tag string
+ ID string
+ Digest digest.Digest
+ Digests []digest.Digest
+ CreatedAt time.Time
+ CreatedSince string
+ Size string
+ ReadOnly bool
+ History string
}
type imagesJSONParams struct {
@@ -65,7 +65,7 @@ func (a imagesSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type imagesSortedCreated struct{ imagesSorted }
func (a imagesSortedCreated) Less(i, j int) bool {
- return a.imagesSorted[i].CreatedTime.After(a.imagesSorted[j].CreatedTime)
+ return a.imagesSorted[i].CreatedAt.After(a.imagesSorted[j].CreatedAt)
}
type imagesSortedID struct{ imagesSorted }
@@ -185,7 +185,17 @@ func imagesCmd(c *cliconfig.ImagesValues) error {
history: c.History,
}
- opts.outputformat = opts.setOutputFormat()
+ outputformat := opts.setOutputFormat()
+ // These fields were renamed, so we need to provide backward compat for
+ // the old names.
+ if strings.Contains(outputformat, "{{.Created}}") {
+ outputformat = strings.Replace(outputformat, "{{.Created}}", "{{.CreatedSince}}", -1)
+ }
+ if strings.Contains(outputformat, "{{.CreatedTime}}") {
+ outputformat = strings.Replace(outputformat, "{{.CreatedTime}}", "{{.CreatedAt}}", -1)
+ }
+ opts.outputformat = outputformat
+
filteredImages, err := runtime.GetFilteredImages(filters, false)
if err != nil {
return errors.Wrapf(err, "unable to get images")
@@ -216,7 +226,7 @@ func (i imagesOptions) setOutputFormat() string {
if i.digests {
format += "{{.Digest}}\t"
}
- format += "{{.ID}}\t{{.Created}}\t{{.Size}}\t"
+ format += "{{.ID}}\t{{.CreatedSince}}\t{{.Size}}\t"
if i.history {
format += "{{if .History}}{{.History}}{{else}}<none>{{end}}\t"
}
@@ -301,16 +311,16 @@ func getImagesTemplateOutput(ctx context.Context, images []*adapter.ContainerIma
imageDigest = img.Digest()
}
params := imagesTemplateParams{
- Repository: repo,
- Tag: tag,
- ID: imageID,
- Digest: imageDigest,
- Digests: img.Digests(),
- CreatedTime: createdTime,
- Created: units.HumanDuration(time.Since(createdTime)) + " ago",
- Size: sizeStr,
- ReadOnly: img.IsReadOnly(),
- History: strings.Join(img.NamesHistory(), ", "),
+ Repository: repo,
+ Tag: tag,
+ ID: imageID,
+ Digest: imageDigest,
+ Digests: img.Digests(),
+ CreatedAt: createdTime,
+ CreatedSince: units.HumanDuration(time.Since(createdTime)) + " ago",
+ Size: sizeStr,
+ ReadOnly: img.IsReadOnly(),
+ History: strings.Join(img.NamesHistory(), ", "),
}
imagesOutput = append(imagesOutput, params)
if opts.quiet { // Show only one image ID when quiet
@@ -384,6 +394,9 @@ func GenImageOutputMap() map[string]string {
values[key] = "R/O"
continue
}
+ if value == "CreatedSince" {
+ value = "created"
+ }
values[key] = strings.ToUpper(splitCamelCase(value))
}
return values
diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go
index d93ccc24c..accd5b51a 100644
--- a/cmd/podman/ps.go
+++ b/cmd/podman/ps.go
@@ -205,9 +205,15 @@ func checkFlagsPassed(c *cliconfig.PsValues) error {
if c.Last >= 0 && c.Latest {
return errors.Errorf("last and latest are mutually exclusive")
}
- // Filter forces all
+ // Filter on status forces all
if len(c.Filter) > 0 {
- c.All = true
+ for _, filter := range c.Filter {
+ splitFilter := strings.SplitN(filter, "=", 2)
+ if strings.ToLower(splitFilter[0]) == "status" {
+ c.All = true
+ break
+ }
+ }
}
// Quiet conflicts with size and namespace and is overridden by a Go
// template.
diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go
index e3a39a421..be5adcccb 100644
--- a/cmd/podman/shared/create.go
+++ b/cmd/podman/shared/create.go
@@ -758,6 +758,7 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
CPURtPeriod: c.Uint64("cpu-rt-period"),
CPURtRuntime: c.Int64("cpu-rt-runtime"),
CPUs: c.Float64("cpus"),
+ DeviceCgroupRules: c.StringSlice("device-cgroup-rule"),
DeviceReadBps: c.StringSlice("device-read-bps"),
DeviceReadIOps: c.StringSlice("device-read-iops"),
DeviceWriteBps: c.StringSlice("device-write-bps"),
diff --git a/cmd/podman/shared/intermediate.go b/cmd/podman/shared/intermediate.go
index cfb3f612c..ee212234f 100644
--- a/cmd/podman/shared/intermediate.go
+++ b/cmd/podman/shared/intermediate.go
@@ -386,6 +386,7 @@ func NewIntermediateLayer(c *cliconfig.PodmanCommand, remote bool) GenericCLIRes
m["detach"] = newCRBool(c, "detach")
m["detach-keys"] = newCRString(c, "detach-keys")
m["device"] = newCRStringSlice(c, "device")
+ m["device-cgroup-rule"] = newCRStringSlice(c, "device-cgroup-rule")
m["device-read-bps"] = newCRStringSlice(c, "device-read-bps")
m["device-read-iops"] = newCRStringSlice(c, "device-read-iops")
m["device-write-bps"] = newCRStringSlice(c, "device-write-bps")
diff --git a/completions/bash/podman b/completions/bash/podman
index 99e6a3bcb..d1dcef0a4 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -1873,6 +1873,7 @@ _podman_container_run() {
--cpuset-mems
--cpu-shares -c
--device
+ --device-cgroup-rule
--device-read-bps
--device-read-iops
--device-write-bps
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index 977382e61..ca38be6a1 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -209,6 +209,13 @@ Note: if the user only has access rights via a group then accessing the device
from inside a rootless container will fail. The `crun` runtime offers a
workaround for this by adding the option `--annotation run.oci.keep_original_groups=1`.
+**--device-cgroup-rule**="type major:minor mode"
+
+Add a rule to the cgroup allowed devices list. The rule is expected to be in the format specified in the Linux kernel documentation (Documentation/cgroup-v1/devices.txt):
+ - type: a (all), c (char), or b (block);
+ - major and minor: either a number, or * for all;
+ - mode: a composition of r (read), w (write), and m (mknod(2)).
+
**--device-read-bps**=*path*
Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)
diff --git a/docs/source/markdown/podman-images.1.md b/docs/source/markdown/podman-images.1.md
index 21fca1dbd..d22fb940f 100644
--- a/docs/source/markdown/podman-images.1.md
+++ b/docs/source/markdown/podman-images.1.md
@@ -51,6 +51,18 @@ Filter output based on conditions provided
Change the default output format. This can be of a supported type like 'json'
or a Go template.
+Valid placeholders for the Go template are listed below:
+
+| **Placeholder** | **Description** |
+| --------------- | ----------------------------------------------------------------------------- |
+| .ID | Image ID |
+| .Repository | Image repository |
+| .Tag | Image tag |
+| .Digest | Image digest |
+| .CreatedSince | Elapsed time since the image was created |
+| .CreatedAt | Time when the image was created |
+| .Size | Size of layer on disk |
+| .History | History of the image layer |
**--history**
diff --git a/go.mod b/go.mod
index 994746704..076a22808 100644
--- a/go.mod
+++ b/go.mod
@@ -38,7 +38,7 @@ require (
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/mux v1.7.4
github.com/gorilla/schema v1.1.0
github.com/hashicorp/go-multierror v1.0.0
github.com/hpcloud/tail v1.0.0
@@ -77,8 +77,8 @@ require (
google.golang.org/appengine v1.6.1 // indirect
google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601 // indirect
gopkg.in/yaml.v2 v2.2.8
- k8s.io/api v0.17.2
- k8s.io/apimachinery v0.17.2
+ k8s.io/api v0.17.3
+ k8s.io/apimachinery v0.17.3
k8s.io/client-go v0.0.0-20190620085101-78d2af792bab
k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a // indirect
)
diff --git a/go.sum b/go.sum
index 0b959646c..691c580ec 100644
--- a/go.sum
+++ b/go.sum
@@ -213,6 +213,8 @@ github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/
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/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
+github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
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=
@@ -621,10 +623,14 @@ k8s.io/api v0.0.0-20190620084959-7cf5895f2711/go.mod h1:TBhBqb1AWbBQbW3XRusr7n7E
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
k8s.io/api v0.17.2 h1:NF1UFXcKN7/OOv1uxdRz3qfra8AHsPav5M93hlV9+Dc=
k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4=
+k8s.io/api v0.17.3 h1:XAm3PZp3wnEdzekNkcmj/9Y1zdmQYJ1I4GKSBBZ8aG0=
+k8s.io/api v0.17.3/go.mod h1:YZ0OTkuw7ipbe305fMpIdf3GLXZKRigjtZaV5gzC2J0=
k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719/go.mod h1:I4A+glKBHiTgiEjQiCCQfCAIcIMFGt291SmsvcrFzJA=
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/apimachinery v0.17.2 h1:hwDQQFbdRlpnnsR64Asdi55GyCaIP/3WQpMmbNBeWr4=
k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
+k8s.io/apimachinery v0.17.3 h1:f+uZV6rm4/tHE7xXgLyToprg6xWairaClGVkm2t8omg=
+k8s.io/apimachinery v0.17.3/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=
k8s.io/client-go v0.0.0-20170217214107-bcde30fb7eae/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
k8s.io/client-go v0.0.0-20181219152756-3dd551c0f083/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
k8s.io/client-go v0.0.0-20190620085101-78d2af792bab h1:E8Fecph0qbNsAbijJJQryKu4Oi9QTp5cVpjTE+nqg6g=
diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go
index 3ff6210d9..19f7eee1e 100644
--- a/libpod/image/image_test.go
+++ b/libpod/image/image_test.go
@@ -18,7 +18,6 @@ import (
var (
bbNames = []string{"docker.io/library/busybox:latest", "docker.io/library/busybox", "docker.io/busybox:latest", "docker.io/busybox", "busybox:latest", "busybox"}
bbGlibcNames = []string{"docker.io/library/busybox:glibc", "docker.io/busybox:glibc", "busybox:glibc"}
- fedoraNames = []string{"registry.fedoraproject.org/fedora-minimal:latest", "registry.fedoraproject.org/fedora-minimal", "fedora-minimal:latest", "fedora-minimal"}
)
type localImageTest struct {
@@ -139,7 +138,6 @@ func TestImage_New(t *testing.T) {
ir.Eventer = events.NewNullEventer()
// Build the list of pull names
names = append(names, bbNames...)
- names = append(names, fedoraNames...)
writer := os.Stdout
// Iterate over the names and delete the image
diff --git a/libpod/stats.go b/libpod/stats.go
index 3b5e0958c..6f42afd18 100644
--- a/libpod/stats.go
+++ b/libpod/stats.go
@@ -66,7 +66,9 @@ func (c *Container) GetContainerStats(previousStats *ContainerStats) (*Container
}
stats.BlockInput, stats.BlockOutput = calculateBlockIO(cgroupStats)
stats.CPUNano = cgroupStats.CPU.Usage.Total
+ stats.CPUSystemNano = cgroupStats.CPU.Usage.Kernel
stats.SystemNano = now
+ stats.PerCPU = cgroupStats.CPU.Usage.PerCPU
// Handle case where the container is not in a network namespace
if netStats != nil {
stats.NetInput = netStats.TxBytes
diff --git a/libpod/stats_config.go b/libpod/stats_config.go
index 9c7d97298..91d3d1493 100644
--- a/libpod/stats_config.go
+++ b/libpod/stats_config.go
@@ -2,17 +2,19 @@ package libpod
// ContainerStats contains the statistics information for a running container
type ContainerStats struct {
- ContainerID string
- Name string
- CPU float64
- CPUNano uint64
- SystemNano uint64
- MemUsage uint64
- MemLimit uint64
- MemPerc float64
- NetInput uint64
- NetOutput uint64
- BlockInput uint64
- BlockOutput uint64
- PIDs uint64
+ ContainerID string
+ Name string
+ PerCPU []uint64
+ CPU float64
+ CPUNano uint64
+ CPUSystemNano uint64
+ SystemNano uint64
+ MemUsage uint64
+ MemLimit uint64
+ MemPerc float64
+ NetInput uint64
+ NetOutput uint64
+ BlockInput uint64
+ BlockOutput uint64
+ PIDs uint64
}
diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go
index b0e63f770..a30ec6649 100644
--- a/pkg/adapter/pods.go
+++ b/pkg/adapter/pods.go
@@ -764,7 +764,6 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container
containerConfig.ImageID = newImage.ID()
containerConfig.Name = containerYAML.Name
containerConfig.Tty = containerYAML.TTY
- containerConfig.WorkDir = containerYAML.WorkingDir
containerConfig.Pod = podID
@@ -796,6 +795,27 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container
containerConfig.StopSignal = 15
+ containerConfig.WorkDir = "/"
+ if imageData != nil {
+ // FIXME,
+ // we are currently ignoring imageData.Config.ExposedPorts
+ containerConfig.BuiltinImgVolumes = imageData.Config.Volumes
+ if imageData.Config.WorkingDir != "" {
+ containerConfig.WorkDir = imageData.Config.WorkingDir
+ }
+ containerConfig.Labels = imageData.Config.Labels
+ if imageData.Config.StopSignal != "" {
+ stopSignal, err := util.ParseSignal(imageData.Config.StopSignal)
+ if err != nil {
+ return nil, err
+ }
+ containerConfig.StopSignal = stopSignal
+ }
+ }
+
+ if containerYAML.WorkingDir != "" {
+ containerConfig.WorkDir = containerYAML.WorkingDir
+ }
// If the user does not pass in ID mappings, just set to basics
if userConfig.IDMappings == nil {
userConfig.IDMappings = &storage.IDMappingOptions{}
diff --git a/pkg/api/handlers/generic/containers_stats.go b/pkg/api/handlers/generic/containers_stats.go
index cbc1be2f0..19e2cc882 100644
--- a/pkg/api/handlers/generic/containers_stats.go
+++ b/pkg/api/handlers/generic/containers_stats.go
@@ -61,14 +61,15 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
var preCPUStats docker.CPUStats
if query.Stream {
preRead = time.Now()
+ systemUsage, _ := cgroups.GetSystemCPUUsage()
preCPUStats = docker.CPUStats{
CPUUsage: docker.CPUUsage{
TotalUsage: stats.CPUNano,
- PercpuUsage: []uint64{uint64(stats.CPU)},
- UsageInKernelmode: 0,
- UsageInUsermode: 0,
+ PercpuUsage: stats.PerCPU,
+ UsageInKernelmode: stats.CPUSystemNano,
+ UsageInUsermode: stats.CPUNano - stats.CPUSystemNano,
},
- SystemUsage: 0,
+ SystemUsage: systemUsage,
OnlineCPUs: 0,
ThrottlingData: docker.ThrottlingData{},
}
@@ -122,6 +123,8 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
InstanceID: "",
}
+ systemUsage, _ := cgroups.GetSystemCPUUsage()
+
s := handlers.Stats{StatsJSON: docker.StatsJSON{
Stats: docker.Stats{
Read: time.Now(),
@@ -143,11 +146,11 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
CPUStats: docker.CPUStats{
CPUUsage: docker.CPUUsage{
TotalUsage: cgroupStat.CPU.Usage.Total,
- PercpuUsage: []uint64{uint64(stats.CPU)},
+ PercpuUsage: cgroupStat.CPU.Usage.PerCPU,
UsageInKernelmode: cgroupStat.CPU.Usage.Kernel,
UsageInUsermode: cgroupStat.CPU.Usage.Total - cgroupStat.CPU.Usage.Kernel,
},
- SystemUsage: 0,
+ SystemUsage: systemUsage,
OnlineCPUs: uint32(len(cgroupStat.CPU.Usage.PerCPU)),
ThrottlingData: docker.ThrottlingData{
Periods: 0,
diff --git a/pkg/api/server/register_swagger.go b/pkg/api/server/register_swagger.go
new file mode 100644
index 000000000..5564ec096
--- /dev/null
+++ b/pkg/api/server/register_swagger.go
@@ -0,0 +1,26 @@
+package server
+
+import (
+ "net/http"
+ "os"
+
+ "github.com/gorilla/mux"
+)
+
+// DefaultPodmanSwaggerSpec provides the default path to the podman swagger spec file
+const DefaultPodmanSwaggerSpec = "/usr/share/containers/podman/swagger.yaml"
+
+// RegisterSwaggerHandlers maps the swagger endpoint for the server
+func (s *APIServer) RegisterSwaggerHandlers(r *mux.Router) error {
+ // This handler does _*NOT*_ provide an UI rather just a swagger spec that an UI could render
+ r.PathPrefix("/swagger/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ path := DefaultPodmanSwaggerSpec
+ if p, found := os.LookupEnv("PODMAN_SWAGGER_SPEC"); found {
+ path = p
+ }
+ w.Header().Set("Content-Type", "text/yaml")
+
+ http.ServeFile(w, r, path)
+ })
+ return nil
+}
diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go
index 87b11b716..2c709bc59 100644
--- a/pkg/api/server/server.go
+++ b/pkg/api/server/server.go
@@ -114,6 +114,7 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li
server.registerPingHandlers,
server.RegisterPluginsHandlers,
server.registerPodsHandlers,
+ server.RegisterSwaggerHandlers,
server.RegisterSwarmHandlers,
server.registerSystemHandlers,
server.registerVersionHandlers,
diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go
index 96786223d..6c5b7978c 100644
--- a/pkg/cgroups/cgroups.go
+++ b/pkg/cgroups/cgroups.go
@@ -536,15 +536,14 @@ func (c *CgroupControl) Stat() (*Metrics, error) {
return &m, nil
}
-func readCgroup2MapFile(ctr *CgroupControl, name string) (map[string][]string, error) {
+func readCgroup2MapPath(path string) (map[string][]string, error) {
ret := map[string][]string{}
- p := filepath.Join(cgroupRoot, ctr.path, name)
- f, err := os.Open(p)
+ f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
return ret, nil
}
- return nil, errors.Wrapf(err, "open file %s", p)
+ return nil, errors.Wrapf(err, "open file %s", path)
}
defer f.Close()
scanner := bufio.NewScanner(f)
@@ -557,7 +556,13 @@ func readCgroup2MapFile(ctr *CgroupControl, name string) (map[string][]string, e
ret[parts[0]] = parts[1:]
}
if err := scanner.Err(); err != nil {
- return nil, errors.Wrapf(err, "parsing file %s", p)
+ return nil, errors.Wrapf(err, "parsing file %s", path)
}
return ret, nil
}
+
+func readCgroup2MapFile(ctr *CgroupControl, name string) (map[string][]string, error) {
+ p := filepath.Join(cgroupRoot, ctr.path, name)
+
+ return readCgroup2MapPath(p)
+}
diff --git a/pkg/cgroups/cpu.go b/pkg/cgroups/cpu.go
index a43a76b22..5f0a18031 100644
--- a/pkg/cgroups/cpu.go
+++ b/pkg/cgroups/cpu.go
@@ -121,3 +121,42 @@ func (c *cpuHandler) Stat(ctr *CgroupControl, m *Metrics) error {
m.CPU = CPUMetrics{Usage: usage}
return nil
}
+
+// GetSystemCPUUsage returns the system usage for all the cgroups
+func GetSystemCPUUsage() (uint64, error) {
+ cgroupv2, err := IsCgroup2UnifiedMode()
+ if err != nil {
+ return 0, err
+ }
+ if !cgroupv2 {
+ p := filepath.Join(cgroupRoot, CPUAcct, "cpuacct.usage")
+ return readFileAsUint64(p)
+ }
+
+ files, err := ioutil.ReadDir(cgroupRoot)
+ if err != nil {
+ return 0, errors.Wrapf(err, "read directory %q", cgroupRoot)
+ }
+ var total uint64
+ for _, file := range files {
+ if !file.IsDir() {
+ continue
+ }
+ p := filepath.Join(cgroupRoot, file.Name(), "cpu.stat")
+
+ values, err := readCgroup2MapPath(p)
+ if err != nil {
+ return 0, err
+ }
+
+ if val, found := values["usage_usec"]; found {
+ v, err := strconv.ParseUint(cleanString(val[0]), 10, 0)
+ if err != nil {
+ return 0, err
+ }
+ total += v * 1000
+ }
+
+ }
+ return total, nil
+}
diff --git a/pkg/rootlessport/rootlessport_linux.go b/pkg/rootlessport/rootlessport_linux.go
index 3e678d33a..2b51f4e09 100644
--- a/pkg/rootlessport/rootlessport_linux.go
+++ b/pkg/rootlessport/rootlessport_linux.go
@@ -122,6 +122,7 @@ func parent() error {
logrus.WithError(driverErr).Warn("parent driver exited")
}
errCh <- driverErr
+ close(errCh)
}()
opaque := driver.OpaqueForChild()
logrus.Infof("opaque=%+v", opaque)
@@ -142,15 +143,12 @@ func parent() error {
}()
// reexec the child process in the child netns
- cmd := exec.Command(fmt.Sprintf("/proc/%d/exe", os.Getpid()))
+ cmd := exec.Command("/proc/self/exe")
cmd.Args = []string{reexecChildKey}
cmd.Stdin = childQuitR
cmd.Stdout = &logrusWriter{prefix: "child"}
cmd.Stderr = cmd.Stdout
cmd.Env = append(os.Environ(), reexecChildEnvOpaque+"="+string(opaqueJSON))
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Pdeathsig: syscall.SIGTERM,
- }
childNS, err := ns.GetNS(cfg.NetNSPath)
if err != nil {
return err
@@ -162,14 +160,27 @@ func parent() error {
return err
}
+ defer func() {
+ if err := syscall.Kill(cmd.Process.Pid, syscall.SIGTERM); err != nil {
+ logrus.WithError(err).Warn("kill child process")
+ }
+ }()
+
logrus.Info("waiting for initComplete")
// wait for the child to connect to the parent
- select {
- case <-initComplete:
- logrus.Infof("initComplete is closed; parent and child established the communication channel")
- case err := <-errCh:
- return err
+outer:
+ for {
+ select {
+ case <-initComplete:
+ logrus.Infof("initComplete is closed; parent and child established the communication channel")
+ break outer
+ case err := <-errCh:
+ if err != nil {
+ return err
+ }
+ }
}
+
defer func() {
logrus.Info("stopping parent driver")
quit <- struct{}{}
diff --git a/pkg/spec/config_linux.go b/pkg/spec/config_linux.go
index 32d8cb4de..5f39b6d0d 100644
--- a/pkg/spec/config_linux.go
+++ b/pkg/spec/config_linux.go
@@ -7,6 +7,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
+ "strconv"
"strings"
"github.com/containers/libpod/pkg/rootless"
@@ -90,6 +91,42 @@ func devicesFromPath(g *generate.Generator, devicePath string) error {
return addDevice(g, strings.Join(append([]string{resolvedDevicePath}, devs[1:]...), ":"))
}
+func deviceCgroupRules(g *generate.Generator, deviceCgroupRules []string) error {
+ for _, deviceCgroupRule := range deviceCgroupRules {
+ if err := validateDeviceCgroupRule(deviceCgroupRule); err != nil {
+ return err
+ }
+ ss := parseDeviceCgroupRule(deviceCgroupRule)
+ if len(ss[0]) != 5 {
+ return errors.Errorf("invalid device cgroup rule format: '%s'", deviceCgroupRule)
+ }
+ matches := ss[0]
+ var major, minor *int64
+ if matches[2] == "*" {
+ majorDev := int64(-1)
+ major = &majorDev
+ } else {
+ majorDev, err := strconv.ParseInt(matches[2], 10, 64)
+ if err != nil {
+ return errors.Errorf("invalid major value in device cgroup rule format: '%s'", deviceCgroupRule)
+ }
+ major = &majorDev
+ }
+ if matches[3] == "*" {
+ minorDev := int64(-1)
+ minor = &minorDev
+ } else {
+ minorDev, err := strconv.ParseInt(matches[2], 10, 64)
+ if err != nil {
+ return errors.Errorf("invalid major value in device cgroup rule format: '%s'", deviceCgroupRule)
+ }
+ minor = &minorDev
+ }
+ g.AddLinuxResourcesDevice(true, matches[1], major, minor, matches[4])
+ }
+ return nil
+}
+
func addDevice(g *generate.Generator, device string) error {
src, dst, permissions, err := ParseDevice(device)
if err != nil {
diff --git a/pkg/spec/config_unsupported.go b/pkg/spec/config_unsupported.go
index a2c7f4416..be3e7046d 100644
--- a/pkg/spec/config_unsupported.go
+++ b/pkg/spec/config_unsupported.go
@@ -30,3 +30,7 @@ func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrott
func devicesFromPath(g *generate.Generator, devicePath string) error {
return errors.New("function not implemented")
}
+
+func deviceCgroupRules(g *generate.Generator, deviceCgroupRules []string) error {
+ return errors.New("function not implemented")
+}
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index 173dfb842..8010be0d4 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -38,6 +38,7 @@ type CreateResourceConfig struct {
CPUs float64 // cpus
CPUsetCPUs string
CPUsetMems string // cpuset-mems
+ DeviceCgroupRules []string //device-cgroup-rule
DeviceReadBps []string // device-read-bps
DeviceReadIOps []string // device-read-iops
DeviceWriteBps []string // device-write-bps
diff --git a/pkg/spec/parse.go b/pkg/spec/parse.go
index 6fa0b0636..a5dfccdb9 100644
--- a/pkg/spec/parse.go
+++ b/pkg/spec/parse.go
@@ -2,12 +2,17 @@ package createconfig
import (
"fmt"
+ "regexp"
"strconv"
"strings"
"github.com/docker/go-units"
+ "github.com/pkg/errors"
)
+// deviceCgroupRulegex defines the valid format of device-cgroup-rule
+var deviceCgroupRuleRegex = regexp.MustCompile(`^([acb]) ([0-9]+|\*):([0-9]+|\*) ([rwm]{1,3})$`)
+
// Pod signifies a kernel namespace is being shared
// by a container with the pod it is associated with
const Pod = "pod"
@@ -205,3 +210,16 @@ func IsValidDeviceMode(mode string) bool {
}
return true
}
+
+// validateDeviceCgroupRule validates the format of deviceCgroupRule
+func validateDeviceCgroupRule(deviceCgroupRule string) error {
+ if !deviceCgroupRuleRegex.MatchString(deviceCgroupRule) {
+ return errors.Errorf("invalid device cgroup rule format: '%s'", deviceCgroupRule)
+ }
+ return nil
+}
+
+// parseDeviceCgroupRule matches and parses the deviceCgroupRule into slice
+func parseDeviceCgroupRule(deviceCgroupRule string) [][]string {
+ return deviceCgroupRuleRegex.FindAllStringSubmatch(deviceCgroupRule, -1)
+}
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index cae055bb0..b2a152a2d 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -232,6 +232,12 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
return nil, err
}
}
+ if len(config.Resources.DeviceCgroupRules) != 0 {
+ if err := deviceCgroupRules(&g, config.Resources.DeviceCgroupRules); err != nil {
+ return nil, err
+ }
+ addedResources = true
+ }
}
// SECURITY OPTS
diff --git a/test/e2e/config.go b/test/e2e/config.go
index 96cc157be..95b0481b3 100644
--- a/test/e2e/config.go
+++ b/test/e2e/config.go
@@ -2,7 +2,7 @@ package integration
var (
redis = "docker.io/library/redis:alpine"
- fedoraMinimal = "registry.fedoraproject.org/fedora-minimal:latest"
+ fedoraMinimal = "quay.io/libpod/fedora-minimal:latest"
ALPINE = "docker.io/library/alpine:latest"
ALPINELISTTAG = "docker.io/library/alpine:3.10.2"
ALPINELISTDIGEST = "docker.io/library/alpine@sha256:72c42ed48c3a2db31b7dafe17d275b634664a708d901ec9fd57b1529280f01fb"
diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go
index 9a67cc83a..8b6b679a5 100644
--- a/test/e2e/images_test.go
+++ b/test/e2e/images_test.go
@@ -116,7 +116,8 @@ var _ = Describe("Podman images", func() {
})
It("podman images in GO template format", func() {
- session := podmanTest.Podman([]string{"images", "--format={{.ID}}"})
+ formatStr := "{{.ID}}\t{{.Created}}\t{{.CreatedAt}}\t{{.CreatedSince}}\t{{.CreatedTime}}"
+ session := podmanTest.Podman([]string{"images", fmt.Sprintf("--format=%s", formatStr)})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
})
@@ -280,7 +281,7 @@ RUN apk update && apk add man
return session.OutputToStringArray()
}
- sortedArr := sortValueTest("created", 0, "CreatedTime")
+ sortedArr := sortValueTest("created", 0, "CreatedAt")
Expect(sort.SliceIsSorted(sortedArr, func(i, j int) bool { return sortedArr[i] > sortedArr[j] })).To(BeTrue())
sortedArr = sortValueTest("id", 0, "ID")
diff --git a/test/e2e/load_test.go b/test/e2e/load_test.go
index 9ff358d26..9a2cee9e1 100644
--- a/test/e2e/load_test.go
+++ b/test/e2e/load_test.go
@@ -205,7 +205,7 @@ var _ = Describe("Podman load", func() {
podmanTest.RestoreArtifact(fedoraMinimal)
outfile := filepath.Join(podmanTest.TempDir, "load_test.tar.gz")
- setup := podmanTest.PodmanNoCache([]string{"tag", "fedora-minimal", "hello"})
+ setup := podmanTest.PodmanNoCache([]string{"tag", fedoraMinimal, "hello"})
setup.WaitWithDefaultTimeout()
Expect(setup.ExitCode()).To(Equal(0))
diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index 8411e632a..9daf266b8 100644
--- a/test/e2e/play_kube_test.go
+++ b/test/e2e/play_kube_test.go
@@ -4,6 +4,7 @@ package integration
import (
"fmt"
+ "io/ioutil"
"os"
"path/filepath"
"text/template"
@@ -486,4 +487,46 @@ var _ = Describe("Podman generate kube", func() {
newBBinspect := inspect.InspectImageJSON()
Expect(oldBBinspect[0].Digest).To(Not(Equal(newBBinspect[0].Digest)))
})
+
+ It("podman play kube with image data", func() {
+ testyaml := `
+apiVersion: v1
+kind: Pod
+metadata:
+ name: demo_pod
+spec:
+ containers:
+ - image: demo
+ name: demo_kube
+`
+ pull := podmanTest.Podman([]string{"create", "--workdir", "/etc", "--name", "newBB", "--label", "key1=value1", "alpine"})
+
+ pull.WaitWithDefaultTimeout()
+ Expect(pull.ExitCode()).To(BeZero())
+
+ c := podmanTest.Podman([]string{"commit", "-c", "STOPSIGNAL=51", "newBB", "demo"})
+ c.WaitWithDefaultTimeout()
+ Expect(c.ExitCode()).To(Equal(0))
+
+ conffile := filepath.Join(podmanTest.TempDir, "kube.yaml")
+ tempdir, err = CreateTempDirInTempDir()
+ Expect(err).To(BeNil())
+
+ err := ioutil.WriteFile(conffile, []byte(testyaml), 0755)
+ Expect(err).To(BeNil())
+
+ kube := podmanTest.Podman([]string{"play", "kube", conffile})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube.ExitCode()).To(Equal(0))
+
+ inspect := podmanTest.Podman([]string{"inspect", "demo_kube"})
+ inspect.WaitWithDefaultTimeout()
+ Expect(inspect.ExitCode()).To(Equal(0))
+
+ ctr := inspect.InspectContainerToJSON()
+ Expect(ctr[0].Config.WorkingDir).To(ContainSubstring("/etc"))
+ Expect(ctr[0].Config.Labels["key1"]).To(ContainSubstring("value1"))
+ Expect(ctr[0].Config.Labels["key1"]).To(ContainSubstring("value1"))
+ Expect(ctr[0].Config.StopSignal).To(Equal(uint(51)))
+ })
})
diff --git a/test/e2e/ps_test.go b/test/e2e/ps_test.go
index fccc5c93b..48dd186e2 100644
--- a/test/e2e/ps_test.go
+++ b/test/e2e/ps_test.go
@@ -243,6 +243,19 @@ var _ = Describe("Podman ps", func() {
Expect(psAll.OutputToString()).To(Equal(psFilter.OutputToString()))
})
+ It("podman filter without status does not find non-running", func() {
+ ctrName := "aContainerName"
+ ctr := podmanTest.Podman([]string{"create", "--name", ctrName, "-t", "-i", ALPINE, "ls", "/"})
+ ctr.WaitWithDefaultTimeout()
+ Expect(ctr.ExitCode()).To(Equal(0))
+
+ psFilter := podmanTest.Podman([]string{"ps", "--no-trunc", "--quiet", "--format", "{{.Names}}", "--filter", fmt.Sprintf("name=%s", ctrName)})
+ psFilter.WaitWithDefaultTimeout()
+ Expect(psFilter.ExitCode()).To(Equal(0))
+
+ Expect(strings.Contains(psFilter.OutputToString(), ctrName)).To(BeFalse())
+ })
+
It("podman ps mutually exclusive flags", func() {
session := podmanTest.Podman([]string{"ps", "-aqs"})
session.WaitWithDefaultTimeout()
diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go
index 72547ea00..3eb93b84a 100644
--- a/test/e2e/run_test.go
+++ b/test/e2e/run_test.go
@@ -999,4 +999,16 @@ USER mail`
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Not(Equal(0)))
})
+
+ It("podman run --device-cgroup-rule", func() {
+ SkipIfRemote()
+ SkipIfRootless()
+ deviceCgroupRule := "c 42:* rwm"
+ session := podmanTest.Podman([]string{"run", "--name", "test", "-d", "--device-cgroup-rule", deviceCgroupRule, ALPINE, "top"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"exec", "test", "mknod", "newDev", "c", "42", "1"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
})
diff --git a/test/endpoint/endpoint.go b/test/endpoint/endpoint.go
index 78aa957ab..f1634b6f0 100644
--- a/test/endpoint/endpoint.go
+++ b/test/endpoint/endpoint.go
@@ -29,7 +29,7 @@ var (
infra = "k8s.gcr.io/pause:3.1"
BB = "docker.io/library/busybox:latest"
redis = "docker.io/library/redis:alpine"
- fedoraMinimal = "registry.fedoraproject.org/fedora-minimal:latest"
+ fedoraMinimal = "quay.io/libpod/fedora-minimal:latest"
)
type EndpointTestIntegration struct {
diff --git a/vendor/github.com/gorilla/mux/README.md b/vendor/github.com/gorilla/mux/README.md
index 92e422eed..35eea9f10 100644
--- a/vendor/github.com/gorilla/mux/README.md
+++ b/vendor/github.com/gorilla/mux/README.md
@@ -1,11 +1,10 @@
# gorilla/mux
[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
-[![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux)
[![CircleCI](https://circleci.com/gh/gorilla/mux.svg?style=svg)](https://circleci.com/gh/gorilla/mux)
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)
-![Gorilla Logo](http://www.gorillatoolkit.org/static/images/gorilla-icon-64.png)
+![Gorilla Logo](https://cloud-cdn.questionable.services/gorilla-icon-64.png)
https://www.gorillatoolkit.org/pkg/mux
@@ -26,6 +25,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv
* [Examples](#examples)
* [Matching Routes](#matching-routes)
* [Static Files](#static-files)
+* [Serving Single Page Applications](#serving-single-page-applications) (e.g. React, Vue, Ember.js, etc.)
* [Registered URLs](#registered-urls)
* [Walking Routes](#walking-routes)
* [Graceful Shutdown](#graceful-shutdown)
@@ -212,6 +212,93 @@ func main() {
}
```
+### Serving Single Page Applications
+
+Most of the time it makes sense to serve your SPA on a separate web server from your API,
+but sometimes it's desirable to serve them both from one place. It's possible to write a simple
+handler for serving your SPA (for use with React Router's [BrowserRouter](https://reacttraining.com/react-router/web/api/BrowserRouter) for example), and leverage
+mux's powerful routing for your API endpoints.
+
+```go
+package main
+
+import (
+ "encoding/json"
+ "log"
+ "net/http"
+ "os"
+ "path/filepath"
+ "time"
+
+ "github.com/gorilla/mux"
+)
+
+// spaHandler implements the http.Handler interface, so we can use it
+// to respond to HTTP requests. The path to the static directory and
+// path to the index file within that static directory are used to
+// serve the SPA in the given static directory.
+type spaHandler struct {
+ staticPath string
+ indexPath string
+}
+
+// ServeHTTP inspects the URL path to locate a file within the static dir
+// on the SPA handler. If a file is found, it will be served. If not, the
+// file located at the index path on the SPA handler will be served. This
+// is suitable behavior for serving an SPA (single page application).
+func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ // get the absolute path to prevent directory traversal
+ path, err := filepath.Abs(r.URL.Path)
+ if err != nil {
+ // if we failed to get the absolute path respond with a 400 bad request
+ // and stop
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ // prepend the path with the path to the static directory
+ path = filepath.Join(h.staticPath, path)
+
+ // check whether a file exists at the given path
+ _, err = os.Stat(path)
+ if os.IsNotExist(err) {
+ // file does not exist, serve index.html
+ http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath))
+ return
+ } else if err != nil {
+ // if we got an error (that wasn't that the file doesn't exist) stating the
+ // file, return a 500 internal server error and stop
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ // otherwise, use http.FileServer to serve the static dir
+ http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r)
+}
+
+func main() {
+ router := mux.NewRouter()
+
+ router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
+ // an example API handler
+ json.NewEncoder(w).Encode(map[string]bool{"ok": true})
+ })
+
+ spa := spaHandler{staticPath: "build", indexPath: "index.html"}
+ router.PathPrefix("/").Handler(spa)
+
+ srv := &http.Server{
+ Handler: router,
+ Addr: "127.0.0.1:8000",
+ // Good practice: enforce timeouts for servers you create!
+ WriteTimeout: 15 * time.Second,
+ ReadTimeout: 15 * time.Second,
+ }
+
+ log.Fatal(srv.ListenAndServe())
+}
+```
+
### Registered URLs
Now let's see how to build registered URLs.
diff --git a/vendor/github.com/gorilla/mux/context.go b/vendor/github.com/gorilla/mux/context.go
deleted file mode 100644
index 665940a26..000000000
--- a/vendor/github.com/gorilla/mux/context.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package mux
-
-import (
- "context"
- "net/http"
-)
-
-func contextGet(r *http.Request, key interface{}) interface{} {
- return r.Context().Value(key)
-}
-
-func contextSet(r *http.Request, key, val interface{}) *http.Request {
- if val == nil {
- return r
- }
-
- return r.WithContext(context.WithValue(r.Context(), key, val))
-}
diff --git a/vendor/github.com/gorilla/mux/go.mod b/vendor/github.com/gorilla/mux/go.mod
index cfc8ede58..df170a399 100644
--- a/vendor/github.com/gorilla/mux/go.mod
+++ b/vendor/github.com/gorilla/mux/go.mod
@@ -1 +1,3 @@
module github.com/gorilla/mux
+
+go 1.12
diff --git a/vendor/github.com/gorilla/mux/middleware.go b/vendor/github.com/gorilla/mux/middleware.go
index cf2b26dc0..cb51c565e 100644
--- a/vendor/github.com/gorilla/mux/middleware.go
+++ b/vendor/github.com/gorilla/mux/middleware.go
@@ -58,22 +58,17 @@ func CORSMethodMiddleware(r *Router) MiddlewareFunc {
func getAllMethodsForRoute(r *Router, req *http.Request) ([]string, error) {
var allMethods []string
- err := r.Walk(func(route *Route, _ *Router, _ []*Route) error {
- for _, m := range route.matchers {
- if _, ok := m.(*routeRegexp); ok {
- if m.Match(req, &RouteMatch{}) {
- methods, err := route.GetMethods()
- if err != nil {
- return err
- }
-
- allMethods = append(allMethods, methods...)
- }
- break
+ for _, route := range r.routes {
+ var match RouteMatch
+ if route.Match(req, &match) || match.MatchErr == ErrMethodMismatch {
+ methods, err := route.GetMethods()
+ if err != nil {
+ return nil, err
}
+
+ allMethods = append(allMethods, methods...)
}
- return nil
- })
+ }
- return allMethods, err
+ return allMethods, nil
}
diff --git a/vendor/github.com/gorilla/mux/mux.go b/vendor/github.com/gorilla/mux/mux.go
index a2cd193e4..c9ba64707 100644
--- a/vendor/github.com/gorilla/mux/mux.go
+++ b/vendor/github.com/gorilla/mux/mux.go
@@ -5,6 +5,7 @@
package mux
import (
+ "context"
"errors"
"fmt"
"net/http"
@@ -58,8 +59,7 @@ type Router struct {
// If true, do not clear the request context after handling the request.
//
- // Deprecated: No effect when go1.7+ is used, since the context is stored
- // on the request itself.
+ // Deprecated: No effect, since the context is stored on the request itself.
KeepContext bool
// Slice of middlewares to be called after a match is found
@@ -111,10 +111,8 @@ func copyRouteConf(r routeConf) routeConf {
c.regexp.queries = append(c.regexp.queries, copyRouteRegexp(q))
}
- c.matchers = make([]matcher, 0, len(r.matchers))
- for _, m := range r.matchers {
- c.matchers = append(c.matchers, m)
- }
+ c.matchers = make([]matcher, len(r.matchers))
+ copy(c.matchers, r.matchers)
return c
}
@@ -197,8 +195,8 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var handler http.Handler
if r.Match(req, &match) {
handler = match.Handler
- req = setVars(req, match.Vars)
- req = setCurrentRoute(req, match.Route)
+ req = requestWithVars(req, match.Vars)
+ req = requestWithRoute(req, match.Route)
}
if handler == nil && match.MatchErr == ErrMethodMismatch {
@@ -428,7 +426,7 @@ const (
// Vars returns the route variables for the current request, if any.
func Vars(r *http.Request) map[string]string {
- if rv := contextGet(r, varsKey); rv != nil {
+ if rv := r.Context().Value(varsKey); rv != nil {
return rv.(map[string]string)
}
return nil
@@ -440,18 +438,20 @@ func Vars(r *http.Request) map[string]string {
// after the handler returns, unless the KeepContext option is set on the
// Router.
func CurrentRoute(r *http.Request) *Route {
- if rv := contextGet(r, routeKey); rv != nil {
+ if rv := r.Context().Value(routeKey); rv != nil {
return rv.(*Route)
}
return nil
}
-func setVars(r *http.Request, val interface{}) *http.Request {
- return contextSet(r, varsKey, val)
+func requestWithVars(r *http.Request, vars map[string]string) *http.Request {
+ ctx := context.WithValue(r.Context(), varsKey, vars)
+ return r.WithContext(ctx)
}
-func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
- return contextSet(r, routeKey, val)
+func requestWithRoute(r *http.Request, route *Route) *http.Request {
+ ctx := context.WithValue(r.Context(), routeKey, route)
+ return r.WithContext(ctx)
}
// ----------------------------------------------------------------------------
diff --git a/vendor/github.com/gorilla/mux/regexp.go b/vendor/github.com/gorilla/mux/regexp.go
index ac1abcd47..96dd94ad1 100644
--- a/vendor/github.com/gorilla/mux/regexp.go
+++ b/vendor/github.com/gorilla/mux/regexp.go
@@ -181,21 +181,21 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
}
}
return r.regexp.MatchString(host)
- } else {
- if r.regexpType == regexpTypeQuery {
- return r.matchQueryString(req)
- }
- path := req.URL.Path
- if r.options.useEncodedPath {
- path = req.URL.EscapedPath()
- }
- return r.regexp.MatchString(path)
}
+
+ if r.regexpType == regexpTypeQuery {
+ return r.matchQueryString(req)
+ }
+ path := req.URL.Path
+ if r.options.useEncodedPath {
+ path = req.URL.EscapedPath()
+ }
+ return r.regexp.MatchString(path)
}
// url builds a URL part using the given values.
func (r *routeRegexp) url(values map[string]string) (string, error) {
- urlValues := make([]interface{}, len(r.varsN))
+ urlValues := make([]interface{}, len(r.varsN), len(r.varsN))
for k, v := range r.varsN {
value, ok := values[v]
if !ok {
@@ -230,14 +230,51 @@ func (r *routeRegexp) getURLQuery(req *http.Request) string {
return ""
}
templateKey := strings.SplitN(r.template, "=", 2)[0]
- for key, vals := range req.URL.Query() {
- if key == templateKey && len(vals) > 0 {
- return key + "=" + vals[0]
- }
+ val, ok := findFirstQueryKey(req.URL.RawQuery, templateKey)
+ if ok {
+ return templateKey + "=" + val
}
return ""
}
+// findFirstQueryKey returns the same result as (*url.URL).Query()[key][0].
+// If key was not found, empty string and false is returned.
+func findFirstQueryKey(rawQuery, key string) (value string, ok bool) {
+ query := []byte(rawQuery)
+ for len(query) > 0 {
+ foundKey := query
+ if i := bytes.IndexAny(foundKey, "&;"); i >= 0 {
+ foundKey, query = foundKey[:i], foundKey[i+1:]
+ } else {
+ query = query[:0]
+ }
+ if len(foundKey) == 0 {
+ continue
+ }
+ var value []byte
+ if i := bytes.IndexByte(foundKey, '='); i >= 0 {
+ foundKey, value = foundKey[:i], foundKey[i+1:]
+ }
+ if len(foundKey) < len(key) {
+ // Cannot possibly be key.
+ continue
+ }
+ keyString, err := url.QueryUnescape(string(foundKey))
+ if err != nil {
+ continue
+ }
+ if keyString != key {
+ continue
+ }
+ valueString, err := url.QueryUnescape(string(value))
+ if err != nil {
+ continue
+ }
+ return valueString, true
+ }
+ return "", false
+}
+
func (r *routeRegexp) matchQueryString(req *http.Request) bool {
return r.regexp.MatchString(r.getURLQuery(req))
}
diff --git a/vendor/github.com/gorilla/mux/route.go b/vendor/github.com/gorilla/mux/route.go
index 8479c68c1..750afe570 100644
--- a/vendor/github.com/gorilla/mux/route.go
+++ b/vendor/github.com/gorilla/mux/route.go
@@ -74,7 +74,7 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
return false
}
- if match.MatchErr == ErrMethodMismatch {
+ if match.MatchErr == ErrMethodMismatch && r.handler != nil {
// We found a route which matches request method, clear MatchErr
match.MatchErr = nil
// Then override the mis-matched handler
@@ -412,11 +412,30 @@ func (r *Route) Queries(pairs ...string) *Route {
type schemeMatcher []string
func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
- return matchInArray(m, r.URL.Scheme)
+ scheme := r.URL.Scheme
+ // https://golang.org/pkg/net/http/#Request
+ // "For [most] server requests, fields other than Path and RawQuery will be
+ // empty."
+ // Since we're an http muxer, the scheme is either going to be http or https
+ // though, so we can just set it based on the tls termination state.
+ if scheme == "" {
+ if r.TLS == nil {
+ scheme = "http"
+ } else {
+ scheme = "https"
+ }
+ }
+ return matchInArray(m, scheme)
}
// Schemes adds a matcher for URL schemes.
// It accepts a sequence of schemes to be matched, e.g.: "http", "https".
+// If the request's URL has a scheme set, it will be matched against.
+// Generally, the URL scheme will only be set if a previous handler set it,
+// such as the ProxyHeaders handler from gorilla/handlers.
+// If unset, the scheme will be determined based on the request's TLS
+// termination state.
+// The first argument to Schemes will be used when constructing a route URL.
func (r *Route) Schemes(schemes ...string) *Route {
for k, v := range schemes {
schemes[k] = strings.ToLower(v)
@@ -493,8 +512,8 @@ func (r *Route) Subrouter() *Router {
// This also works for host variables:
//
// r := mux.NewRouter()
-// r.Host("{subdomain}.domain.com").
-// HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
+// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
+// Host("{subdomain}.domain.com").
// Name("article")
//
// // url.String() will be "http://news.domain.com/articles/technology/42"
@@ -502,6 +521,13 @@ func (r *Route) Subrouter() *Router {
// "category", "technology",
// "id", "42")
//
+// The scheme of the resulting url will be the first argument that was passed to Schemes:
+//
+// // url.String() will be "https://example.com"
+// r := mux.NewRouter()
+// url, err := r.Host("example.com")
+// .Schemes("https", "http").URL()
+//
// All variables defined in the route are required, and their values must
// conform to the corresponding patterns.
func (r *Route) URL(pairs ...string) (*url.URL, error) {
@@ -635,7 +661,7 @@ func (r *Route) GetQueriesRegexp() ([]string, error) {
if r.regexp.queries == nil {
return nil, errors.New("mux: route doesn't have queries")
}
- var queries []string
+ queries := make([]string, 0, len(r.regexp.queries))
for _, query := range r.regexp.queries {
queries = append(queries, query.regexp.String())
}
@@ -654,7 +680,7 @@ func (r *Route) GetQueriesTemplates() ([]string, error) {
if r.regexp.queries == nil {
return nil, errors.New("mux: route doesn't have queries")
}
- var queries []string
+ queries := make([]string, 0, len(r.regexp.queries))
for _, query := range r.regexp.queries {
queries = append(queries, query.template)
}
diff --git a/vendor/github.com/gorilla/mux/test_helpers.go b/vendor/github.com/gorilla/mux/test_helpers.go
index 32ecffde4..5f5c496de 100644
--- a/vendor/github.com/gorilla/mux/test_helpers.go
+++ b/vendor/github.com/gorilla/mux/test_helpers.go
@@ -15,5 +15,5 @@ import "net/http"
// can be set by making a route that captures the required variables,
// starting a server and sending the request to that server.
func SetURLVars(r *http.Request, val map[string]string) *http.Request {
- return setVars(r, val)
+ return requestWithVars(r, val)
}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 5c2485f38..73bca1ef8 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -291,7 +291,7 @@ github.com/google/gofuzz
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 v1.7.4
github.com/gorilla/mux
# github.com/gorilla/schema v1.1.0
github.com/gorilla/schema
@@ -630,9 +630,9 @@ gopkg.in/square/go-jose.v2/json
gopkg.in/tomb.v1
# gopkg.in/yaml.v2 v2.2.8
gopkg.in/yaml.v2
-# k8s.io/api v0.17.2
+# k8s.io/api v0.17.3
k8s.io/api/core/v1
-# k8s.io/apimachinery v0.17.2
+# k8s.io/apimachinery v0.17.3
k8s.io/apimachinery/pkg/api/errors
k8s.io/apimachinery/pkg/api/resource
k8s.io/apimachinery/pkg/apis/meta/v1