update to use generated files for assets

Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
Jess Frazelle 2018-09-17 15:23:04 -04:00
parent 695602b303
commit 0301f323b3
No known key found for this signature in database
GPG key ID: 18F3685C0022BFF3
288 changed files with 25331 additions and 4170 deletions

View file

@ -11,7 +11,7 @@ jobs:
include: include:
- script: - script:
- make fmt lint staticcheck vet install - make fmt lint staticcheck vet install
- DOCKER_API_VERSION=1.37 make dind dtest - DOCKER_API_VERSION=1.38 make dind dtest
- stage: Build Release - stage: Build Release
script: script:
- make release - make release

View file

@ -28,9 +28,6 @@ FROM scratch
COPY --from=builder /usr/bin/reg /usr/bin/reg COPY --from=builder /usr/bin/reg /usr/bin/reg
COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs
COPY server/static /src/static
COPY server/templates /src/templates
WORKDIR /src WORKDIR /src
ENTRYPOINT [ "reg" ] ENTRYPOINT [ "reg" ]

116
Gopkg.lock generated
View file

@ -13,12 +13,12 @@
revision = "d6e3b3328b783f23731bc4d058875b0371ff8109" revision = "d6e3b3328b783f23731bc4d058875b0371ff8109"
[[projects]] [[projects]]
digest = "1:52200fa7808b1ee999b1f5a8daf19a6c79b6e2077956125f55c100e633d8bde3" digest = "1:2be791e7b333ff7c06f8fb3dc18a7d70580e9399dbdffd352621d067ff260b6e"
name = "github.com/Microsoft/go-winio" name = "github.com/Microsoft/go-winio"
packages = ["."] packages = ["."]
pruneopts = "NUT" pruneopts = "NUT"
revision = "67921128fb397dd80339870d2193d6b1e6856fd4" revision = "97e4973ce50b2ff5f09635a57e2b88a037aae829"
version = "v0.4.8" version = "v0.4.11"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -38,11 +38,11 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:fc8dbcc2a5de7c093e167828ebbdf551641761d2ad75431d3a167d467a264115" digest = "1:4a029051269e04c040c092eb4ddd92732f8f3a3921a8b43b82b30804e00f3357"
name = "github.com/containerd/continuity" name = "github.com/containerd/continuity"
packages = ["pathdriver"] packages = ["pathdriver"]
pruneopts = "NUT" pruneopts = "NUT"
revision = "0377f7d767206f3a9e8881d0f02267b0d89c7a62" revision = "c2ac4ecc959316e616c37fd95143e972811bd12e"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -59,7 +59,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:30aa048f2c90ef2d0568cce07cb8afbaeabbec3ccd75f99d22864039c273bcc8" digest = "1:3d253a76fba28c8ed28b517fd89b362b4e0116d05833023bcc987111b5aa9b15"
name = "github.com/docker/cli" name = "github.com/docker/cli"
packages = [ packages = [
"cli/config/configfile", "cli/config/configfile",
@ -67,11 +67,11 @@
"opts", "opts",
] ]
pruneopts = "NUT" pruneopts = "NUT"
revision = "b395d2d6f5eec2c047e6bba4a3fd941d5757d725" revision = "b4180e8757e9d5f9b38813e10c4f4a1c0f6643b5"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:f8b9eb707f84a5f01f1537f2086962590855bb63a616eb35c02c437f60431018" digest = "1:1aed2b27277a9c4a2b3b40abf3e0484704a35482f24a804e7791c5172dcbf323"
name = "github.com/docker/distribution" name = "github.com/docker/distribution"
packages = [ packages = [
".", ".",
@ -92,11 +92,11 @@
"registry/storage/cache/memory", "registry/storage/cache/memory",
] ]
pruneopts = "NUT" pruneopts = "NUT"
revision = "749f6afb4572201e3c37325d0ffedb6f32be8950" revision = "b089e91688254eb5aef187f188c8ee0ebe4e4675"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:f8ac8d8219f7afe01d3821f3ed2327af7263390227369456c2a603f8659e88d3" digest = "1:29566f3fa9f66bf12978baba01329b29da05526cace5c15a74e1a61f7f3a4381"
name = "github.com/docker/docker" name = "github.com/docker/docker"
packages = [ packages = [
"api", "api",
@ -132,15 +132,15 @@
"registry/resumable", "registry/resumable",
] ]
pruneopts = "NUT" pruneopts = "NUT"
revision = "492545e139e7461aac044149a931bb4b2dd48f75" revision = "ed392603acbf7849aa68d760c67d9c645ac035d8"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:3d29840a1e0fee12b70d874896b3c4f63507049e2dac83a066b583ff513c67bf" digest = "1:961fe5dd401b2284aff5141c1076e6a5cce10fcf2e6440b79bb7e2ea353d1c01"
name = "github.com/docker/docker-ce" name = "github.com/docker/docker-ce"
packages = ["components/cli/cli/config"] packages = ["components/cli/cli/config"]
pruneopts = "NUT" pruneopts = "NUT"
revision = "2ec1cede27a3dc04c44f8ed2feb1efb00c724d63" revision = "d156a93844295ae887439d37031e71737db7b5e0"
[[projects]] [[projects]]
digest = "1:8866486038791fe65ea1abf660041423954b1f3fb99ea6a0ad8424422e943458" digest = "1:8866486038791fe65ea1abf660041423954b1f3fb99ea6a0ad8424422e943458"
@ -155,7 +155,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:2a47f7eb1a2c30428d1ee6808cb66d4deb17e68a3e55d696f03c8068552ba5e8" digest = "1:42cfcc9461365aacb806b9cfe929c926f0d3fb0f692bfc10c375872f2881b2f2"
name = "github.com/docker/go-connections" name = "github.com/docker/go-connections"
packages = [ packages = [
"nat", "nat",
@ -163,7 +163,7 @@
"tlsconfig", "tlsconfig",
] ]
pruneopts = "NUT" pruneopts = "NUT"
revision = "7395e3f8aa162843a74ed6d48e79627d9792ac55" revision = "97c2040d34dfae1d1b1275fa3a78dbdd2f41cf7e"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -199,22 +199,22 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:26bac590d2ee7a8ace363d3b8f84e3f790e732470ba54995c9bf722827fc96c4" digest = "1:bd228d1a02fffcaf3abca973886870113d843ea7a67d5b6f70a5169950499ea9"
name = "github.com/genuinetools/pkg" name = "github.com/genuinetools/pkg"
packages = ["cli"] packages = ["cli"]
pruneopts = "NUT" pruneopts = "NUT"
revision = "3654fc151753f8cd41b366e0c15b9fa070890ddf" revision = "1c141f661797b5fb22033c9a7a5ab004994a0404"
[[projects]] [[projects]]
digest = "1:38e684375ef5b55e812332266d63f9fc5b6329ab303067c4cdda051db6d29ca4" digest = "1:38e684375ef5b55e812332266d63f9fc5b6329ab303067c4cdda051db6d29ca4"
name = "github.com/gogo/protobuf" name = "github.com/gogo/protobuf"
packages = ["proto"] packages = ["proto"]
pruneopts = "NUT" pruneopts = "NUT"
revision = "7d68e886eac4f7e34d0d82241a6273d6c304c5cf" revision = "636bf0302bc95575d69441b25a2603156ffdddf1"
version = "v1.1.0" version = "v1.1.1"
[[projects]] [[projects]]
digest = "1:6aef947ba53156da1a66ee891d70d61835e0dcfc9f0d728ae1132db651e81c22" digest = "1:9b117ac202b9de210a7093bca0c29d0d2424e4c9235c9e025ae0d6ef6b121c82"
name = "github.com/golang/protobuf" name = "github.com/golang/protobuf"
packages = [ packages = [
"jsonpb", "jsonpb",
@ -227,8 +227,8 @@
"ptypes/timestamp", "ptypes/timestamp",
] ]
pruneopts = "NUT" pruneopts = "NUT"
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265" revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5"
version = "v1.1.0" version = "v1.2.0"
[[projects]] [[projects]]
digest = "1:2e3c336fc7fde5c984d2841455a658a6d626450b1754a854b3b32e7a8f49a07a" digest = "1:2e3c336fc7fde5c984d2841455a658a6d626450b1754a854b3b32e7a8f49a07a"
@ -260,7 +260,7 @@
version = "v1.6.2" version = "v1.6.2"
[[projects]] [[projects]]
digest = "1:5c3363169fc963321cd0789dda2ddff90562817d277e78011b0e1bbf51a6b473" digest = "1:228dfa88f068306d5d23fb3efac8e61b868e1d327aac6eebc21f0f15b03e44e4"
name = "github.com/grpc-ecosystem/grpc-gateway" name = "github.com/grpc-ecosystem/grpc-gateway"
packages = [ packages = [
"runtime", "runtime",
@ -268,8 +268,8 @@
"utilities", "utilities",
] ]
pruneopts = "NUT" pruneopts = "NUT"
revision = "92583770e3f01b09a0d3e9bdf64321d8bebd48f2" revision = "8558711daa6c2853489043207b563dceacbc19cf"
version = "v1.4.1" version = "v1.5.0"
[[projects]] [[projects]]
digest = "1:5985ef4caf91ece5d54817c11ea25f182697534f8ae6521eadcd628c142ac4b6" digest = "1:5985ef4caf91ece5d54817c11ea25f182697534f8ae6521eadcd628c142ac4b6"
@ -280,12 +280,12 @@
version = "v1.0.1" version = "v1.0.1"
[[projects]] [[projects]]
branch = "master"
digest = "1:9f63926f52745d1f9d6053f8fbbb3bd3983c2c97c0565957e682b8d2f7aad3af" digest = "1:9f63926f52745d1f9d6053f8fbbb3bd3983c2c97c0565957e682b8d2f7aad3af"
name = "github.com/mitchellh/go-wordwrap" name = "github.com/mitchellh/go-wordwrap"
packages = ["."] packages = ["."]
pruneopts = "NUT" pruneopts = "NUT"
revision = "ad45545899c7b13c020ea92b2072220eefad42b8" revision = "9e67c67572bc5dd02aef930e2b0ae3c02a4b5a5c"
version = "v1.0.0"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -332,14 +332,15 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:f434beb84b411972e928573deaf8776680c5359d2c6859720a86fc23fe06d27c" digest = "1:cfa8aa49949ef9e612512736234abd8023cffd7bc76865afcfeec9846b246daf"
name = "github.com/prometheus/client_golang" name = "github.com/prometheus/client_golang"
packages = [ packages = [
"prometheus", "prometheus",
"prometheus/internal",
"prometheus/promhttp", "prometheus/promhttp",
] ]
pruneopts = "NUT" pruneopts = "NUT"
revision = "bcbbc08eb2ddff3af83bbf11e7ec13b4fd730b6e" revision = "e637cec7d9c8990247098639ebc6d43dd34ddd49"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -351,7 +352,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:768b555b86742de2f28beb37f1dedce9a75f91f871d75b5717c96399c1a78c08" digest = "1:fad5a35eea6a1a33d6c8f949fbc146f24275ca809ece854248187683f52cc30b"
name = "github.com/prometheus/common" name = "github.com/prometheus/common"
packages = [ packages = [
"expfmt", "expfmt",
@ -359,7 +360,7 @@
"model", "model",
] ]
pruneopts = "NUT" pruneopts = "NUT"
revision = "7600349dcfe1abd18d72d3a1770870d9800a7801" revision = "c7de2306084e37d54b8be01f3541a8464345e9a5"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -372,15 +373,35 @@
"xfs", "xfs",
] ]
pruneopts = "NUT" pruneopts = "NUT"
revision = "ae68e2d4c00fed4943b5f6698d504a5fe083da8a" revision = "05ee40e3a273f7245e8777337fc7b46e533a9a92"
[[projects]] [[projects]]
digest = "1:6989062eb7ccf25cf38bf4fe3dba097ee209f896cda42cefdca3927047bef7b6" branch = "master"
digest = "1:25eadade1fa09cbada860b0f17e559a40b97feea133ff73d8d6451be5df6d9a4"
name = "github.com/shurcooL/httpfs"
packages = [
"html/vfstemplate",
"path/vfspath",
"vfsutil",
]
pruneopts = "NUT"
revision = "809beceb23714880abc4a382a00c05f89d13b1cc"
[[projects]]
branch = "master"
digest = "1:43c44ab8b001881225afe48c6aedcf2dd4f93495efd4586d1649c9dc2c7c7eaa"
name = "github.com/shurcooL/vfsgen"
packages = ["."]
pruneopts = "NUT"
revision = "33ae1944be3fe078a54c597f107e0867da19c713"
[[projects]]
digest = "1:b2339e83ce9b5c4f79405f949429a7f68a9a904fed903c672aac1e7ceb7f5f02"
name = "github.com/sirupsen/logrus" name = "github.com/sirupsen/logrus"
packages = ["."] packages = ["."]
pruneopts = "NUT" pruneopts = "NUT"
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc" revision = "3e01752db0189b9157070a0e1668a620f9a85da2"
version = "v1.0.5" version = "v1.0.6"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -388,11 +409,11 @@
name = "golang.org/x/crypto" name = "golang.org/x/crypto"
packages = ["ssh/terminal"] packages = ["ssh/terminal"]
pruneopts = "NUT" pruneopts = "NUT"
revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602" revision = "0e37d006457bf46f9e6692014ba72ef82c33022c"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:fef55c9d9f298ab9816a672db63040c2de40d7895de3b8ee74e53979f433b93e" digest = "1:8f96a3abd7277a1f433c5b7036c00f21188fd6a36ff1e8c9c1681ab14476e192"
name = "golang.org/x/net" name = "golang.org/x/net"
packages = [ packages = [
"context", "context",
@ -407,18 +428,18 @@
"trace", "trace",
] ]
pruneopts = "NUT" pruneopts = "NUT"
revision = "d0887baf81f4598189d4e12a37c6da86f0bba4d0" revision = "26e67e76b6c3f6ce91f7c52def5af501b4e0f3a2"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:5420733d35e5e562fffc7c5b99660f3587af042b5420f19b17fe0426e6a5259d" digest = "1:581828cee9f0d31195c1ae52ea8935dfaec395cee6c23adc02109b892b391c2e"
name = "golang.org/x/sys" name = "golang.org/x/sys"
packages = [ packages = [
"unix", "unix",
"windows", "windows",
] ]
pruneopts = "NUT" pruneopts = "NUT"
revision = "ac767d655b305d4e9612f5f6e33120b9176c4ad4" revision = "d0be0721c37eeb5299f245a996a483160fc36940"
[[projects]] [[projects]]
digest = "1:e7071ed636b5422cc51c0e3a6cebc229d6c9fffc528814b519a980641422d619" digest = "1:e7071ed636b5422cc51c0e3a6cebc229d6c9fffc528814b519a980641422d619"
@ -445,17 +466,17 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:eff11a4ed88d9d3e28dd697961c550af1c4d0d3dccdc6252715b9a65a1d541bb" digest = "1:42448f154e7d40907ae03cbdf99dbcfa3feb927c4521e0f01c52c6142e4891d5"
name = "google.golang.org/genproto" name = "google.golang.org/genproto"
packages = [ packages = [
"googleapis/api/annotations", "googleapis/api/annotations",
"googleapis/rpc/status", "googleapis/rpc/status",
] ]
pruneopts = "NUT" pruneopts = "NUT"
revision = "e92b116572682a5b432ddd840aeaba2a559eeff1" revision = "4b56f30a1fd96a133a036b62cdd2a249883dd89b"
[[projects]] [[projects]]
digest = "1:d0e5846da58e4e20d1bbd7502b24f31d37e3ac1e02a9042d95bd93c2d0c139dd" digest = "1:5b805b8e03b29399b344655cac16873f026e54dc0a7c17b381f6f4d4c7b6d741"
name = "google.golang.org/grpc" name = "google.golang.org/grpc"
packages = [ packages = [
".", ".",
@ -471,7 +492,9 @@
"internal", "internal",
"internal/backoff", "internal/backoff",
"internal/channelz", "internal/channelz",
"internal/envconfig",
"internal/grpcrand", "internal/grpcrand",
"internal/transport",
"keepalive", "keepalive",
"metadata", "metadata",
"naming", "naming",
@ -482,11 +505,10 @@
"stats", "stats",
"status", "status",
"tap", "tap",
"transport",
] ]
pruneopts = "NUT" pruneopts = "NUT"
revision = "168a6198bcb0ef175f7dacec0b8691fc141dc9b8" revision = "8dea3dc473e90c8179e519d91302d0597c0ca1d1"
version = "v1.13.0" version = "v1.15.0"
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
@ -510,6 +532,8 @@
"github.com/mitchellh/go-wordwrap", "github.com/mitchellh/go-wordwrap",
"github.com/opencontainers/go-digest", "github.com/opencontainers/go-digest",
"github.com/peterhellberg/link", "github.com/peterhellberg/link",
"github.com/shurcooL/httpfs/html/vfstemplate",
"github.com/shurcooL/vfsgen",
"github.com/sirupsen/logrus", "github.com/sirupsen/logrus",
"google.golang.org/grpc", "google.golang.org/grpc",
] ]

View file

@ -63,3 +63,11 @@
[[override]] [[override]]
name = "github.com/prometheus/client_golang" name = "github.com/prometheus/client_golang"
branch = "master" branch = "master"
[[constraint]]
name = "github.com/shurcooL/vfsgen"
branch = "master"
[[constraint]]
name = "github.com/shurcooL/httpfs"
branch = "master"

View file

@ -16,10 +16,20 @@ func main() {
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
// Generate server assets.
assets := http.Dir(filepath.Join(wd, "server/static")) assets := http.Dir(filepath.Join(wd, "server/static"))
if err := vfsgen.Generate(assets, vfsgen.Options{ if err := vfsgen.Generate(assets, vfsgen.Options{
Filename: filepath.Join(wd, "internal/binutils", "static.go"), Filename: filepath.Join(wd, "internal/binutils/static", "static.go"),
PackageName: "binutils", PackageName: "static",
VariableName: "Assets",
}); err != nil {
logrus.Fatal(err)
}
// Generate template assets.
assets = http.Dir(filepath.Join(wd, "server/templates"))
if err := vfsgen.Generate(assets, vfsgen.Options{
Filename: filepath.Join(wd, "internal/binutils/templates", "templates.go"),
PackageName: "templates",
VariableName: "Assets", VariableName: "Assets",
}); err != nil { }); err != nil {
logrus.Fatal(err) logrus.Fatal(err)

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,200 @@
// Code generated by vfsgen; DO NOT EDIT.
package templates
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
pathpkg "path"
"time"
)
// Assets statically implements the virtual filesystem provided to vfsgen.
var Assets = func() http.FileSystem {
fs := vfsgen۰FS{
"/": &vfsgen۰DirInfo{
name: "/",
modTime: time.Date(2018, 9, 10, 21, 40, 58, 715425786, time.UTC),
},
"/repositories.html": &vfsgen۰CompressedFileInfo{
name: "repositories.html",
modTime: time.Date(2018, 9, 10, 21, 40, 58, 715425786, time.UTC),
uncompressedSize: 1904,
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x55\xc9\x6e\xe4\x36\x10\xbd\xf7\x57\x94\x89\x39\x8e\x44\x18\x39\x8c\xd3\xa0\x84\x04\x1e\x1f\x0c\x64\x19\x18\x31\x90\x60\x30\x07\x36\x55\x92\x68\x53\xa4\x86\x2c\xb5\xd1\x51\xf4\xef\x01\xb5\xa0\x57\x1b\xce\x21\xba\x50\x7c\xf5\xea\xb1\x16\xaa\xd4\xf7\x05\x96\xda\x22\x30\x8f\xad\x0b\x9a\x9c\xd7\x18\xd8\x30\xac\xc4\xd5\xe7\xdf\x6f\xff\xf8\xeb\xcb\x1d\xd4\xd4\x98\x7c\x25\xae\x92\xe4\xab\x2e\xc1\x10\xdc\xdf\xc1\xa7\x6f\x39\x8c\x8f\x88\x56\x50\x46\x86\x90\x31\xeb\x92\xa7\x00\x86\x12\x8d\x3f\x4e\xcb\xcd\xb4\x7c\x62\x39\x88\xab\xaf\x68\x0b\x5d\x7e\x4b\x92\xbd\xda\xa1\xd4\x3b\xd4\xde\x90\xb9\x79\x8f\xcc\x6b\xfe\x15\xcd\x12\x11\xc8\x2f\xf8\x8f\x8e\x49\x72\xec\x5c\xa3\x2c\xf2\xd5\x78\x60\x83\x24\x41\xd5\xd2\x07\xa4\x8c\x75\x54\x26\x37\x6c\x36\x6d\x64\x40\xa8\x3d\x96\x19\xe3\x0c\x0e\xf9\x35\x51\x9b\xe0\xf7\x4e\x6f\x33\xf6\x67\xf2\xf8\x73\x72\xeb\x9a\x56\x92\xde\x18\x64\xa0\x9c\x25\xb4\x94\xb1\xfb\xbb\x0c\x8b\x0a\x3f\xaa\xda\xbb\x06\xb3\xeb\x45\x97\x34\x19\xcc\xfb\x1e\xd2\x07\xac\x74\x20\xbf\xfb\xec\x1a\xa9\x2d\x0c\x83\xe0\x93\x71\x22\x1a\x6d\x9f\xc1\xa3\xc9\x98\x56\xce\x32\xa0\x5d\x8b\x19\xd3\x8d\xac\x90\x6b\xe5\xd8\x12\x5c\x20\x49\x5a\xf1\x52\x6e\x23\x2f\x8d\xa6\x33\x85\x40\x3b\x83\xa1\x46\xa4\x53\x37\x15\x02\x9f\xac\xa9\x0a\x81\x01\xcf\x57\x82\x4f\x15\x12\x1b\x57\xec\x66\xa9\xfa\xfa\xb5\x90\xeb\xeb\x99\x52\x3a\xdf\x4c\xaf\xe3\x56\xdb\xb6\x23\xb0\xb2\xc1\x8c\x95\xda\x10\xfa\x25\x85\x80\xd2\xab\x9a\xe5\x42\x2e\xbd\x52\x06\xa5\x67\xf9\xb8\x08\x2e\x67\x41\x3e\x29\x4e\x9b\x42\x6f\x17\xf6\x8b\x97\x6d\x8b\x9e\x1d\x1c\x46\x72\xb3\xd4\x6d\x8f\xf9\x63\x60\x02\xeb\xfc\x61\xf9\x62\x76\xf0\x9b\x6c\x50\x70\xaa\x2f\x13\xbf\x74\xc6\xc0\xad\x6b\x1a\x69\x8b\x73\x96\xe0\xa7\x07\xf4\x3d\x78\x69\x2b\x84\x0f\xcf\xb8\xfb\x08\x1f\xb6\xd2\x74\x08\xeb\x2c\x96\x6d\xff\x91\xc2\x30\xbc\x27\xce\x02\xb6\xd2\xe8\xca\x66\x8c\x5c\xcb\xce\x19\x23\x4b\x2e\xcd\x8c\x53\x80\xf7\xfd\x7c\x66\x1a\xf3\x82\x7f\xa0\xf3\xe6\x7b\x87\x7e\x07\xc3\xc0\x49\x56\xe1\x15\x99\x13\xbf\x93\xf8\xf6\xf9\xca\x0b\x71\x72\x2a\xe6\x0e\x9d\x86\x3f\x47\xef\x75\x55\x13\x03\xeb\x62\xd7\xfe\xd7\x34\x84\x72\x05\xe6\x85\x53\xcf\xe8\xa1\x8d\xbd\xdb\x2b\x3d\x3e\xdc\x8f\x97\x75\xa4\xfc\xd7\xf4\x4e\x90\xf3\xae\xa3\x2d\x0e\xab\x26\xf8\xc1\x75\x14\xbc\xd0\xdb\x0b\x97\xb8\x74\x8e\x8e\xef\x70\x9b\xff\x2a\x0b\x84\x17\x4d\xf5\x9c\x8b\xf8\x61\x8e\x18\x36\xbb\x7d\x95\xe2\xf8\x09\x6b\xce\x2b\x4d\x75\xb7\x49\x95\x6b\xf8\x13\x86\x50\x7a\xf9\x37\xcb\x7f\x5a\x5e\x63\x3e\x82\xb7\x47\xfa\xb7\x35\xaa\x67\xd7\x11\x50\x8d\x10\x5c\xe7\x15\x42\xd4\x07\x49\xeb\x37\xf5\x2b\xb4\x9d\xb6\x48\xce\x99\xc0\x3d\x56\x2c\x7f\xc3\x78\xe9\xe4\x5f\x64\x20\x78\x6c\x0b\x49\x58\xac\x63\xc9\xd2\x88\xcc\xc0\xd8\x99\x63\xfe\x64\x81\x7b\x4b\xe8\xb7\xd2\xac\xc7\x32\xa7\x13\xba\x80\x87\x6e\x53\x95\xe3\xa8\xe7\xe9\x54\xd9\x38\xe9\x47\x4b\x50\x5e\xb7\x04\xc1\xab\xfd\xd0\x7b\x0a\x7c\x82\x43\x1a\xff\x11\x62\xde\xc5\xd1\x37\xcd\x3c\xc1\xa7\xbf\x67\xdf\xa3\x2d\x86\x61\xf5\x6f\x00\x00\x00\xff\xff\x82\xfc\x97\x4b\x70\x07\x00\x00"),
},
"/tags.html": &vfsgen۰CompressedFileInfo{
name: "tags.html",
modTime: time.Date(2018, 9, 10, 21, 40, 58, 715425786, time.UTC),
uncompressedSize: 2868,
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x56\xdf\x6f\xdb\x36\x10\x7e\xf7\x5f\x71\x25\x0a\xb4\x05\x2c\x31\xe9\xd6\xb5\x73\x24\x61\x85\x9b\x61\x1d\xf6\x0b\x43\x32\x6c\x08\xf2\x40\x4b\x27\x89\x0e\x4d\xaa\xe4\xc9\x89\xa7\xe9\x7f\x1f\x68\xc9\x8e\xec\xd8\x41\x86\xee\xa1\x7e\x11\x7d\x3f\x3e\x7d\xf7\x9d\xc8\x63\xd3\x64\x98\x4b\x8d\xc0\x48\x14\x8e\xb5\xed\x28\x7a\xf6\xe1\xd7\xe9\xc5\x5f\xbf\x9d\x43\x49\x0b\x95\x8c\xa2\x67\x41\x70\x25\x73\x50\x04\x1f\xcf\xe1\xed\x75\x02\xeb\x5f\xe4\xbd\x90\x2a\xe1\x5c\xcc\xb4\x09\xe6\x0e\x14\x05\x12\xbf\xed\x1e\xef\xba\xc7\x5b\x96\x40\xf4\xec\x0a\x75\x26\xf3\xeb\x20\xb8\x47\x1b\x42\x3d\x01\xed\x11\x98\x77\x4f\x81\x39\x96\x5f\x50\x0f\xe1\x0d\xc9\x81\xfc\x75\x62\x10\xec\x26\x97\x28\xb2\x64\xb4\x7e\xe1\x02\x49\x40\x5a\x0a\xeb\x90\x62\x56\x53\x1e\xbc\x63\xbd\x6b\x26\x1c\x42\x69\x31\x8f\x19\x67\x30\x8c\x2f\x89\xaa\x00\x3f\xd5\x72\x19\xb3\x3f\x83\xcb\xf7\xc1\xd4\x2c\x2a\x41\x72\xa6\x90\x41\x6a\x34\xa1\xa6\x98\x7d\x3c\x8f\x31\x2b\x70\x9c\x96\xd6\x2c\x30\x3e\xdd\xe0\x92\x24\x85\x49\xd3\x40\xf8\x3b\x16\xd2\x91\x5d\x7d\x30\x0b\x21\x35\xb4\x2d\xf7\xd6\x5f\xc4\x02\xa1\x6d\x23\xde\x05\x76\x49\x4a\xea\x1b\xb0\xa8\x62\x26\x53\xa3\x19\xd0\xaa\xc2\x98\xc9\x85\x28\x90\xcb\xd4\xb0\x0d\x51\x47\x82\x64\xca\x73\xb1\xf4\x71\xa1\x77\x3d\x40\x70\xb4\x52\xe8\x4a\x44\xda\x4f\x4b\x9d\xe3\x9d\x37\x4c\x9d\x63\xc0\x93\x51\xc4\x3b\xb5\xa2\x99\xc9\x56\x3d\x54\x79\xfa\x14\xfa\xe5\x69\x1f\x9e\xc9\xe5\xa6\x27\xb7\x56\x54\x15\xda\x9e\x53\x27\x87\x98\x6d\xaa\xbc\xb7\xd9\x5d\x43\x67\x2c\x13\x8f\x1d\x71\x2a\x0f\x7b\x2f\x44\x71\xdc\x39\xb5\x28\x08\xb3\xc3\x01\x4d\x23\x73\x08\x7f\x10\xee\x8f\x5a\x69\xd7\xb6\x3e\xc1\x2f\xd1\x8a\x99\x54\x92\x24\xba\x75\x62\xd3\xa0\xce\xda\x76\x97\x2c\xdf\x67\xdb\x34\x60\x85\x2e\x10\x9e\xdf\xe0\x6a\x0c\xcf\x97\x42\xd5\x08\x93\xd8\x2b\x56\x19\x27\xc9\x58\x89\x0e\xf6\x71\x0e\x17\x9d\xc1\x52\x28\x59\xe8\x98\x29\xcc\x89\x81\x36\x5e\xc3\x87\x91\xdb\x32\x9e\x0f\xeb\x10\x9b\x06\x5b\xac\x8c\x6f\x4f\x47\xa6\x6b\xd2\x3f\x50\x5b\xf5\xa9\x46\xbb\xf2\xbd\x23\x51\x0c\x02\x2e\x44\xe1\x8d\x4b\x8f\xc3\x0e\xd6\x3d\xa8\x76\x08\x7a\x34\x6a\x9f\x1a\x17\x47\x61\x23\x4e\xd9\x61\x2d\x7a\x29\xac\x2c\xca\x2f\x5b\x8b\x2e\xe7\x4b\x91\x62\xc3\xaa\xdf\x04\xe1\xf7\xc6\x2e\x04\x01\x3b\x79\x0d\x3f\x0a\x3d\x86\xd7\x27\x27\xdf\xc0\xe9\x9b\xc9\xc9\xd7\x93\x93\x37\x70\x79\x31\x65\x87\xc8\x1f\xe6\xf2\xa0\x9c\xcf\x64\xfb\x3f\xf5\x09\x64\x16\xb3\x07\xdf\xe6\x64\x3f\x9a\x1d\x26\xb1\x7b\x6a\x39\x59\x68\xa1\x58\x12\xf1\x4c\x2e\x8f\xb0\xe6\xe2\x40\x9b\x8e\x08\xf6\xc4\x43\x04\x75\x36\x6c\x43\xc4\x07\x47\x65\x4f\xe5\xc1\x01\x9b\x1b\x43\xbb\xe7\x6b\x95\xfc\x2c\x32\x84\x5b\x49\x25\x44\xa9\xc9\x30\x89\xbe\x8a\xf8\x7a\x01\xb3\xd5\xbd\xdc\x7e\xa8\xb9\x09\xe7\x85\xa4\xb2\x9e\x85\xa9\x59\xf0\x39\x3a\x97\x5b\xf1\x37\x4b\xbe\xdb\x2c\x7d\x9d\x11\xaf\x76\xf0\xa7\x25\xa6\x37\xa6\x26\xa0\x12\xc1\x99\xda\xa6\x08\x1e\x1f\x04\x4d\x1e\xc5\x2f\x50\xd7\x52\x23\x19\xa3\x1c\xb7\x58\xb0\xe4\x11\xe7\xa1\x37\xff\x24\x1c\xc1\x65\x95\xf9\x8f\x7a\xe2\x25\x0b\xbd\xa5\x37\xac\x87\x50\x35\x94\xcb\xdf\x04\x78\xd8\x49\xe4\x2f\x02\x6b\x8f\x4b\xad\xac\x08\x9c\x4d\xef\xe7\xe0\xdc\xf1\xce\xec\x42\x7f\x85\x88\xfa\x7f\x5d\xc6\xfe\xb0\xd8\x81\xe9\xe6\x32\xe1\x1d\xf1\xb9\x58\x8a\xce\xba\x6d\xc7\x52\x58\x10\x73\x71\x37\x15\x4a\x39\x88\xe1\x6a\x34\x68\xf7\x7f\x9e\x19\x2f\x3e\x6b\x87\x84\x73\x67\xf4\x8b\xf1\xe8\xd8\x07\x77\x7d\xd6\x2f\x6e\xa5\xce\xcc\x6d\x68\xb4\x32\x22\x83\x18\xf2\x5a\xa7\x24\x8d\x7e\xf9\x0a\x9a\x6d\xf6\x7b\x6b\xc5\x2a\xac\xac\x21\xe3\x25\x08\x73\x63\xcf\x45\x5a\x86\xa9\x50\xea\xe5\xb6\xe4\xf1\x7d\x72\x6d\xd5\x18\xa4\xce\xf0\xee\x55\x33\xa8\xc9\xbf\x63\x38\x79\x57\x53\x53\x6b\xf2\xd1\xaf\xce\xb6\x61\xed\x76\xdd\x9e\xf5\xed\xdd\x6d\x50\xb7\xc1\x22\xde\xdd\x59\x22\xde\xdd\x84\x37\xf6\x7f\x03\x00\x00\xff\xff\x88\x1c\x69\x6a\x34\x0b\x00\x00"),
},
"/vulns.html": &vfsgen۰CompressedFileInfo{
name: "vulns.html",
modTime: time.Date(2018, 9, 10, 21, 40, 58, 715425786, time.UTC),
uncompressedSize: 2715,
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xa4\x56\xdf\x6f\xdb\x36\x10\x7e\xcf\x5f\x71\x21\xfa\x18\x99\x48\x82\xa1\x59\x40\x0b\x5b\x93\x60\x2b\xd0\xfd\x40\x9b\x0e\x1b\x8a\x62\xa0\xa4\xb3\xc4\x86\x22\x35\xf2\xe4\xcd\x13\xf4\xbf\x0f\x12\x2d\x5b\x96\xec\x2c\xc3\xfc\x20\x4b\xbe\xbb\xef\xee\xfb\xee\x74\x74\xd3\x64\xb8\x52\x06\x81\xad\x6b\x6d\x3c\x6b\xdb\x33\x71\x7e\xff\xd3\xdd\xe3\x6f\x3f\x3f\x40\x41\xa5\x8e\xcf\xc4\x79\x14\x7d\x52\x2b\xd0\x04\x6f\x1f\xe0\xf5\xe7\x18\xfa\x8f\xe8\xac\x90\x6a\xe9\xfd\x92\x19\x1b\x7d\xf1\xa0\x29\x52\xf8\x75\xf8\xba\x09\x5f\xaf\x59\x0c\xe2\xfc\x13\x9a\x4c\xad\x3e\x47\xd1\x1e\x6d\x0c\xf5\x02\xb4\x67\x60\x6e\x5e\x02\x73\x2a\x3e\xa7\x2d\x44\xf7\x43\x7c\x24\xbe\x0f\x8c\xa2\xc3\xe0\x02\x65\x16\x9f\xf5\x09\x4b\x24\x09\x69\x21\x9d\x47\x5a\xb2\x9a\x56\xd1\x0d\xdb\x9a\x12\xe9\x11\x0a\x87\xab\x25\xe3\x0c\xc6\xfe\x05\x51\x15\xe1\x1f\xb5\x5a\x2f\xd9\xaf\xd1\xc7\x6f\xa3\x3b\x5b\x56\x92\x54\xa2\x91\x41\x6a\x0d\xa1\xa1\x25\x7b\xfb\xb0\xc4\x2c\xc7\x8b\xb4\x70\xb6\xc4\xe5\xe5\x80\x4b\x8a\x34\xc6\x4d\x03\x8b\xf7\x98\x2b\x4f\x6e\xf3\xf1\xfd\x3b\x68\x5b\x1e\x7e\xaa\x2c\xb4\xed\x6d\x77\xff\x28\x73\x68\x5b\xf8\xa5\xd6\x06\x9d\x4c\x94\x56\xb4\x81\xce\xc1\x91\xe0\x01\x25\x20\x6a\x65\x9e\xc0\xa1\x5e\x32\x95\x5a\xc3\x80\x36\x15\x2e\x99\x2a\x65\x8e\x5c\xa5\x96\x0d\x2c\x3c\x49\x52\x29\x5f\xc9\x75\xe7\xb7\xe8\x4c\x33\x04\x4f\x1b\x8d\xbe\x40\xa4\x69\x58\xea\x3d\x4f\xac\x25\x4f\x4e\x56\x8b\x52\x99\x45\xea\x3d\x03\x1e\x9f\x09\x1e\x14\x15\x89\xcd\x36\x5b\xc4\x4c\xad\x87\x46\x74\x8a\x48\x65\xd0\x6d\xb3\xf5\x76\xbb\xeb\x53\xe2\x50\x66\xa9\xab\xcb\x64\x64\xdf\x56\x15\x0b\xb9\x6b\xc1\x11\xc9\x04\x97\xb1\xe0\x5a\xcd\xe2\x06\x6c\x99\x92\x5a\xe3\x10\x3a\x95\xf6\x30\x54\x70\xab\xe3\xb3\xfd\xe3\x88\x41\x25\x73\x8c\x3a\x8e\x07\x1c\xc2\xc4\x5e\xfe\x87\x56\x0a\x5f\x4a\xad\xe3\xe3\x1d\x0d\x36\xc1\x8b\xcb\x71\x4d\x99\x5a\x8f\x1e\xab\xa1\x22\xc2\xbf\x28\x72\x2a\x2f\x88\xc5\xdf\x61\x87\x46\x98\x81\x35\xb7\xd0\x34\x8b\x7b\x49\xd8\xb1\xab\x46\x74\x9a\x26\xbc\x2e\x8b\x37\x32\xeb\xf2\x7b\xf8\xaa\x6d\x8f\x92\x95\x1a\x1d\x41\x7f\x8d\x32\x69\x72\x74\x0c\x9c\xd5\xb8\xb5\x4c\x04\x68\x9a\x1d\x62\xdb\xc2\xf7\x2a\x2f\x2e\xe0\xce\x29\x52\xa9\xd4\x17\x20\x4d\xc6\xad\x83\x7b\x5c\xa5\xd6\x5c\xc2\x7a\x44\x5c\xa1\x87\x95\xad\x4d\x76\x8a\x6c\xd3\xa0\xc9\xda\x76\xd4\x91\xe2\x2a\xfe\x50\x97\xa5\x74\x1b\xc1\x8b\xab\x91\x2c\xf5\x6e\x98\xb4\xf2\x14\xe5\xce\xd6\xd5\x7c\x98\xe6\x3e\x91\x22\x2c\x27\x8e\xbd\xb3\xaf\xa4\xd9\xcd\xa7\xcc\xf2\x30\x42\x1a\x0d\x2c\x82\x78\x9d\xbc\x9d\xd3\x91\xd8\x24\x7e\xb4\x24\xb5\xe0\xc9\xa4\x82\xd9\xa0\x36\x8d\xeb\xf4\x85\x57\x4f\xb8\xb9\x80\x57\x6b\xa9\x6b\x84\xdb\xe5\x36\xc7\x9b\xcd\x07\x5c\xa3\x53\xb4\x19\x35\xea\xff\x51\xd1\x32\x41\x0d\xfd\x35\x6a\x9a\xd4\x6a\xeb\xfa\xe4\x6d\x0b\x55\xad\xf5\x30\x50\x5b\xaa\xdb\x82\x4e\x53\x6d\x9a\x3e\x1a\xa6\xf5\x1d\x21\x1a\x5a\xb9\xf7\xa8\x0f\x5e\xb5\xe2\x2a\xbe\x47\x92\x4a\xfb\xc3\xc6\xee\x04\xea\x4f\xb7\x7f\x91\x66\xef\x3c\xe8\x18\xc2\x4e\x8c\x79\x25\x0d\x6a\xe8\xaf\x51\x86\x2b\x59\xeb\xe9\x68\xcf\xbc\xfb\x15\xa0\x4c\x7e\x4c\xe6\xe2\xfa\xd0\xb5\xdf\xcf\x9d\x94\xa1\x9c\xc5\x8f\xb2\xc4\x89\x4e\x2f\x6f\x51\x80\xd8\x73\x9e\x74\x6b\x66\x3f\x39\x9b\xbc\xb8\x9e\x0e\xe5\xc1\x3b\x77\x9c\x76\xb7\xd6\x8f\x70\xde\x25\xbe\x47\x9f\x3a\x55\x91\xb2\x66\x36\x0a\x2f\x80\x5f\x59\x4b\xb3\xc5\xda\xbb\x0e\xcb\x7f\x97\xe9\x9d\x32\x4f\x6d\xcb\x80\xa4\xcb\xbb\x03\xfb\xf7\x44\x4b\xf3\x34\xd2\x20\x38\x74\xe7\xc2\x73\x75\x9c\xd8\x34\xd3\xe7\xbd\x7f\x28\xf1\x60\xf9\xa6\x68\xe6\x55\x8b\x2a\xfe\x41\x66\x08\x7f\x2a\x2a\x40\xa4\x36\xc3\x58\x5c\x0b\xde\xdf\x40\xb2\xd9\x33\xea\xfe\x42\xf8\x5b\xce\x73\x45\x45\x9d\x2c\x52\x5b\xf2\x2f\xe8\xfd\xca\xc9\xbf\x59\xfc\xcd\x70\x1b\xce\xb7\x6a\x96\xe3\xae\xc0\xf4\xc9\xd6\x04\x54\x20\x78\x5b\xbb\x14\xa1\xcb\x01\x92\x6e\x9f\xcd\x91\xa3\xa9\x95\x41\xb2\x56\x7b\xee\x30\x67\xf1\x33\xc6\x59\x76\xc1\x83\x0e\xdb\x23\x3e\x88\x28\x78\x38\xf5\x05\x0f\xff\x37\x07\xf1\xfe\x09\x00\x00\xff\xff\x30\xe0\x25\xc3\x9b\x0a\x00\x00"),
},
}
fs["/"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
fs["/repositories.html"].(os.FileInfo),
fs["/tags.html"].(os.FileInfo),
fs["/vulns.html"].(os.FileInfo),
}
return fs
}()
type vfsgen۰FS map[string]interface{}
func (fs vfsgen۰FS) Open(path string) (http.File, error) {
path = pathpkg.Clean("/" + path)
f, ok := fs[path]
if !ok {
return nil, &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist}
}
switch f := f.(type) {
case *vfsgen۰CompressedFileInfo:
gr, err := gzip.NewReader(bytes.NewReader(f.compressedContent))
if err != nil {
// This should never happen because we generate the gzip bytes such that they are always valid.
panic("unexpected error reading own gzip compressed bytes: " + err.Error())
}
return &vfsgen۰CompressedFile{
vfsgen۰CompressedFileInfo: f,
gr: gr,
}, nil
case *vfsgen۰DirInfo:
return &vfsgen۰Dir{
vfsgen۰DirInfo: f,
}, nil
default:
// This should never happen because we generate only the above types.
panic(fmt.Sprintf("unexpected type %T", f))
}
}
// vfsgen۰CompressedFileInfo is a static definition of a gzip compressed file.
type vfsgen۰CompressedFileInfo struct {
name string
modTime time.Time
compressedContent []byte
uncompressedSize int64
}
func (f *vfsgen۰CompressedFileInfo) Readdir(count int) ([]os.FileInfo, error) {
return nil, fmt.Errorf("cannot Readdir from file %s", f.name)
}
func (f *vfsgen۰CompressedFileInfo) Stat() (os.FileInfo, error) { return f, nil }
func (f *vfsgen۰CompressedFileInfo) GzipBytes() []byte {
return f.compressedContent
}
func (f *vfsgen۰CompressedFileInfo) Name() string { return f.name }
func (f *vfsgen۰CompressedFileInfo) Size() int64 { return f.uncompressedSize }
func (f *vfsgen۰CompressedFileInfo) Mode() os.FileMode { return 0444 }
func (f *vfsgen۰CompressedFileInfo) ModTime() time.Time { return f.modTime }
func (f *vfsgen۰CompressedFileInfo) IsDir() bool { return false }
func (f *vfsgen۰CompressedFileInfo) Sys() interface{} { return nil }
// vfsgen۰CompressedFile is an opened compressedFile instance.
type vfsgen۰CompressedFile struct {
*vfsgen۰CompressedFileInfo
gr *gzip.Reader
grPos int64 // Actual gr uncompressed position.
seekPos int64 // Seek uncompressed position.
}
func (f *vfsgen۰CompressedFile) Read(p []byte) (n int, err error) {
if f.grPos > f.seekPos {
// Rewind to beginning.
err = f.gr.Reset(bytes.NewReader(f.compressedContent))
if err != nil {
return 0, err
}
f.grPos = 0
}
if f.grPos < f.seekPos {
// Fast-forward.
_, err = io.CopyN(ioutil.Discard, f.gr, f.seekPos-f.grPos)
if err != nil {
return 0, err
}
f.grPos = f.seekPos
}
n, err = f.gr.Read(p)
f.grPos += int64(n)
f.seekPos = f.grPos
return n, err
}
func (f *vfsgen۰CompressedFile) Seek(offset int64, whence int) (int64, error) {
switch whence {
case io.SeekStart:
f.seekPos = 0 + offset
case io.SeekCurrent:
f.seekPos += offset
case io.SeekEnd:
f.seekPos = f.uncompressedSize + offset
default:
panic(fmt.Errorf("invalid whence value: %v", whence))
}
return f.seekPos, nil
}
func (f *vfsgen۰CompressedFile) Close() error {
return f.gr.Close()
}
// vfsgen۰DirInfo is a static definition of a directory.
type vfsgen۰DirInfo struct {
name string
modTime time.Time
entries []os.FileInfo
}
func (d *vfsgen۰DirInfo) Read([]byte) (int, error) {
return 0, fmt.Errorf("cannot Read from directory %s", d.name)
}
func (d *vfsgen۰DirInfo) Close() error { return nil }
func (d *vfsgen۰DirInfo) Stat() (os.FileInfo, error) { return d, nil }
func (d *vfsgen۰DirInfo) Name() string { return d.name }
func (d *vfsgen۰DirInfo) Size() int64 { return 0 }
func (d *vfsgen۰DirInfo) Mode() os.FileMode { return 0755 | os.ModeDir }
func (d *vfsgen۰DirInfo) ModTime() time.Time { return d.modTime }
func (d *vfsgen۰DirInfo) IsDir() bool { return true }
func (d *vfsgen۰DirInfo) Sys() interface{} { return nil }
// vfsgen۰Dir is an opened dir instance.
type vfsgen۰Dir struct {
*vfsgen۰DirInfo
pos int // Position within entries for Seek and Readdir.
}
func (d *vfsgen۰Dir) Seek(offset int64, whence int) (int64, error) {
if offset == 0 && whence == io.SeekStart {
d.pos = 0
return 0, nil
}
return 0, fmt.Errorf("unsupported Seek in directory %s", d.name)
}
func (d *vfsgen۰Dir) Readdir(count int) ([]os.FileInfo, error) {
if d.pos >= len(d.entries) && count > 0 {
return nil, io.EOF
}
if count <= 0 || count > len(d.entries)-d.pos {
count = len(d.entries) - d.pos
}
e := d.entries[d.pos : d.pos+count]
d.pos += count
return e, nil
}

View file

@ -12,9 +12,11 @@ import (
"time" "time"
"github.com/genuinetools/reg/clair" "github.com/genuinetools/reg/clair"
"github.com/genuinetools/reg/internal/binutils" "github.com/genuinetools/reg/internal/binutils/static"
"github.com/genuinetools/reg/internal/binutils/templates"
"github.com/gorilla/mux" "github.com/gorilla/mux"
wordwrap "github.com/mitchellh/go-wordwrap" wordwrap "github.com/mitchellh/go-wordwrap"
"github.com/shurcooL/httpfs/html/vfstemplate"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -93,18 +95,6 @@ func (cmd *serverCommand) Run(ctx context.Context, args []string) error {
} }
staticDir := filepath.Join(assetDir, "static") staticDir := filepath.Join(assetDir, "static")
templateDir := filepath.Join(assetDir, "templates")
// Make sure all the paths exist.
tmplPaths := []string{
filepath.Join(templateDir, "vulns.html"),
filepath.Join(templateDir, "repositories.html"),
filepath.Join(templateDir, "tags.html"),
}
for _, path := range tmplPaths {
if _, err := os.Stat(path); os.IsNotExist(err) {
return fmt.Errorf("template %s not found", path)
}
}
funcMap := template.FuncMap{ funcMap := template.FuncMap{
"trim": func(s string) string { "trim": func(s string) string {
@ -132,7 +122,8 @@ func (cmd *serverCommand) Run(ctx context.Context, args []string) error {
}, },
} }
rc.tmpl = template.Must(template.New("").Funcs(funcMap).Parse(templateDir + "/*.html")) rc.tmpl = template.New("").Funcs(funcMap)
rc.tmpl = template.Must(vfstemplate.ParseGlob(templates.Assets, rc.tmpl, "*.html"))
// Create the initial index. // Create the initial index.
logrus.Info("creating initial static index") logrus.Info("creating initial static index")
@ -176,7 +167,7 @@ func (cmd *serverCommand) Run(ctx context.Context, args []string) error {
} }
// Serve the static assets. // Serve the static assets.
staticHandler := http.FileServer(binutils.Assets) staticHandler := http.FileServer(static.Assets)
mux.PathPrefix("/static/").Handler(http.StripPrefix("/static/", staticHandler)) mux.PathPrefix("/static/").Handler(http.StripPrefix("/static/", staticHandler))
mux.Handle("/", staticHandler) mux.Handle("/", staticHandler)

View file

@ -1,137 +1,137 @@
package winio package winio
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
) )
type fileFullEaInformation struct { type fileFullEaInformation struct {
NextEntryOffset uint32 NextEntryOffset uint32
Flags uint8 Flags uint8
NameLength uint8 NameLength uint8
ValueLength uint16 ValueLength uint16
} }
var ( var (
fileFullEaInformationSize = binary.Size(&fileFullEaInformation{}) fileFullEaInformationSize = binary.Size(&fileFullEaInformation{})
errInvalidEaBuffer = errors.New("invalid extended attribute buffer") errInvalidEaBuffer = errors.New("invalid extended attribute buffer")
errEaNameTooLarge = errors.New("extended attribute name too large") errEaNameTooLarge = errors.New("extended attribute name too large")
errEaValueTooLarge = errors.New("extended attribute value too large") errEaValueTooLarge = errors.New("extended attribute value too large")
) )
// ExtendedAttribute represents a single Windows EA. // ExtendedAttribute represents a single Windows EA.
type ExtendedAttribute struct { type ExtendedAttribute struct {
Name string Name string
Value []byte Value []byte
Flags uint8 Flags uint8
} }
func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
var info fileFullEaInformation var info fileFullEaInformation
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info) err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info)
if err != nil { if err != nil {
err = errInvalidEaBuffer err = errInvalidEaBuffer
return return
} }
nameOffset := fileFullEaInformationSize nameOffset := fileFullEaInformationSize
nameLen := int(info.NameLength) nameLen := int(info.NameLength)
valueOffset := nameOffset + int(info.NameLength) + 1 valueOffset := nameOffset + int(info.NameLength) + 1
valueLen := int(info.ValueLength) valueLen := int(info.ValueLength)
nextOffset := int(info.NextEntryOffset) nextOffset := int(info.NextEntryOffset)
if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) { if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) {
err = errInvalidEaBuffer err = errInvalidEaBuffer
return return
} }
ea.Name = string(b[nameOffset : nameOffset+nameLen]) ea.Name = string(b[nameOffset : nameOffset+nameLen])
ea.Value = b[valueOffset : valueOffset+valueLen] ea.Value = b[valueOffset : valueOffset+valueLen]
ea.Flags = info.Flags ea.Flags = info.Flags
if info.NextEntryOffset != 0 { if info.NextEntryOffset != 0 {
nb = b[info.NextEntryOffset:] nb = b[info.NextEntryOffset:]
} }
return return
} }
// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION // DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION
// buffer retrieved from BackupRead, ZwQueryEaFile, etc. // buffer retrieved from BackupRead, ZwQueryEaFile, etc.
func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) { func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) {
for len(b) != 0 { for len(b) != 0 {
ea, nb, err := parseEa(b) ea, nb, err := parseEa(b)
if err != nil { if err != nil {
return nil, err return nil, err
} }
eas = append(eas, ea) eas = append(eas, ea)
b = nb b = nb
} }
return return
} }
func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error { func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {
if int(uint8(len(ea.Name))) != len(ea.Name) { if int(uint8(len(ea.Name))) != len(ea.Name) {
return errEaNameTooLarge return errEaNameTooLarge
} }
if int(uint16(len(ea.Value))) != len(ea.Value) { if int(uint16(len(ea.Value))) != len(ea.Value) {
return errEaValueTooLarge return errEaValueTooLarge
} }
entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value)) entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value))
withPadding := (entrySize + 3) &^ 3 withPadding := (entrySize + 3) &^ 3
nextOffset := uint32(0) nextOffset := uint32(0)
if !last { if !last {
nextOffset = withPadding nextOffset = withPadding
} }
info := fileFullEaInformation{ info := fileFullEaInformation{
NextEntryOffset: nextOffset, NextEntryOffset: nextOffset,
Flags: ea.Flags, Flags: ea.Flags,
NameLength: uint8(len(ea.Name)), NameLength: uint8(len(ea.Name)),
ValueLength: uint16(len(ea.Value)), ValueLength: uint16(len(ea.Value)),
} }
err := binary.Write(buf, binary.LittleEndian, &info) err := binary.Write(buf, binary.LittleEndian, &info)
if err != nil { if err != nil {
return err return err
} }
_, err = buf.Write([]byte(ea.Name)) _, err = buf.Write([]byte(ea.Name))
if err != nil { if err != nil {
return err return err
} }
err = buf.WriteByte(0) err = buf.WriteByte(0)
if err != nil { if err != nil {
return err return err
} }
_, err = buf.Write(ea.Value) _, err = buf.Write(ea.Value)
if err != nil { if err != nil {
return err return err
} }
_, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize]) _, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize])
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION // EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION
// buffer for use with BackupWrite, ZwSetEaFile, etc. // buffer for use with BackupWrite, ZwSetEaFile, etc.
func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) { func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) {
var buf bytes.Buffer var buf bytes.Buffer
for i := range eas { for i := range eas {
last := false last := false
if i == len(eas)-1 { if i == len(eas)-1 {
last = true last = true
} }
err := writeEa(&buf, &eas[i], last) err := writeEa(&buf, &eas[i], last)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
return buf.Bytes(), nil return buf.Bytes(), nil
} }

View file

@ -20,7 +20,8 @@ const (
// FileBasicInfo contains file access time and file attributes information. // FileBasicInfo contains file access time and file attributes information.
type FileBasicInfo struct { type FileBasicInfo struct {
CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime
FileAttributes uintptr // includes padding FileAttributes uint32
pad uint32 // padding
} }
// GetFileBasicInfo retrieves times and attributes for a file. // GetFileBasicInfo retrieves times and attributes for a file.

View file

@ -15,7 +15,6 @@ import (
//sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe //sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe
//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW //sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW
//sys createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW //sys createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW
//sys waitNamedPipe(name string, timeout uint32) (err error) = WaitNamedPipeW
//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo //sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW //sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
//sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc //sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc
@ -139,12 +138,14 @@ func (s pipeAddress) String() string {
} }
// DialPipe connects to a named pipe by path, timing out if the connection // DialPipe connects to a named pipe by path, timing out if the connection
// takes longer than the specified duration. If timeout is nil, then the timeout // takes longer than the specified duration. If timeout is nil, then we use
// is the default timeout established by the pipe server. // a default timeout of 5 seconds. (We do not use WaitNamedPipe.)
func DialPipe(path string, timeout *time.Duration) (net.Conn, error) { func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
var absTimeout time.Time var absTimeout time.Time
if timeout != nil { if timeout != nil {
absTimeout = time.Now().Add(*timeout) absTimeout = time.Now().Add(*timeout)
} else {
absTimeout = time.Now().Add(time.Second * 2)
} }
var err error var err error
var h syscall.Handle var h syscall.Handle
@ -153,22 +154,13 @@ func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
if err != cERROR_PIPE_BUSY { if err != cERROR_PIPE_BUSY {
break break
} }
now := time.Now() if time.Now().After(absTimeout) {
var ms uint32 return nil, ErrTimeout
if absTimeout.IsZero() {
ms = cNMPWAIT_USE_DEFAULT_WAIT
} else if now.After(absTimeout) {
ms = cNMPWAIT_NOWAIT
} else {
ms = uint32(absTimeout.Sub(now).Nanoseconds() / 1000 / 1000)
}
err = waitNamedPipe(path, ms)
if err != nil {
if err == cERROR_SEM_TIMEOUT {
return nil, ErrTimeout
}
break
} }
// Wait 10 msec and try again. This is a rather simplistic
// view, as we always try each 10 milliseconds.
time.Sleep(time.Millisecond * 10)
} }
if err != nil { if err != nil {
return nil, &os.PathError{Op: "open", Path: path, Err: err} return nil, &os.PathError{Op: "open", Path: path, Err: err}
@ -349,13 +341,23 @@ func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Immediately open and then close a client handle so that the named pipe is // Create a client handle and connect it. This results in the pipe
// created but not currently accepting connections. // instance always existing, so that clients see ERROR_PIPE_BUSY
// rather than ERROR_FILE_NOT_FOUND. This ties the first instance
// up so that no other instances can be used. This would have been
// cleaner if the Win32 API matched CreateFile with ConnectNamedPipe
// instead of CreateNamedPipe. (Apparently created named pipes are
// considered to be in listening state regardless of whether any
// active calls to ConnectNamedPipe are outstanding.)
h2, err := createFile(path, 0, 0, nil, syscall.OPEN_EXISTING, cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0) h2, err := createFile(path, 0, 0, nil, syscall.OPEN_EXISTING, cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
if err != nil { if err != nil {
syscall.Close(h) syscall.Close(h)
return nil, err return nil, err
} }
// Close the client handle. The server side of the instance will
// still be busy, leading to ERROR_PIPE_BUSY instead of
// ERROR_NOT_FOUND, as long as we don't close the server handle,
// or disconnect the client with DisconnectNamedPipe.
syscall.Close(h2) syscall.Close(h2)
l := &win32PipeListener{ l := &win32PipeListener{
firstHandle: h, firstHandle: h,

View file

@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package pathdriver package pathdriver
import ( import (

View file

@ -175,15 +175,21 @@ func (configFile *ConfigFile) Save() error {
return errors.Errorf("Can't save config with empty filename") return errors.Errorf("Can't save config with empty filename")
} }
if err := os.MkdirAll(filepath.Dir(configFile.Filename), 0700); err != nil { dir := filepath.Dir(configFile.Filename)
if err := os.MkdirAll(dir, 0700); err != nil {
return err return err
} }
f, err := os.OpenFile(configFile.Filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) temp, err := ioutil.TempFile(dir, filepath.Base(configFile.Filename))
if err != nil { if err != nil {
return err return err
} }
defer f.Close() err = configFile.SaveToWriter(temp)
return configFile.SaveToWriter(f) temp.Close()
if err != nil {
os.Remove(temp.Name())
return err
}
return os.Rename(temp.Name(), configFile.Filename)
} }
// ParseProxyConfig computes proxy configuration by retrieving the config for the provided host and // ParseProxyConfig computes proxy configuration by retrieving the config for the provided host and

View file

@ -18,5 +18,5 @@ import (
// environment variables, that's why we just strip leading whitespace and // environment variables, that's why we just strip leading whitespace and
// nothing more. // nothing more.
func ParseEnvFile(filename string) ([]string, error) { func ParseEnvFile(filename string) ([]string, error) {
return parseKeyValueFile(filename, os.Getenv) return parseKeyValueFile(filename, os.LookupEnv)
} }

View file

@ -21,7 +21,7 @@ func (e ErrBadKey) Error() string {
return fmt.Sprintf("poorly formatted environment: %s", e.msg) return fmt.Sprintf("poorly formatted environment: %s", e.msg)
} }
func parseKeyValueFile(filename string, emptyFn func(string) string) ([]string, error) { func parseKeyValueFile(filename string, emptyFn func(string) (string, bool)) ([]string, error) {
fh, err := os.Open(filename) fh, err := os.Open(filename)
if err != nil { if err != nil {
return []string{}, err return []string{}, err
@ -53,17 +53,23 @@ func parseKeyValueFile(filename string, emptyFn func(string) string) ([]string,
if strings.ContainsAny(variable, whiteSpaces) { if strings.ContainsAny(variable, whiteSpaces) {
return []string{}, ErrBadKey{fmt.Sprintf("variable '%s' has white spaces", variable)} return []string{}, ErrBadKey{fmt.Sprintf("variable '%s' has white spaces", variable)}
} }
if len(variable) == 0 {
return []string{}, ErrBadKey{fmt.Sprintf("no variable name on line '%s'", line)}
}
if len(data) > 1 { if len(data) > 1 {
// pass the value through, no trimming // pass the value through, no trimming
lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1])) lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1]))
} else { } else {
var value string var value string
var present bool
if emptyFn != nil { if emptyFn != nil {
value = emptyFn(line) value, present = emptyFn(line)
}
if present {
// if only a pass-through variable is given, clean it up.
lines = append(lines, fmt.Sprintf("%s=%s", strings.TrimSpace(line), value))
} }
// if only a pass-through variable is given, clean it up.
lines = append(lines, fmt.Sprintf("%s=%s", strings.TrimSpace(line), value))
} }
} }
} }

View file

@ -6,11 +6,11 @@ import (
"net" "net"
"path" "path"
"regexp" "regexp"
"strconv"
"strings" "strings"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
units "github.com/docker/go-units" units "github.com/docker/go-units"
"github.com/pkg/errors"
) )
var ( var (
@ -307,6 +307,17 @@ func ValidateSysctl(val string) (string, error) {
return "", fmt.Errorf("sysctl '%s' is not whitelisted", val) return "", fmt.Errorf("sysctl '%s' is not whitelisted", val)
} }
// ValidateProgressOutput errors out if an invalid value is passed to --progress
func ValidateProgressOutput(val string) error {
valid := []string{"auto", "plain", "tty"}
for _, s := range valid {
if s == val {
return nil
}
}
return fmt.Errorf("invalid value %q passed to --progress, valid values are: %s", val, strings.Join(valid, ", "))
}
// FilterOpt is a flag type for validating filters // FilterOpt is a flag type for validating filters
type FilterOpt struct { type FilterOpt struct {
filter filters.Args filter filters.Args
@ -318,7 +329,7 @@ func NewFilterOpt() FilterOpt {
} }
func (o *FilterOpt) String() string { func (o *FilterOpt) String() string {
repr, err := filters.ToParam(o.filter) repr, err := filters.ToJSON(o.filter)
if err != nil { if err != nil {
return "invalid filters" return "invalid filters"
} }
@ -327,9 +338,18 @@ func (o *FilterOpt) String() string {
// Set sets the value of the opt by parsing the command line value // Set sets the value of the opt by parsing the command line value
func (o *FilterOpt) Set(value string) error { func (o *FilterOpt) Set(value string) error {
var err error if value == "" {
o.filter, err = filters.ParseFlag(value, o.filter) return nil
return err }
if !strings.Contains(value, "=") {
return errors.New("bad format of filter (expected name=value)")
}
f := strings.SplitN(value, "=", 2)
name := strings.ToLower(strings.TrimSpace(f[0]))
value = strings.TrimSpace(f[1])
o.filter.Add(name, value)
return nil
} }
// Type returns the option type // Type returns the option type
@ -487,38 +507,3 @@ func (m *MemSwapBytes) UnmarshalJSON(s []byte) error {
b := MemBytes(*m) b := MemBytes(*m)
return b.UnmarshalJSON(s) return b.UnmarshalJSON(s)
} }
// NullableBool is a type for tri-state boolean options
type NullableBool struct {
b *bool
}
// Type returns the type
func (n *NullableBool) Type() string {
return ""
}
// Value returns the value in *bool
func (n *NullableBool) Value() *bool {
return n.b
}
// Set sets the value. If value is empty string or "auto", nil is set.
// Otherwise true or false are set based on flag.Bool behavior.
func (n *NullableBool) Set(value string) error {
if value != "auto" && value != "" {
b, err := strconv.ParseBool(value)
if err != nil {
return err
}
n.b = &b
}
return nil
}
func (n *NullableBool) String() string {
if n.b == nil {
return "auto"
}
return strconv.FormatBool(*n.b)
}

View file

@ -19,10 +19,10 @@ func ReadKVStrings(files []string, override []string) ([]string, error) {
// present in the file with additional pairs specified in the override parameter. // present in the file with additional pairs specified in the override parameter.
// If a key has no value, it will get the value from the environment. // If a key has no value, it will get the value from the environment.
func ReadKVEnvStrings(files []string, override []string) ([]string, error) { func ReadKVEnvStrings(files []string, override []string) ([]string, error) {
return readKVStrings(files, override, os.Getenv) return readKVStrings(files, override, os.LookupEnv)
} }
func readKVStrings(files []string, override []string, emptyFn func(string) string) ([]string, error) { func readKVStrings(files []string, override []string, emptyFn func(string) (string, bool)) ([]string, error) {
variables := []string{} variables := []string{}
for _, ef := range files { for _, ef := range files {
parsedVars, err := parseKeyValueFile(ef, emptyFn) parsedVars, err := parseKeyValueFile(ef, emptyFn)

View file

@ -10,6 +10,7 @@ import (
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go/v1"
) )
var ( var (
@ -66,12 +67,19 @@ type Descriptor struct {
Size int64 `json:"size,omitempty"` Size int64 `json:"size,omitempty"`
// Digest uniquely identifies the content. A byte stream can be verified // Digest uniquely identifies the content. A byte stream can be verified
// against against this digest. // against this digest.
Digest digest.Digest `json:"digest,omitempty"` Digest digest.Digest `json:"digest,omitempty"`
// URLs contains the source URLs of this content. // URLs contains the source URLs of this content.
URLs []string `json:"urls,omitempty"` URLs []string `json:"urls,omitempty"`
// Annotations contains arbitrary metadata relating to the targeted content.
Annotations map[string]string `json:"annotations,omitempty"`
// Platform describes the platform which the image in the manifest runs on.
// This should only be used when referring to a manifest.
Platform *v1.Platform `json:"platform,omitempty"`
// NOTE: Before adding a field here, please ensure that all // NOTE: Before adding a field here, please ensure that all
// other options have been exhausted. Much of the type relationships // other options have been exhausted. Much of the type relationships
// depend on the simplicity of this type. // depend on the simplicity of this type.

View file

@ -20,6 +20,10 @@ var ErrManifestNotModified = errors.New("manifest not modified")
// performed // performed
var ErrUnsupported = errors.New("operation unsupported") var ErrUnsupported = errors.New("operation unsupported")
// ErrSchemaV1Unsupported is returned when a client tries to upload a schema v1
// manifest but the registry is configured to reject it
var ErrSchemaV1Unsupported = errors.New("manifest schema v1 unsupported")
// ErrTagUnknown is returned if the given tag is not known by the tag service // ErrTagUnknown is returned if the given tag is not known by the tag service
type ErrTagUnknown struct { type ErrTagUnknown struct {
Tag string Tag string

View file

@ -8,10 +8,13 @@ import (
"github.com/docker/distribution" "github.com/docker/distribution"
"github.com/docker/distribution/manifest" "github.com/docker/distribution/manifest"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go/v1"
) )
// MediaTypeManifestList specifies the mediaType for manifest lists. const (
const MediaTypeManifestList = "application/vnd.docker.distribution.manifest.list.v2+json" // MediaTypeManifestList specifies the mediaType for manifest lists.
MediaTypeManifestList = "application/vnd.docker.distribution.manifest.list.v2+json"
)
// SchemaVersion provides a pre-initialized version structure for this // SchemaVersion provides a pre-initialized version structure for this
// packages version of the manifest. // packages version of the manifest.
@ -20,6 +23,13 @@ var SchemaVersion = manifest.Versioned{
MediaType: MediaTypeManifestList, MediaType: MediaTypeManifestList,
} }
// OCISchemaVersion provides a pre-initialized version structure for this
// packages OCIschema version of the manifest.
var OCISchemaVersion = manifest.Versioned{
SchemaVersion: 2,
MediaType: v1.MediaTypeImageIndex,
}
func init() { func init() {
manifestListFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) { manifestListFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
m := new(DeserializedManifestList) m := new(DeserializedManifestList)
@ -28,6 +38,13 @@ func init() {
return nil, distribution.Descriptor{}, err return nil, distribution.Descriptor{}, err
} }
if m.MediaType != MediaTypeManifestList {
err = fmt.Errorf("mediaType in manifest list should be '%s' not '%s'",
MediaTypeManifestList, m.MediaType)
return nil, distribution.Descriptor{}, err
}
dgst := digest.FromBytes(b) dgst := digest.FromBytes(b)
return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: MediaTypeManifestList}, err return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: MediaTypeManifestList}, err
} }
@ -35,6 +52,28 @@ func init() {
if err != nil { if err != nil {
panic(fmt.Sprintf("Unable to register manifest: %s", err)) panic(fmt.Sprintf("Unable to register manifest: %s", err))
} }
imageIndexFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
m := new(DeserializedManifestList)
err := m.UnmarshalJSON(b)
if err != nil {
return nil, distribution.Descriptor{}, err
}
if m.MediaType != "" && m.MediaType != v1.MediaTypeImageIndex {
err = fmt.Errorf("if present, mediaType in image index should be '%s' not '%s'",
v1.MediaTypeImageIndex, m.MediaType)
return nil, distribution.Descriptor{}, err
}
dgst := digest.FromBytes(b)
return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: v1.MediaTypeImageIndex}, err
}
err = distribution.RegisterManifestSchema(v1.MediaTypeImageIndex, imageIndexFunc)
if err != nil {
panic(fmt.Sprintf("Unable to register OCI Image Index: %s", err))
}
} }
// PlatformSpec specifies a platform where a particular image manifest is // PlatformSpec specifies a platform where a particular image manifest is
@ -105,8 +144,23 @@ type DeserializedManifestList struct {
// DeserializedManifestList which contains the resulting manifest list // DeserializedManifestList which contains the resulting manifest list
// and its JSON representation. // and its JSON representation.
func FromDescriptors(descriptors []ManifestDescriptor) (*DeserializedManifestList, error) { func FromDescriptors(descriptors []ManifestDescriptor) (*DeserializedManifestList, error) {
var mediaType string
if len(descriptors) > 0 && descriptors[0].Descriptor.MediaType == v1.MediaTypeImageManifest {
mediaType = v1.MediaTypeImageIndex
} else {
mediaType = MediaTypeManifestList
}
return FromDescriptorsWithMediaType(descriptors, mediaType)
}
// FromDescriptorsWithMediaType is for testing purposes, it's useful to be able to specify the media type explicitly
func FromDescriptorsWithMediaType(descriptors []ManifestDescriptor, mediaType string) (*DeserializedManifestList, error) {
m := ManifestList{ m := ManifestList{
Versioned: SchemaVersion, Versioned: manifest.Versioned{
SchemaVersion: 2,
MediaType: mediaType,
},
} }
m.Manifests = make([]ManifestDescriptor, len(descriptors), len(descriptors)) m.Manifests = make([]ManifestDescriptor, len(descriptors), len(descriptors))
@ -151,5 +205,12 @@ func (m *DeserializedManifestList) MarshalJSON() ([]byte, error) {
// Payload returns the raw content of the manifest list. The contents can be // Payload returns the raw content of the manifest list. The contents can be
// used to calculate the content identifier. // used to calculate the content identifier.
func (m DeserializedManifestList) Payload() (string, []byte, error) { func (m DeserializedManifestList) Payload() (string, []byte, error) {
return m.MediaType, m.canonical, nil var mediaType string
if m.MediaType == "" {
mediaType = v1.MediaTypeImageIndex
} else {
mediaType = m.MediaType
}
return mediaType, m.canonical, nil
} }

View file

@ -138,7 +138,7 @@ func (sm *SignedManifest) UnmarshalJSON(b []byte) error {
return nil return nil
} }
// References returnes the descriptors of this manifests references // References returns the descriptors of this manifests references
func (sm SignedManifest) References() []distribution.Descriptor { func (sm SignedManifest) References() []distribution.Descriptor {
dependencies := make([]distribution.Descriptor, len(sm.FSLayers)) dependencies := make([]distribution.Descriptor, len(sm.FSLayers))
for i, fsLayer := range sm.FSLayers { for i, fsLayer := range sm.FSLayers {

View file

@ -71,7 +71,7 @@ type Manifest struct {
Layers []distribution.Descriptor `json:"layers"` Layers []distribution.Descriptor `json:"layers"`
} }
// References returnes the descriptors of this manifests references. // References returns the descriptors of this manifests references.
func (m Manifest) References() []distribution.Descriptor { func (m Manifest) References() []distribution.Descriptor {
references := make([]distribution.Descriptor, 0, 1+len(m.Layers)) references := make([]distribution.Descriptor, 0, 1+len(m.Layers))
references = append(references, m.Config) references = append(references, m.Config)
@ -79,7 +79,7 @@ func (m Manifest) References() []distribution.Descriptor {
return references return references
} }
// Target returns the target of this signed manifest. // Target returns the target of this manifest.
func (m Manifest) Target() distribution.Descriptor { func (m Manifest) Target() distribution.Descriptor {
return m.Config return m.Config
} }
@ -116,6 +116,12 @@ func (m *DeserializedManifest) UnmarshalJSON(b []byte) error {
return err return err
} }
if manifest.MediaType != MediaTypeManifest {
return fmt.Errorf("mediaType in manifest should be '%s' not '%s'",
MediaTypeManifest, manifest.MediaType)
}
m.Manifest = manifest m.Manifest = manifest
return nil return nil

View file

@ -54,6 +54,11 @@ type RepositoryEnumerator interface {
Enumerate(ctx context.Context, ingester func(string) error) error Enumerate(ctx context.Context, ingester func(string) error) error
} }
// RepositoryRemover removes given repository
type RepositoryRemover interface {
Remove(ctx context.Context, name reference.Named) error
}
// ManifestServiceOption is a function argument for Manifest Service methods // ManifestServiceOption is a function argument for Manifest Service methods
type ManifestServiceOption interface { type ManifestServiceOption interface {
Apply(ManifestService) error Apply(ManifestService) error

View file

@ -14,15 +14,6 @@ const (
RouteNameCatalog = "catalog" RouteNameCatalog = "catalog"
) )
var allEndpoints = []string{
RouteNameManifest,
RouteNameCatalog,
RouteNameTags,
RouteNameBlob,
RouteNameBlobUpload,
RouteNameBlobUploadChunk,
}
// Router builds a gorilla router with named routes for the various API // Router builds a gorilla router with named routes for the various API
// methods. This can be used directly by both server implementations and // methods. This can be used directly by both server implementations and
// clients. // clients.

View file

@ -68,7 +68,6 @@ func NewAuthorizer(manager challenge.Manager, handlers ...AuthenticationHandler)
type endpointAuthorizer struct { type endpointAuthorizer struct {
challenges challenge.Manager challenges challenge.Manager
handlers []AuthenticationHandler handlers []AuthenticationHandler
transport http.RoundTripper
} }
func (ea *endpointAuthorizer) ModifyRequest(req *http.Request) error { func (ea *endpointAuthorizer) ModifyRequest(req *http.Request) error {
@ -121,7 +120,6 @@ type clock interface {
} }
type tokenHandler struct { type tokenHandler struct {
header http.Header
creds CredentialStore creds CredentialStore
transport http.RoundTripper transport http.RoundTripper
clock clock clock clock

View file

@ -81,9 +81,8 @@ func NewRegistry(baseURL string, transport http.RoundTripper) (Registry, error)
} }
type registry struct { type registry struct {
client *http.Client client *http.Client
ub *v2.URLBuilder ub *v2.URLBuilder
context context.Context
} }
// Repositories returns a lexigraphically sorted catalog given a base URL. The 'entries' slice will be filled up to the size // Repositories returns a lexigraphically sorted catalog given a base URL. The 'entries' slice will be filled up to the size
@ -152,10 +151,9 @@ func NewRepository(name reference.Named, baseURL string, transport http.RoundTri
} }
type repository struct { type repository struct {
client *http.Client client *http.Client
ub *v2.URLBuilder ub *v2.URLBuilder
context context.Context name reference.Named
name reference.Named
} }
func (r *repository) Named() reference.Named { func (r *repository) Named() reference.Named {

View file

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"os"
"regexp" "regexp"
"strconv" "strconv"
) )
@ -97,7 +96,7 @@ func (hrs *httpReadSeeker) Seek(offset int64, whence int) (int64, error) {
lastReaderOffset := hrs.readerOffset lastReaderOffset := hrs.readerOffset
if whence == os.SEEK_SET && hrs.rc == nil { if whence == io.SeekStart && hrs.rc == nil {
// If no request has been made yet, and we are seeking to an // If no request has been made yet, and we are seeking to an
// absolute position, set the read offset as well to avoid an // absolute position, set the read offset as well to avoid an
// unnecessary request. // unnecessary request.
@ -113,14 +112,14 @@ func (hrs *httpReadSeeker) Seek(offset int64, whence int) (int64, error) {
newOffset := hrs.seekOffset newOffset := hrs.seekOffset
switch whence { switch whence {
case os.SEEK_CUR: case io.SeekCurrent:
newOffset += offset newOffset += offset
case os.SEEK_END: case io.SeekEnd:
if hrs.size < 0 { if hrs.size < 0 {
return 0, errors.New("content length not known") return 0, errors.New("content length not known")
} }
newOffset = hrs.size + offset newOffset = hrs.size + offset
case os.SEEK_SET: case io.SeekStart:
newOffset = offset newOffset = offset
} }

View file

@ -96,12 +96,12 @@ func Load(configDir string) (*configfile.ConfigFile, error) {
} }
file, err := os.Open(confFile) file, err := os.Open(confFile)
if err != nil { if err != nil {
return configFile, errors.Wrap(err, confFile) return configFile, errors.Wrap(err, filename)
} }
defer file.Close() defer file.Close()
err = configFile.LegacyLoadFromReader(file) err = configFile.LegacyLoadFromReader(file)
if err != nil { if err != nil {
return configFile, errors.Wrap(err, confFile) return configFile, errors.Wrap(err, filename)
} }
return configFile, nil return configFile, nil
} }

View file

@ -201,6 +201,7 @@ Ben Severson <BenSeverson@users.noreply.github.com>
Ben Toews <mastahyeti@gmail.com> Ben Toews <mastahyeti@gmail.com>
Ben Wiklund <ben@daisyowl.com> Ben Wiklund <ben@daisyowl.com>
Benjamin Atkin <ben@benatkin.com> Benjamin Atkin <ben@benatkin.com>
Benjamin Baker <Benjamin.baker@utexas.edu>
Benjamin Boudreau <boudreau.benjamin@gmail.com> Benjamin Boudreau <boudreau.benjamin@gmail.com>
Benjamin Yolken <yolken@stripe.com> Benjamin Yolken <yolken@stripe.com>
Benoit Chesneau <bchesneau@gmail.com> Benoit Chesneau <bchesneau@gmail.com>
@ -246,6 +247,7 @@ Brian Torres-Gil <brian@dralth.com>
Brian Trump <btrump@yelp.com> Brian Trump <btrump@yelp.com>
Brice Jaglin <bjaglin@teads.tv> Brice Jaglin <bjaglin@teads.tv>
Briehan Lombaard <briehan.lombaard@gmail.com> Briehan Lombaard <briehan.lombaard@gmail.com>
Brielle Broder <bbroder@google.com>
Bruno Bigras <bigras.bruno@gmail.com> Bruno Bigras <bigras.bruno@gmail.com>
Bruno Binet <bruno.binet@gmail.com> Bruno Binet <bruno.binet@gmail.com>
Bruno Gazzera <bgazzera@paginar.com> Bruno Gazzera <bgazzera@paginar.com>
@ -325,9 +327,11 @@ Chris Swan <chris.swan@iee.org>
Chris Telfer <ctelfer@docker.com> Chris Telfer <ctelfer@docker.com>
Chris Wahl <github@wahlnetwork.com> Chris Wahl <github@wahlnetwork.com>
Chris Weyl <cweyl@alumni.drew.edu> Chris Weyl <cweyl@alumni.drew.edu>
Chris White <me@cwprogram.com>
Christian Berendt <berendt@b1-systems.de> Christian Berendt <berendt@b1-systems.de>
Christian Brauner <christian.brauner@ubuntu.com> Christian Brauner <christian.brauner@ubuntu.com>
Christian Böhme <developement@boehme3d.de> Christian Böhme <developement@boehme3d.de>
Christian Muehlhaeuser <muesli@gmail.com>
Christian Persson <saser@live.se> Christian Persson <saser@live.se>
Christian Rotzoll <ch.rotzoll@gmail.com> Christian Rotzoll <ch.rotzoll@gmail.com>
Christian Simon <simon@swine.de> Christian Simon <simon@swine.de>
@ -444,6 +448,7 @@ David Röthlisberger <david@rothlis.net>
David Sheets <dsheets@docker.com> David Sheets <dsheets@docker.com>
David Sissitka <me@dsissitka.com> David Sissitka <me@dsissitka.com>
David Trott <github@davidtrott.com> David Trott <github@davidtrott.com>
David Wang <00107082@163.com>
David Williamson <david.williamson@docker.com> David Williamson <david.williamson@docker.com>
David Xia <dxia@spotify.com> David Xia <dxia@spotify.com>
David Young <yangboh@cn.ibm.com> David Young <yangboh@cn.ibm.com>
@ -451,6 +456,7 @@ Davide Ceretti <davide.ceretti@hogarthww.com>
Dawn Chen <dawnchen@google.com> Dawn Chen <dawnchen@google.com>
dbdd <wangtong2712@gmail.com> dbdd <wangtong2712@gmail.com>
dcylabs <dcylabs@gmail.com> dcylabs <dcylabs@gmail.com>
Debayan De <debayande@users.noreply.github.com>
Deborah Gertrude Digges <deborah.gertrude.digges@gmail.com> Deborah Gertrude Digges <deborah.gertrude.digges@gmail.com>
deed02392 <georgehafiz@gmail.com> deed02392 <georgehafiz@gmail.com>
Deng Guangxing <dengguangxing@huawei.com> Deng Guangxing <dengguangxing@huawei.com>
@ -503,6 +509,7 @@ Don Kjer <don.kjer@gmail.com>
Don Spaulding <donspauldingii@gmail.com> Don Spaulding <donspauldingii@gmail.com>
Donald Huang <don.hcd@gmail.com> Donald Huang <don.hcd@gmail.com>
Dong Chen <dongluo.chen@docker.com> Dong Chen <dongluo.chen@docker.com>
Donghwa Kim <shanytt@gmail.com>
Donovan Jones <git@gamma.net.nz> Donovan Jones <git@gamma.net.nz>
Doron Podoleanu <doronp@il.ibm.com> Doron Podoleanu <doronp@il.ibm.com>
Doug Davis <dug@us.ibm.com> Doug Davis <dug@us.ibm.com>
@ -580,6 +587,7 @@ Eystein Måløy Stenberg <eystein.maloy.stenberg@cfengine.com>
ezbercih <cem.ezberci@gmail.com> ezbercih <cem.ezberci@gmail.com>
Ezra Silvera <ezra@il.ibm.com> Ezra Silvera <ezra@il.ibm.com>
Fabian Lauer <kontakt@softwareschmiede-saar.de> Fabian Lauer <kontakt@softwareschmiede-saar.de>
Fabian Raetz <fabian.raetz@gmail.com>
Fabiano Rosas <farosas@br.ibm.com> Fabiano Rosas <farosas@br.ibm.com>
Fabio Falci <fabiofalci@gmail.com> Fabio Falci <fabiofalci@gmail.com>
Fabio Kung <fabio.kung@gmail.com> Fabio Kung <fabio.kung@gmail.com>
@ -591,6 +599,7 @@ Faiz Khan <faizkhan00@gmail.com>
falmp <chico.lopes@gmail.com> falmp <chico.lopes@gmail.com>
Fangming Fang <fangming.fang@arm.com> Fangming Fang <fangming.fang@arm.com>
Fangyuan Gao <21551127@zju.edu.cn> Fangyuan Gao <21551127@zju.edu.cn>
fanjiyun <fan.jiyun@zte.com.cn>
Fareed Dudhia <fareeddudhia@googlemail.com> Fareed Dudhia <fareeddudhia@googlemail.com>
Fathi Boudra <fathi.boudra@linaro.org> Fathi Boudra <fathi.boudra@linaro.org>
Federico Gimenez <fgimenez@coit.es> Federico Gimenez <fgimenez@coit.es>
@ -621,6 +630,7 @@ Florin Patan <florinpatan@gmail.com>
fonglh <fonglh@gmail.com> fonglh <fonglh@gmail.com>
Foysal Iqbal <foysal.iqbal.fb@gmail.com> Foysal Iqbal <foysal.iqbal.fb@gmail.com>
Francesc Campoy <campoy@google.com> Francesc Campoy <campoy@google.com>
Francesco Mari <mari.francesco@gmail.com>
Francis Chuang <francis.chuang@boostport.com> Francis Chuang <francis.chuang@boostport.com>
Francisco Carriedo <fcarriedo@gmail.com> Francisco Carriedo <fcarriedo@gmail.com>
Francisco Souza <f@souza.cc> Francisco Souza <f@souza.cc>
@ -653,6 +663,7 @@ Gaël PORTAY <gael.portay@savoirfairelinux.com>
Genki Takiuchi <genki@s21g.com> Genki Takiuchi <genki@s21g.com>
GennadySpb <lipenkov@gmail.com> GennadySpb <lipenkov@gmail.com>
Geoffrey Bachelet <grosfrais@gmail.com> Geoffrey Bachelet <grosfrais@gmail.com>
Geon Kim <geon0250@gmail.com>
George Kontridze <george@bugsnag.com> George Kontridze <george@bugsnag.com>
George MacRorie <gmacr31@gmail.com> George MacRorie <gmacr31@gmail.com>
George Xie <georgexsh@gmail.com> George Xie <georgexsh@gmail.com>
@ -676,6 +687,7 @@ Gopikannan Venugopalsamy <gopikannan.venugopalsamy@gmail.com>
Gosuke Miyashita <gosukenator@gmail.com> Gosuke Miyashita <gosukenator@gmail.com>
Gou Rao <gou@portworx.com> Gou Rao <gou@portworx.com>
Govinda Fichtner <govinda.fichtner@googlemail.com> Govinda Fichtner <govinda.fichtner@googlemail.com>
Grant Millar <grant@cylo.io>
Grant Reaber <grant.reaber@gmail.com> Grant Reaber <grant.reaber@gmail.com>
Graydon Hoare <graydon@pobox.com> Graydon Hoare <graydon@pobox.com>
Greg Fausak <greg@tacodata.com> Greg Fausak <greg@tacodata.com>
@ -694,6 +706,7 @@ Guruprasad <lgp171188@gmail.com>
Gustav Sinder <gustav.sinder@gmail.com> Gustav Sinder <gustav.sinder@gmail.com>
gwx296173 <gaojing3@huawei.com> gwx296173 <gaojing3@huawei.com>
Günter Zöchbauer <guenter@gzoechbauer.com> Günter Zöchbauer <guenter@gzoechbauer.com>
haikuoliu <haikuo@amazon.com>
Hakan Özler <hakan.ozler@kodcu.com> Hakan Özler <hakan.ozler@kodcu.com>
Hans Kristian Flaatten <hans@starefossen.com> Hans Kristian Flaatten <hans@starefossen.com>
Hans Rødtang <hansrodtang@gmail.com> Hans Rødtang <hansrodtang@gmail.com>
@ -735,6 +748,7 @@ Ian Bishop <ianbishop@pace7.com>
Ian Bull <irbull@gmail.com> Ian Bull <irbull@gmail.com>
Ian Calvert <ianjcalvert@gmail.com> Ian Calvert <ianjcalvert@gmail.com>
Ian Campbell <ian.campbell@docker.com> Ian Campbell <ian.campbell@docker.com>
Ian Chen <ianre657@gmail.com>
Ian Lee <IanLee1521@gmail.com> Ian Lee <IanLee1521@gmail.com>
Ian Main <imain@redhat.com> Ian Main <imain@redhat.com>
Ian Philpot <ian.philpot@microsoft.com> Ian Philpot <ian.philpot@microsoft.com>
@ -755,6 +769,7 @@ Ingo Gottwald <in.gottwald@gmail.com>
Isaac Dupree <antispam@idupree.com> Isaac Dupree <antispam@idupree.com>
Isabel Jimenez <contact.isabeljimenez@gmail.com> Isabel Jimenez <contact.isabeljimenez@gmail.com>
Isao Jonas <isao.jonas@gmail.com> Isao Jonas <isao.jonas@gmail.com>
Iskander Sharipov <quasilyte@gmail.com>
Ivan Babrou <ibobrik@gmail.com> Ivan Babrou <ibobrik@gmail.com>
Ivan Fraixedes <ifcdev@gmail.com> Ivan Fraixedes <ifcdev@gmail.com>
Ivan Grcic <igrcic@gmail.com> Ivan Grcic <igrcic@gmail.com>
@ -847,7 +862,7 @@ Jeroen Franse <jeroenfranse@gmail.com>
Jeroen Jacobs <github@jeroenj.be> Jeroen Jacobs <github@jeroenj.be>
Jesse Dearing <jesse.dearing@gmail.com> Jesse Dearing <jesse.dearing@gmail.com>
Jesse Dubay <jesse@thefortytwo.net> Jesse Dubay <jesse@thefortytwo.net>
Jessica Frazelle <jessfraz@google.com> Jessica Frazelle <acidburn@microsoft.com>
Jezeniel Zapanta <jpzapanta22@gmail.com> Jezeniel Zapanta <jpzapanta22@gmail.com>
Jhon Honce <jhonce@redhat.com> Jhon Honce <jhonce@redhat.com>
Ji.Zhilong <zhilongji@gmail.com> Ji.Zhilong <zhilongji@gmail.com>
@ -983,6 +998,7 @@ Karl Grzeszczak <karlgrz@gmail.com>
Karol Duleba <mr.fuxi@gmail.com> Karol Duleba <mr.fuxi@gmail.com>
Karthik Karanth <karanth.karthik@gmail.com> Karthik Karanth <karanth.karthik@gmail.com>
Karthik Nayak <Karthik.188@gmail.com> Karthik Nayak <Karthik.188@gmail.com>
Kasper Fabæch Brandt <poizan@poizan.dk>
Kate Heddleston <kate.heddleston@gmail.com> Kate Heddleston <kate.heddleston@gmail.com>
Katie McLaughlin <katie@glasnt.com> Katie McLaughlin <katie@glasnt.com>
Kato Kazuyoshi <kato.kazuyoshi@gmail.com> Kato Kazuyoshi <kato.kazuyoshi@gmail.com>
@ -990,6 +1006,7 @@ Katrina Owen <katrina.owen@gmail.com>
Kawsar Saiyeed <kawsar.saiyeed@projiris.com> Kawsar Saiyeed <kawsar.saiyeed@projiris.com>
Kay Yan <kay.yan@daocloud.io> Kay Yan <kay.yan@daocloud.io>
kayrus <kay.diam@gmail.com> kayrus <kay.diam@gmail.com>
Kazuhiro Sera <seratch@gmail.com>
Ke Li <kel@splunk.com> Ke Li <kel@splunk.com>
Ke Xu <leonhartx.k@gmail.com> Ke Xu <leonhartx.k@gmail.com>
Kei Ohmura <ohmura.kei@gmail.com> Kei Ohmura <ohmura.kei@gmail.com>
@ -998,6 +1015,7 @@ Keli Hu <dev@keli.hu>
Ken Cochrane <kencochrane@gmail.com> Ken Cochrane <kencochrane@gmail.com>
Ken Herner <kherner@progress.com> Ken Herner <kherner@progress.com>
Ken ICHIKAWA <ichikawa.ken@jp.fujitsu.com> Ken ICHIKAWA <ichikawa.ken@jp.fujitsu.com>
Ken Reese <krrgithub@gmail.com>
Kenfe-Mickaël Laventure <mickael.laventure@gmail.com> Kenfe-Mickaël Laventure <mickael.laventure@gmail.com>
Kenjiro Nakayama <nakayamakenjiro@gmail.com> Kenjiro Nakayama <nakayamakenjiro@gmail.com>
Kent Johnson <kentoj@gmail.com> Kent Johnson <kentoj@gmail.com>
@ -1035,9 +1053,9 @@ Krasimir Georgiev <support@vip-consult.co.uk>
Kris-Mikael Krister <krismikael@protonmail.com> Kris-Mikael Krister <krismikael@protonmail.com>
Kristian Haugene <kristian.haugene@capgemini.com> Kristian Haugene <kristian.haugene@capgemini.com>
Kristina Zabunova <triara.xiii@gmail.com> Kristina Zabunova <triara.xiii@gmail.com>
krrg <krrgithub@gmail.com>
Kun Zhang <zkazure@gmail.com> Kun Zhang <zkazure@gmail.com>
Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp> Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp>
Kunal Tyagi <tyagi.kunal@live.com>
Kyle Conroy <kyle.j.conroy@gmail.com> Kyle Conroy <kyle.j.conroy@gmail.com>
Kyle Linden <linden.kyle@gmail.com> Kyle Linden <linden.kyle@gmail.com>
kyu <leehk1227@gmail.com> kyu <leehk1227@gmail.com>
@ -1060,6 +1078,7 @@ Leandro Siqueira <leandro.siqueira@gmail.com>
Lee Chao <932819864@qq.com> Lee Chao <932819864@qq.com>
Lee, Meng-Han <sunrisedm4@gmail.com> Lee, Meng-Han <sunrisedm4@gmail.com>
leeplay <hyeongkyu.lee@navercorp.com> leeplay <hyeongkyu.lee@navercorp.com>
Lei Gong <lgong@alauda.io>
Lei Jitang <leijitang@huawei.com> Lei Jitang <leijitang@huawei.com>
Len Weincier <len@cloudafrica.net> Len Weincier <len@cloudafrica.net>
Lennie <github@consolejunkie.net> Lennie <github@consolejunkie.net>
@ -1095,6 +1114,7 @@ Lokesh Mandvekar <lsm5@fedoraproject.org>
longliqiang88 <394564827@qq.com> longliqiang88 <394564827@qq.com>
Lorenz Leutgeb <lorenz.leutgeb@gmail.com> Lorenz Leutgeb <lorenz.leutgeb@gmail.com>
Lorenzo Fontana <lo@linux.com> Lorenzo Fontana <lo@linux.com>
Lotus Fenn <fenn.lotus@gmail.com>
Louis Opter <kalessin@kalessin.fr> Louis Opter <kalessin@kalessin.fr>
Luca Favatella <luca.favatella@erlang-solutions.com> Luca Favatella <luca.favatella@erlang-solutions.com>
Luca Marturana <lucamarturana@gmail.com> Luca Marturana <lucamarturana@gmail.com>
@ -1167,6 +1187,7 @@ Martijn van Oosterhout <kleptog@svana.org>
Martin Honermeyer <maze@strahlungsfrei.de> Martin Honermeyer <maze@strahlungsfrei.de>
Martin Kelly <martin@surround.io> Martin Kelly <martin@surround.io>
Martin Mosegaard Amdisen <martin.amdisen@praqma.com> Martin Mosegaard Amdisen <martin.amdisen@praqma.com>
Martin Muzatko <martin@happy-css.com>
Martin Redmond <redmond.martin@gmail.com> Martin Redmond <redmond.martin@gmail.com>
Mary Anthony <mary.anthony@docker.com> Mary Anthony <mary.anthony@docker.com>
Masahito Zembutsu <zembutsu@users.noreply.github.com> Masahito Zembutsu <zembutsu@users.noreply.github.com>
@ -1248,8 +1269,9 @@ Michal Wieczorek <wieczorek-michal@wp.pl>
Michaël Pailloncy <mpapo.dev@gmail.com> Michaël Pailloncy <mpapo.dev@gmail.com>
Michał Czeraszkiewicz <czerasz@gmail.com> Michał Czeraszkiewicz <czerasz@gmail.com>
Michał Gryko <github@odkurzacz.org> Michał Gryko <github@odkurzacz.org>
Michiel@unhosted <michiel@unhosted.org> Michiel de Jong <michiel@unhosted.org>
Mickaël FORTUNATO <morsi.morsicus@gmail.com> Mickaël Fortunato <morsi.morsicus@gmail.com>
Mickaël Remars <mickael@remars.com>
Miguel Angel Fernández <elmendalerenda@gmail.com> Miguel Angel Fernández <elmendalerenda@gmail.com>
Miguel Morales <mimoralea@gmail.com> Miguel Morales <mimoralea@gmail.com>
Mihai Borobocea <MihaiBorob@gmail.com> Mihai Borobocea <MihaiBorob@gmail.com>
@ -1337,6 +1359,7 @@ Nicolas Dudebout <nicolas.dudebout@gatech.edu>
Nicolas Goy <kuon@goyman.com> Nicolas Goy <kuon@goyman.com>
Nicolas Kaiser <nikai@nikai.net> Nicolas Kaiser <nikai@nikai.net>
Nicolas Sterchele <sterchele.nicolas@gmail.com> Nicolas Sterchele <sterchele.nicolas@gmail.com>
Nicolas V Castet <nvcastet@us.ibm.com>
Nicolás Hock Isaza <nhocki@gmail.com> Nicolás Hock Isaza <nhocki@gmail.com>
Nigel Poulton <nigelpoulton@hotmail.com> Nigel Poulton <nigelpoulton@hotmail.com>
Nik Nyby <nikolas@gnu.org> Nik Nyby <nikolas@gnu.org>
@ -1452,6 +1475,7 @@ Prasanna Gautam <prasannagautam@gmail.com>
Pratik Karki <prertik@outlook.com> Pratik Karki <prertik@outlook.com>
Prayag Verma <prayag.verma@gmail.com> Prayag Verma <prayag.verma@gmail.com>
Priya Wadhwa <priyawadhwa@google.com> Priya Wadhwa <priyawadhwa@google.com>
Projjol Banerji <probaner23@gmail.com>
Przemek Hejman <przemyslaw.hejman@gmail.com> Przemek Hejman <przemyslaw.hejman@gmail.com>
Pure White <daniel48@126.com> Pure White <daniel48@126.com>
pysqz <randomq@126.com> pysqz <randomq@126.com>
@ -1546,6 +1570,7 @@ Rozhnov Alexandr <nox73@ya.ru>
Rudolph Gottesheim <r.gottesheim@loot.at> Rudolph Gottesheim <r.gottesheim@loot.at>
Rui Lopes <rgl@ruilopes.com> Rui Lopes <rgl@ruilopes.com>
Runshen Zhu <runshen.zhu@gmail.com> Runshen Zhu <runshen.zhu@gmail.com>
Russ Magee <rmagee@gmail.com>
Ryan Abrams <rdabrams@gmail.com> Ryan Abrams <rdabrams@gmail.com>
Ryan Anderson <anderson.ryanc@gmail.com> Ryan Anderson <anderson.ryanc@gmail.com>
Ryan Aslett <github@mixologic.com> Ryan Aslett <github@mixologic.com>
@ -1572,6 +1597,7 @@ Sachin Joshi <sachin_jayant_joshi@hotmail.com>
Sagar Hani <sagarhani33@gmail.com> Sagar Hani <sagarhani33@gmail.com>
Sainath Grandhi <sainath.grandhi@intel.com> Sainath Grandhi <sainath.grandhi@intel.com>
Sakeven Jiang <jc5930@sina.cn> Sakeven Jiang <jc5930@sina.cn>
Salahuddin Khan <salah@docker.com>
Sally O'Malley <somalley@redhat.com> Sally O'Malley <somalley@redhat.com>
Sam Abed <sam.abed@gmail.com> Sam Abed <sam.abed@gmail.com>
Sam Alba <sam.alba@gmail.com> Sam Alba <sam.alba@gmail.com>
@ -1620,6 +1646,7 @@ Sergey Alekseev <sergey.alekseev.minsk@gmail.com>
Sergey Evstifeev <sergey.evstifeev@gmail.com> Sergey Evstifeev <sergey.evstifeev@gmail.com>
Sergii Kabashniuk <skabashnyuk@codenvy.com> Sergii Kabashniuk <skabashnyuk@codenvy.com>
Serhat Gülçiçek <serhat25@gmail.com> Serhat Gülçiçek <serhat25@gmail.com>
SeungUkLee <lsy931106@gmail.com>
Sevki Hasirci <s@sevki.org> Sevki Hasirci <s@sevki.org>
Shane Canon <scanon@lbl.gov> Shane Canon <scanon@lbl.gov>
Shane da Silva <shane@dasilva.io> Shane da Silva <shane@dasilva.io>
@ -1714,10 +1741,11 @@ tang0th <tang0th@gmx.com>
Tangi Colin <tangicolin@gmail.com> Tangi Colin <tangicolin@gmail.com>
Tatsuki Sugiura <sugi@nemui.org> Tatsuki Sugiura <sugi@nemui.org>
Tatsushi Inagaki <e29253@jp.ibm.com> Tatsushi Inagaki <e29253@jp.ibm.com>
Taylan Isikdemir <taylani@google.com>
Taylor Jones <monitorjbl@gmail.com> Taylor Jones <monitorjbl@gmail.com>
tbonza <tylers.pile@gmail.com>
Ted M. Young <tedyoung@gmail.com> Ted M. Young <tedyoung@gmail.com>
Tehmasp Chaudhri <tehmasp@gmail.com> Tehmasp Chaudhri <tehmasp@gmail.com>
Tejaswini Duggaraju <naduggar@microsoft.com>
Tejesh Mehta <tejesh.mehta@gmail.com> Tejesh Mehta <tejesh.mehta@gmail.com>
terryding77 <550147740@qq.com> terryding77 <550147740@qq.com>
tgic <farmer1992@gmail.com> tgic <farmer1992@gmail.com>
@ -1811,6 +1839,7 @@ Tristan Carel <tristan@cogniteev.com>
Troy Denton <trdenton@gmail.com> Troy Denton <trdenton@gmail.com>
Tycho Andersen <tycho@docker.com> Tycho Andersen <tycho@docker.com>
Tyler Brock <tyler.brock@gmail.com> Tyler Brock <tyler.brock@gmail.com>
Tyler Brown <tylers.pile@gmail.com>
Tzu-Jung Lee <roylee17@gmail.com> Tzu-Jung Lee <roylee17@gmail.com>
uhayate <uhayate.gong@daocloud.io> uhayate <uhayate.gong@daocloud.io>
Ulysse Carion <ulyssecarion@gmail.com> Ulysse Carion <ulyssecarion@gmail.com>
@ -1906,11 +1935,13 @@ XiaoBing Jiang <s7v7nislands@gmail.com>
Xiaoxu Chen <chenxiaoxu14@otcaix.iscas.ac.cn> Xiaoxu Chen <chenxiaoxu14@otcaix.iscas.ac.cn>
Xiaoyu Zhang <zhang.xiaoyu33@zte.com.cn> Xiaoyu Zhang <zhang.xiaoyu33@zte.com.cn>
xiekeyang <xiekeyang@huawei.com> xiekeyang <xiekeyang@huawei.com>
Ximo Guanter Gonzálbez <joaquin.guantergonzalbez@telefonica.com>
Xinbo Weng <xihuanbo_0521@zju.edu.cn> Xinbo Weng <xihuanbo_0521@zju.edu.cn>
Xinzi Zhou <imdreamrunner@gmail.com> Xinzi Zhou <imdreamrunner@gmail.com>
Xiuming Chen <cc@cxm.cc> Xiuming Chen <cc@cxm.cc>
Xuecong Liao <satorulogic@gmail.com> Xuecong Liao <satorulogic@gmail.com>
xuzhaokui <cynicholas@gmail.com> xuzhaokui <cynicholas@gmail.com>
Yadnyawalkya Tale <ytale@redhat.com>
Yahya <ya7yaz@gmail.com> Yahya <ya7yaz@gmail.com>
YAMADA Tsuyoshi <tyamada@minimum2scp.org> YAMADA Tsuyoshi <tyamada@minimum2scp.org>
Yamasaki Masahide <masahide.y@gmail.com> Yamasaki Masahide <masahide.y@gmail.com>
@ -1941,6 +1972,7 @@ Yu-Ju Hong <yjhong@google.com>
Yuan Sun <sunyuan3@huawei.com> Yuan Sun <sunyuan3@huawei.com>
Yuanhong Peng <pengyuanhong@huawei.com> Yuanhong Peng <pengyuanhong@huawei.com>
Yuhao Fang <fangyuhao@gmail.com> Yuhao Fang <fangyuhao@gmail.com>
Yuichiro Kaneko <spiketeika@gmail.com>
Yunxiang Huang <hyxqshk@vip.qq.com> Yunxiang Huang <hyxqshk@vip.qq.com>
Yurii Rashkovskii <yrashk@gmail.com> Yurii Rashkovskii <yrashk@gmail.com>
Yves Junqueira <yves.junqueira@gmail.com> Yves Junqueira <yves.junqueira@gmail.com>

View file

@ -176,7 +176,7 @@
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
Copyright 2013-2017 Docker, Inc. Copyright 2013-2018 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -176,7 +176,7 @@
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
Copyright 2014-2017 Docker, Inc. Copyright 2014-2018 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
Copyright (c) 2014-2017 The Docker & Go Authors. All rights reserved. Copyright (c) 2014-2018 The Docker & Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are

View file

@ -201,6 +201,7 @@ Ben Severson <BenSeverson@users.noreply.github.com>
Ben Toews <mastahyeti@gmail.com> Ben Toews <mastahyeti@gmail.com>
Ben Wiklund <ben@daisyowl.com> Ben Wiklund <ben@daisyowl.com>
Benjamin Atkin <ben@benatkin.com> Benjamin Atkin <ben@benatkin.com>
Benjamin Baker <Benjamin.baker@utexas.edu>
Benjamin Boudreau <boudreau.benjamin@gmail.com> Benjamin Boudreau <boudreau.benjamin@gmail.com>
Benjamin Yolken <yolken@stripe.com> Benjamin Yolken <yolken@stripe.com>
Benoit Chesneau <bchesneau@gmail.com> Benoit Chesneau <bchesneau@gmail.com>
@ -246,6 +247,7 @@ Brian Torres-Gil <brian@dralth.com>
Brian Trump <btrump@yelp.com> Brian Trump <btrump@yelp.com>
Brice Jaglin <bjaglin@teads.tv> Brice Jaglin <bjaglin@teads.tv>
Briehan Lombaard <briehan.lombaard@gmail.com> Briehan Lombaard <briehan.lombaard@gmail.com>
Brielle Broder <bbroder@google.com>
Bruno Bigras <bigras.bruno@gmail.com> Bruno Bigras <bigras.bruno@gmail.com>
Bruno Binet <bruno.binet@gmail.com> Bruno Binet <bruno.binet@gmail.com>
Bruno Gazzera <bgazzera@paginar.com> Bruno Gazzera <bgazzera@paginar.com>
@ -325,9 +327,11 @@ Chris Swan <chris.swan@iee.org>
Chris Telfer <ctelfer@docker.com> Chris Telfer <ctelfer@docker.com>
Chris Wahl <github@wahlnetwork.com> Chris Wahl <github@wahlnetwork.com>
Chris Weyl <cweyl@alumni.drew.edu> Chris Weyl <cweyl@alumni.drew.edu>
Chris White <me@cwprogram.com>
Christian Berendt <berendt@b1-systems.de> Christian Berendt <berendt@b1-systems.de>
Christian Brauner <christian.brauner@ubuntu.com> Christian Brauner <christian.brauner@ubuntu.com>
Christian Böhme <developement@boehme3d.de> Christian Böhme <developement@boehme3d.de>
Christian Muehlhaeuser <muesli@gmail.com>
Christian Persson <saser@live.se> Christian Persson <saser@live.se>
Christian Rotzoll <ch.rotzoll@gmail.com> Christian Rotzoll <ch.rotzoll@gmail.com>
Christian Simon <simon@swine.de> Christian Simon <simon@swine.de>
@ -444,6 +448,7 @@ David Röthlisberger <david@rothlis.net>
David Sheets <dsheets@docker.com> David Sheets <dsheets@docker.com>
David Sissitka <me@dsissitka.com> David Sissitka <me@dsissitka.com>
David Trott <github@davidtrott.com> David Trott <github@davidtrott.com>
David Wang <00107082@163.com>
David Williamson <david.williamson@docker.com> David Williamson <david.williamson@docker.com>
David Xia <dxia@spotify.com> David Xia <dxia@spotify.com>
David Young <yangboh@cn.ibm.com> David Young <yangboh@cn.ibm.com>
@ -451,6 +456,7 @@ Davide Ceretti <davide.ceretti@hogarthww.com>
Dawn Chen <dawnchen@google.com> Dawn Chen <dawnchen@google.com>
dbdd <wangtong2712@gmail.com> dbdd <wangtong2712@gmail.com>
dcylabs <dcylabs@gmail.com> dcylabs <dcylabs@gmail.com>
Debayan De <debayande@users.noreply.github.com>
Deborah Gertrude Digges <deborah.gertrude.digges@gmail.com> Deborah Gertrude Digges <deborah.gertrude.digges@gmail.com>
deed02392 <georgehafiz@gmail.com> deed02392 <georgehafiz@gmail.com>
Deng Guangxing <dengguangxing@huawei.com> Deng Guangxing <dengguangxing@huawei.com>
@ -503,6 +509,7 @@ Don Kjer <don.kjer@gmail.com>
Don Spaulding <donspauldingii@gmail.com> Don Spaulding <donspauldingii@gmail.com>
Donald Huang <don.hcd@gmail.com> Donald Huang <don.hcd@gmail.com>
Dong Chen <dongluo.chen@docker.com> Dong Chen <dongluo.chen@docker.com>
Donghwa Kim <shanytt@gmail.com>
Donovan Jones <git@gamma.net.nz> Donovan Jones <git@gamma.net.nz>
Doron Podoleanu <doronp@il.ibm.com> Doron Podoleanu <doronp@il.ibm.com>
Doug Davis <dug@us.ibm.com> Doug Davis <dug@us.ibm.com>
@ -580,6 +587,7 @@ Eystein Måløy Stenberg <eystein.maloy.stenberg@cfengine.com>
ezbercih <cem.ezberci@gmail.com> ezbercih <cem.ezberci@gmail.com>
Ezra Silvera <ezra@il.ibm.com> Ezra Silvera <ezra@il.ibm.com>
Fabian Lauer <kontakt@softwareschmiede-saar.de> Fabian Lauer <kontakt@softwareschmiede-saar.de>
Fabian Raetz <fabian.raetz@gmail.com>
Fabiano Rosas <farosas@br.ibm.com> Fabiano Rosas <farosas@br.ibm.com>
Fabio Falci <fabiofalci@gmail.com> Fabio Falci <fabiofalci@gmail.com>
Fabio Kung <fabio.kung@gmail.com> Fabio Kung <fabio.kung@gmail.com>
@ -591,6 +599,7 @@ Faiz Khan <faizkhan00@gmail.com>
falmp <chico.lopes@gmail.com> falmp <chico.lopes@gmail.com>
Fangming Fang <fangming.fang@arm.com> Fangming Fang <fangming.fang@arm.com>
Fangyuan Gao <21551127@zju.edu.cn> Fangyuan Gao <21551127@zju.edu.cn>
fanjiyun <fan.jiyun@zte.com.cn>
Fareed Dudhia <fareeddudhia@googlemail.com> Fareed Dudhia <fareeddudhia@googlemail.com>
Fathi Boudra <fathi.boudra@linaro.org> Fathi Boudra <fathi.boudra@linaro.org>
Federico Gimenez <fgimenez@coit.es> Federico Gimenez <fgimenez@coit.es>
@ -621,6 +630,7 @@ Florin Patan <florinpatan@gmail.com>
fonglh <fonglh@gmail.com> fonglh <fonglh@gmail.com>
Foysal Iqbal <foysal.iqbal.fb@gmail.com> Foysal Iqbal <foysal.iqbal.fb@gmail.com>
Francesc Campoy <campoy@google.com> Francesc Campoy <campoy@google.com>
Francesco Mari <mari.francesco@gmail.com>
Francis Chuang <francis.chuang@boostport.com> Francis Chuang <francis.chuang@boostport.com>
Francisco Carriedo <fcarriedo@gmail.com> Francisco Carriedo <fcarriedo@gmail.com>
Francisco Souza <f@souza.cc> Francisco Souza <f@souza.cc>
@ -653,6 +663,7 @@ Gaël PORTAY <gael.portay@savoirfairelinux.com>
Genki Takiuchi <genki@s21g.com> Genki Takiuchi <genki@s21g.com>
GennadySpb <lipenkov@gmail.com> GennadySpb <lipenkov@gmail.com>
Geoffrey Bachelet <grosfrais@gmail.com> Geoffrey Bachelet <grosfrais@gmail.com>
Geon Kim <geon0250@gmail.com>
George Kontridze <george@bugsnag.com> George Kontridze <george@bugsnag.com>
George MacRorie <gmacr31@gmail.com> George MacRorie <gmacr31@gmail.com>
George Xie <georgexsh@gmail.com> George Xie <georgexsh@gmail.com>
@ -676,6 +687,7 @@ Gopikannan Venugopalsamy <gopikannan.venugopalsamy@gmail.com>
Gosuke Miyashita <gosukenator@gmail.com> Gosuke Miyashita <gosukenator@gmail.com>
Gou Rao <gou@portworx.com> Gou Rao <gou@portworx.com>
Govinda Fichtner <govinda.fichtner@googlemail.com> Govinda Fichtner <govinda.fichtner@googlemail.com>
Grant Millar <grant@cylo.io>
Grant Reaber <grant.reaber@gmail.com> Grant Reaber <grant.reaber@gmail.com>
Graydon Hoare <graydon@pobox.com> Graydon Hoare <graydon@pobox.com>
Greg Fausak <greg@tacodata.com> Greg Fausak <greg@tacodata.com>
@ -694,6 +706,7 @@ Guruprasad <lgp171188@gmail.com>
Gustav Sinder <gustav.sinder@gmail.com> Gustav Sinder <gustav.sinder@gmail.com>
gwx296173 <gaojing3@huawei.com> gwx296173 <gaojing3@huawei.com>
Günter Zöchbauer <guenter@gzoechbauer.com> Günter Zöchbauer <guenter@gzoechbauer.com>
haikuoliu <haikuo@amazon.com>
Hakan Özler <hakan.ozler@kodcu.com> Hakan Özler <hakan.ozler@kodcu.com>
Hans Kristian Flaatten <hans@starefossen.com> Hans Kristian Flaatten <hans@starefossen.com>
Hans Rødtang <hansrodtang@gmail.com> Hans Rødtang <hansrodtang@gmail.com>
@ -735,6 +748,7 @@ Ian Bishop <ianbishop@pace7.com>
Ian Bull <irbull@gmail.com> Ian Bull <irbull@gmail.com>
Ian Calvert <ianjcalvert@gmail.com> Ian Calvert <ianjcalvert@gmail.com>
Ian Campbell <ian.campbell@docker.com> Ian Campbell <ian.campbell@docker.com>
Ian Chen <ianre657@gmail.com>
Ian Lee <IanLee1521@gmail.com> Ian Lee <IanLee1521@gmail.com>
Ian Main <imain@redhat.com> Ian Main <imain@redhat.com>
Ian Philpot <ian.philpot@microsoft.com> Ian Philpot <ian.philpot@microsoft.com>
@ -755,6 +769,7 @@ Ingo Gottwald <in.gottwald@gmail.com>
Isaac Dupree <antispam@idupree.com> Isaac Dupree <antispam@idupree.com>
Isabel Jimenez <contact.isabeljimenez@gmail.com> Isabel Jimenez <contact.isabeljimenez@gmail.com>
Isao Jonas <isao.jonas@gmail.com> Isao Jonas <isao.jonas@gmail.com>
Iskander Sharipov <quasilyte@gmail.com>
Ivan Babrou <ibobrik@gmail.com> Ivan Babrou <ibobrik@gmail.com>
Ivan Fraixedes <ifcdev@gmail.com> Ivan Fraixedes <ifcdev@gmail.com>
Ivan Grcic <igrcic@gmail.com> Ivan Grcic <igrcic@gmail.com>
@ -847,7 +862,7 @@ Jeroen Franse <jeroenfranse@gmail.com>
Jeroen Jacobs <github@jeroenj.be> Jeroen Jacobs <github@jeroenj.be>
Jesse Dearing <jesse.dearing@gmail.com> Jesse Dearing <jesse.dearing@gmail.com>
Jesse Dubay <jesse@thefortytwo.net> Jesse Dubay <jesse@thefortytwo.net>
Jessica Frazelle <jessfraz@google.com> Jessica Frazelle <acidburn@microsoft.com>
Jezeniel Zapanta <jpzapanta22@gmail.com> Jezeniel Zapanta <jpzapanta22@gmail.com>
Jhon Honce <jhonce@redhat.com> Jhon Honce <jhonce@redhat.com>
Ji.Zhilong <zhilongji@gmail.com> Ji.Zhilong <zhilongji@gmail.com>
@ -983,6 +998,7 @@ Karl Grzeszczak <karlgrz@gmail.com>
Karol Duleba <mr.fuxi@gmail.com> Karol Duleba <mr.fuxi@gmail.com>
Karthik Karanth <karanth.karthik@gmail.com> Karthik Karanth <karanth.karthik@gmail.com>
Karthik Nayak <Karthik.188@gmail.com> Karthik Nayak <Karthik.188@gmail.com>
Kasper Fabæch Brandt <poizan@poizan.dk>
Kate Heddleston <kate.heddleston@gmail.com> Kate Heddleston <kate.heddleston@gmail.com>
Katie McLaughlin <katie@glasnt.com> Katie McLaughlin <katie@glasnt.com>
Kato Kazuyoshi <kato.kazuyoshi@gmail.com> Kato Kazuyoshi <kato.kazuyoshi@gmail.com>
@ -990,6 +1006,7 @@ Katrina Owen <katrina.owen@gmail.com>
Kawsar Saiyeed <kawsar.saiyeed@projiris.com> Kawsar Saiyeed <kawsar.saiyeed@projiris.com>
Kay Yan <kay.yan@daocloud.io> Kay Yan <kay.yan@daocloud.io>
kayrus <kay.diam@gmail.com> kayrus <kay.diam@gmail.com>
Kazuhiro Sera <seratch@gmail.com>
Ke Li <kel@splunk.com> Ke Li <kel@splunk.com>
Ke Xu <leonhartx.k@gmail.com> Ke Xu <leonhartx.k@gmail.com>
Kei Ohmura <ohmura.kei@gmail.com> Kei Ohmura <ohmura.kei@gmail.com>
@ -998,6 +1015,7 @@ Keli Hu <dev@keli.hu>
Ken Cochrane <kencochrane@gmail.com> Ken Cochrane <kencochrane@gmail.com>
Ken Herner <kherner@progress.com> Ken Herner <kherner@progress.com>
Ken ICHIKAWA <ichikawa.ken@jp.fujitsu.com> Ken ICHIKAWA <ichikawa.ken@jp.fujitsu.com>
Ken Reese <krrgithub@gmail.com>
Kenfe-Mickaël Laventure <mickael.laventure@gmail.com> Kenfe-Mickaël Laventure <mickael.laventure@gmail.com>
Kenjiro Nakayama <nakayamakenjiro@gmail.com> Kenjiro Nakayama <nakayamakenjiro@gmail.com>
Kent Johnson <kentoj@gmail.com> Kent Johnson <kentoj@gmail.com>
@ -1035,9 +1053,9 @@ Krasimir Georgiev <support@vip-consult.co.uk>
Kris-Mikael Krister <krismikael@protonmail.com> Kris-Mikael Krister <krismikael@protonmail.com>
Kristian Haugene <kristian.haugene@capgemini.com> Kristian Haugene <kristian.haugene@capgemini.com>
Kristina Zabunova <triara.xiii@gmail.com> Kristina Zabunova <triara.xiii@gmail.com>
krrg <krrgithub@gmail.com>
Kun Zhang <zkazure@gmail.com> Kun Zhang <zkazure@gmail.com>
Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp> Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp>
Kunal Tyagi <tyagi.kunal@live.com>
Kyle Conroy <kyle.j.conroy@gmail.com> Kyle Conroy <kyle.j.conroy@gmail.com>
Kyle Linden <linden.kyle@gmail.com> Kyle Linden <linden.kyle@gmail.com>
kyu <leehk1227@gmail.com> kyu <leehk1227@gmail.com>
@ -1060,6 +1078,7 @@ Leandro Siqueira <leandro.siqueira@gmail.com>
Lee Chao <932819864@qq.com> Lee Chao <932819864@qq.com>
Lee, Meng-Han <sunrisedm4@gmail.com> Lee, Meng-Han <sunrisedm4@gmail.com>
leeplay <hyeongkyu.lee@navercorp.com> leeplay <hyeongkyu.lee@navercorp.com>
Lei Gong <lgong@alauda.io>
Lei Jitang <leijitang@huawei.com> Lei Jitang <leijitang@huawei.com>
Len Weincier <len@cloudafrica.net> Len Weincier <len@cloudafrica.net>
Lennie <github@consolejunkie.net> Lennie <github@consolejunkie.net>
@ -1095,6 +1114,7 @@ Lokesh Mandvekar <lsm5@fedoraproject.org>
longliqiang88 <394564827@qq.com> longliqiang88 <394564827@qq.com>
Lorenz Leutgeb <lorenz.leutgeb@gmail.com> Lorenz Leutgeb <lorenz.leutgeb@gmail.com>
Lorenzo Fontana <lo@linux.com> Lorenzo Fontana <lo@linux.com>
Lotus Fenn <fenn.lotus@gmail.com>
Louis Opter <kalessin@kalessin.fr> Louis Opter <kalessin@kalessin.fr>
Luca Favatella <luca.favatella@erlang-solutions.com> Luca Favatella <luca.favatella@erlang-solutions.com>
Luca Marturana <lucamarturana@gmail.com> Luca Marturana <lucamarturana@gmail.com>
@ -1167,6 +1187,7 @@ Martijn van Oosterhout <kleptog@svana.org>
Martin Honermeyer <maze@strahlungsfrei.de> Martin Honermeyer <maze@strahlungsfrei.de>
Martin Kelly <martin@surround.io> Martin Kelly <martin@surround.io>
Martin Mosegaard Amdisen <martin.amdisen@praqma.com> Martin Mosegaard Amdisen <martin.amdisen@praqma.com>
Martin Muzatko <martin@happy-css.com>
Martin Redmond <redmond.martin@gmail.com> Martin Redmond <redmond.martin@gmail.com>
Mary Anthony <mary.anthony@docker.com> Mary Anthony <mary.anthony@docker.com>
Masahito Zembutsu <zembutsu@users.noreply.github.com> Masahito Zembutsu <zembutsu@users.noreply.github.com>
@ -1248,8 +1269,9 @@ Michal Wieczorek <wieczorek-michal@wp.pl>
Michaël Pailloncy <mpapo.dev@gmail.com> Michaël Pailloncy <mpapo.dev@gmail.com>
Michał Czeraszkiewicz <czerasz@gmail.com> Michał Czeraszkiewicz <czerasz@gmail.com>
Michał Gryko <github@odkurzacz.org> Michał Gryko <github@odkurzacz.org>
Michiel@unhosted <michiel@unhosted.org> Michiel de Jong <michiel@unhosted.org>
Mickaël FORTUNATO <morsi.morsicus@gmail.com> Mickaël Fortunato <morsi.morsicus@gmail.com>
Mickaël Remars <mickael@remars.com>
Miguel Angel Fernández <elmendalerenda@gmail.com> Miguel Angel Fernández <elmendalerenda@gmail.com>
Miguel Morales <mimoralea@gmail.com> Miguel Morales <mimoralea@gmail.com>
Mihai Borobocea <MihaiBorob@gmail.com> Mihai Borobocea <MihaiBorob@gmail.com>
@ -1337,6 +1359,7 @@ Nicolas Dudebout <nicolas.dudebout@gatech.edu>
Nicolas Goy <kuon@goyman.com> Nicolas Goy <kuon@goyman.com>
Nicolas Kaiser <nikai@nikai.net> Nicolas Kaiser <nikai@nikai.net>
Nicolas Sterchele <sterchele.nicolas@gmail.com> Nicolas Sterchele <sterchele.nicolas@gmail.com>
Nicolas V Castet <nvcastet@us.ibm.com>
Nicolás Hock Isaza <nhocki@gmail.com> Nicolás Hock Isaza <nhocki@gmail.com>
Nigel Poulton <nigelpoulton@hotmail.com> Nigel Poulton <nigelpoulton@hotmail.com>
Nik Nyby <nikolas@gnu.org> Nik Nyby <nikolas@gnu.org>
@ -1452,6 +1475,7 @@ Prasanna Gautam <prasannagautam@gmail.com>
Pratik Karki <prertik@outlook.com> Pratik Karki <prertik@outlook.com>
Prayag Verma <prayag.verma@gmail.com> Prayag Verma <prayag.verma@gmail.com>
Priya Wadhwa <priyawadhwa@google.com> Priya Wadhwa <priyawadhwa@google.com>
Projjol Banerji <probaner23@gmail.com>
Przemek Hejman <przemyslaw.hejman@gmail.com> Przemek Hejman <przemyslaw.hejman@gmail.com>
Pure White <daniel48@126.com> Pure White <daniel48@126.com>
pysqz <randomq@126.com> pysqz <randomq@126.com>
@ -1546,6 +1570,7 @@ Rozhnov Alexandr <nox73@ya.ru>
Rudolph Gottesheim <r.gottesheim@loot.at> Rudolph Gottesheim <r.gottesheim@loot.at>
Rui Lopes <rgl@ruilopes.com> Rui Lopes <rgl@ruilopes.com>
Runshen Zhu <runshen.zhu@gmail.com> Runshen Zhu <runshen.zhu@gmail.com>
Russ Magee <rmagee@gmail.com>
Ryan Abrams <rdabrams@gmail.com> Ryan Abrams <rdabrams@gmail.com>
Ryan Anderson <anderson.ryanc@gmail.com> Ryan Anderson <anderson.ryanc@gmail.com>
Ryan Aslett <github@mixologic.com> Ryan Aslett <github@mixologic.com>
@ -1572,6 +1597,7 @@ Sachin Joshi <sachin_jayant_joshi@hotmail.com>
Sagar Hani <sagarhani33@gmail.com> Sagar Hani <sagarhani33@gmail.com>
Sainath Grandhi <sainath.grandhi@intel.com> Sainath Grandhi <sainath.grandhi@intel.com>
Sakeven Jiang <jc5930@sina.cn> Sakeven Jiang <jc5930@sina.cn>
Salahuddin Khan <salah@docker.com>
Sally O'Malley <somalley@redhat.com> Sally O'Malley <somalley@redhat.com>
Sam Abed <sam.abed@gmail.com> Sam Abed <sam.abed@gmail.com>
Sam Alba <sam.alba@gmail.com> Sam Alba <sam.alba@gmail.com>
@ -1620,6 +1646,7 @@ Sergey Alekseev <sergey.alekseev.minsk@gmail.com>
Sergey Evstifeev <sergey.evstifeev@gmail.com> Sergey Evstifeev <sergey.evstifeev@gmail.com>
Sergii Kabashniuk <skabashnyuk@codenvy.com> Sergii Kabashniuk <skabashnyuk@codenvy.com>
Serhat Gülçiçek <serhat25@gmail.com> Serhat Gülçiçek <serhat25@gmail.com>
SeungUkLee <lsy931106@gmail.com>
Sevki Hasirci <s@sevki.org> Sevki Hasirci <s@sevki.org>
Shane Canon <scanon@lbl.gov> Shane Canon <scanon@lbl.gov>
Shane da Silva <shane@dasilva.io> Shane da Silva <shane@dasilva.io>
@ -1714,10 +1741,11 @@ tang0th <tang0th@gmx.com>
Tangi Colin <tangicolin@gmail.com> Tangi Colin <tangicolin@gmail.com>
Tatsuki Sugiura <sugi@nemui.org> Tatsuki Sugiura <sugi@nemui.org>
Tatsushi Inagaki <e29253@jp.ibm.com> Tatsushi Inagaki <e29253@jp.ibm.com>
Taylan Isikdemir <taylani@google.com>
Taylor Jones <monitorjbl@gmail.com> Taylor Jones <monitorjbl@gmail.com>
tbonza <tylers.pile@gmail.com>
Ted M. Young <tedyoung@gmail.com> Ted M. Young <tedyoung@gmail.com>
Tehmasp Chaudhri <tehmasp@gmail.com> Tehmasp Chaudhri <tehmasp@gmail.com>
Tejaswini Duggaraju <naduggar@microsoft.com>
Tejesh Mehta <tejesh.mehta@gmail.com> Tejesh Mehta <tejesh.mehta@gmail.com>
terryding77 <550147740@qq.com> terryding77 <550147740@qq.com>
tgic <farmer1992@gmail.com> tgic <farmer1992@gmail.com>
@ -1811,6 +1839,7 @@ Tristan Carel <tristan@cogniteev.com>
Troy Denton <trdenton@gmail.com> Troy Denton <trdenton@gmail.com>
Tycho Andersen <tycho@docker.com> Tycho Andersen <tycho@docker.com>
Tyler Brock <tyler.brock@gmail.com> Tyler Brock <tyler.brock@gmail.com>
Tyler Brown <tylers.pile@gmail.com>
Tzu-Jung Lee <roylee17@gmail.com> Tzu-Jung Lee <roylee17@gmail.com>
uhayate <uhayate.gong@daocloud.io> uhayate <uhayate.gong@daocloud.io>
Ulysse Carion <ulyssecarion@gmail.com> Ulysse Carion <ulyssecarion@gmail.com>
@ -1906,11 +1935,13 @@ XiaoBing Jiang <s7v7nislands@gmail.com>
Xiaoxu Chen <chenxiaoxu14@otcaix.iscas.ac.cn> Xiaoxu Chen <chenxiaoxu14@otcaix.iscas.ac.cn>
Xiaoyu Zhang <zhang.xiaoyu33@zte.com.cn> Xiaoyu Zhang <zhang.xiaoyu33@zte.com.cn>
xiekeyang <xiekeyang@huawei.com> xiekeyang <xiekeyang@huawei.com>
Ximo Guanter Gonzálbez <joaquin.guantergonzalbez@telefonica.com>
Xinbo Weng <xihuanbo_0521@zju.edu.cn> Xinbo Weng <xihuanbo_0521@zju.edu.cn>
Xinzi Zhou <imdreamrunner@gmail.com> Xinzi Zhou <imdreamrunner@gmail.com>
Xiuming Chen <cc@cxm.cc> Xiuming Chen <cc@cxm.cc>
Xuecong Liao <satorulogic@gmail.com> Xuecong Liao <satorulogic@gmail.com>
xuzhaokui <cynicholas@gmail.com> xuzhaokui <cynicholas@gmail.com>
Yadnyawalkya Tale <ytale@redhat.com>
Yahya <ya7yaz@gmail.com> Yahya <ya7yaz@gmail.com>
YAMADA Tsuyoshi <tyamada@minimum2scp.org> YAMADA Tsuyoshi <tyamada@minimum2scp.org>
Yamasaki Masahide <masahide.y@gmail.com> Yamasaki Masahide <masahide.y@gmail.com>
@ -1941,6 +1972,7 @@ Yu-Ju Hong <yjhong@google.com>
Yuan Sun <sunyuan3@huawei.com> Yuan Sun <sunyuan3@huawei.com>
Yuanhong Peng <pengyuanhong@huawei.com> Yuanhong Peng <pengyuanhong@huawei.com>
Yuhao Fang <fangyuhao@gmail.com> Yuhao Fang <fangyuhao@gmail.com>
Yuichiro Kaneko <spiketeika@gmail.com>
Yunxiang Huang <hyxqshk@vip.qq.com> Yunxiang Huang <hyxqshk@vip.qq.com>
Yurii Rashkovskii <yrashk@gmail.com> Yurii Rashkovskii <yrashk@gmail.com>
Yves Junqueira <yves.junqueira@gmail.com> Yves Junqueira <yves.junqueira@gmail.com>

View file

@ -176,7 +176,7 @@
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
Copyright 2013-2017 Docker, Inc. Copyright 2013-2018 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -3,7 +3,7 @@ package api // import "github.com/docker/docker/api"
// Common constants for daemon and client. // Common constants for daemon and client.
const ( const (
// DefaultVersion of Current REST API // DefaultVersion of Current REST API
DefaultVersion = "1.38" DefaultVersion = "1.39"
// NoBaseImageSpecifier is the symbol used by the FROM // NoBaseImageSpecifier is the symbol used by the FROM
// command to specify that no base image is to be used. // command to specify that no base image is to be used.

View file

@ -120,7 +120,7 @@ type NetworkStats struct {
RxBytes uint64 `json:"rx_bytes"` RxBytes uint64 `json:"rx_bytes"`
// Packets received. Windows and Linux. // Packets received. Windows and Linux.
RxPackets uint64 `json:"rx_packets"` RxPackets uint64 `json:"rx_packets"`
// Received errors. Not used on Windows. Note that we dont `omitempty` this // Received errors. Not used on Windows. Note that we don't `omitempty` this
// field as it is expected in the >=v1.21 API stats structure. // field as it is expected in the >=v1.21 API stats structure.
RxErrors uint64 `json:"rx_errors"` RxErrors uint64 `json:"rx_errors"`
// Incoming packets dropped. Windows and Linux. // Incoming packets dropped. Windows and Linux.
@ -129,7 +129,7 @@ type NetworkStats struct {
TxBytes uint64 `json:"tx_bytes"` TxBytes uint64 `json:"tx_bytes"`
// Packets sent. Windows and Linux. // Packets sent. Windows and Linux.
TxPackets uint64 `json:"tx_packets"` TxPackets uint64 `json:"tx_packets"`
// Sent errors. Not used on Windows. Note that we dont `omitempty` this // Sent errors. Not used on Windows. Note that we don't `omitempty` this
// field as it is expected in the >=v1.21 API stats structure. // field as it is expected in the >=v1.21 API stats structure.
TxErrors uint64 `json:"tx_errors"` TxErrors uint64 `json:"tx_errors"`
// Outgoing packets dropped. Windows and Linux. // Outgoing packets dropped. Windows and Linux.

View file

@ -1,6 +1,8 @@
package swarm // import "github.com/docker/docker/api/types/swarm" package swarm // import "github.com/docker/docker/api/types/swarm"
import "time" import (
"time"
)
// ClusterInfo represents info about the cluster for outputting in "info" // ClusterInfo represents info about the cluster for outputting in "info"
// it contains the same information as "Swarm", but without the JoinTokens // it contains the same information as "Swarm", but without the JoinTokens
@ -10,6 +12,8 @@ type ClusterInfo struct {
Spec Spec Spec Spec
TLSInfo TLSInfo TLSInfo TLSInfo
RootRotationInProgress bool RootRotationInProgress bool
DefaultAddrPool []string
SubnetSize uint32
} }
// Swarm represents a swarm. // Swarm represents a swarm.
@ -153,6 +157,8 @@ type InitRequest struct {
Spec Spec Spec Spec
AutoLockManagers bool AutoLockManagers bool
Availability NodeAvailability Availability NodeAvailability
DefaultAddrPool []string
SubnetSize uint32
} }
// JoinRequest is the request used to join a swarm. // JoinRequest is the request used to join a swarm.

View file

@ -102,9 +102,10 @@ type ContainerStats struct {
// Ping contains response of Engine API: // Ping contains response of Engine API:
// GET "/_ping" // GET "/_ping"
type Ping struct { type Ping struct {
APIVersion string APIVersion string
OSType string OSType string
Experimental bool Experimental bool
BuilderVersion BuilderVersion
} }
// ComponentVersion describes the version information for a specific component. // ComponentVersion describes the version information for a specific component.
@ -204,6 +205,8 @@ type Info struct {
RuncCommit Commit RuncCommit Commit
InitCommit Commit InitCommit Commit
SecurityOptions []string SecurityOptions []string
ProductLicense string `json:",omitempty"`
Warnings []string
} }
// KeyValue holds a key/value pair // KeyValue holds a key/value pair
@ -540,6 +543,7 @@ type ImagesPruneReport struct {
// BuildCachePruneReport contains the response for Engine API: // BuildCachePruneReport contains the response for Engine API:
// POST "/build/prune" // POST "/build/prune"
type BuildCachePruneReport struct { type BuildCachePruneReport struct {
CachesDeleted []string
SpaceReclaimed uint64 SpaceReclaimed uint64
} }
@ -589,14 +593,21 @@ type BuildResult struct {
// BuildCache contains information about a build cache record // BuildCache contains information about a build cache record
type BuildCache struct { type BuildCache struct {
ID string ID string
Mutable bool Parent string
InUse bool Type string
Size int64 Description string
InUse bool
Shared bool
Size int64
CreatedAt time.Time CreatedAt time.Time
LastUsedAt *time.Time LastUsedAt *time.Time
UsageCount int UsageCount int
Parent string }
Description string
// BuildCachePruneOptions hold parameters to prune the build cache
type BuildCachePruneOptions struct {
All bool
KeepStorage int64
Filters filters.Args
} }

View file

@ -4,19 +4,34 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/url"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/pkg/errors"
) )
// BuildCachePrune requests the daemon to delete unused cache data // BuildCachePrune requests the daemon to delete unused cache data
func (cli *Client) BuildCachePrune(ctx context.Context) (*types.BuildCachePruneReport, error) { func (cli *Client) BuildCachePrune(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) {
if err := cli.NewVersionError("1.31", "build prune"); err != nil { if err := cli.NewVersionError("1.31", "build prune"); err != nil {
return nil, err return nil, err
} }
report := types.BuildCachePruneReport{} report := types.BuildCachePruneReport{}
serverResp, err := cli.post(ctx, "/build/prune", nil, nil, nil) query := url.Values{}
if opts.All {
query.Set("all", "1")
}
query.Set("keep-storage", fmt.Sprintf("%d", opts.KeepStorage))
filters, err := filters.ToJSON(opts.Filters)
if err != nil {
return nil, errors.Wrap(err, "prune could not marshal filters option")
}
query.Set("filters", filters)
serverResp, err := cli.post(ctx, "/build/prune", query, nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -173,10 +173,17 @@ func WithTLSClientConfig(cacertPath, certPath, keyPath string) func(*Client) err
// WithDialer applies the dialer.DialContext to the client transport. This can be // WithDialer applies the dialer.DialContext to the client transport. This can be
// used to set the Timeout and KeepAlive settings of the client. // used to set the Timeout and KeepAlive settings of the client.
// Deprecated: use WithDialContext
func WithDialer(dialer *net.Dialer) func(*Client) error { func WithDialer(dialer *net.Dialer) func(*Client) error {
return WithDialContext(dialer.DialContext)
}
// WithDialContext applies the dialer to the client transport. This can be
// used to set the Timeout and KeepAlive settings of the client.
func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) func(*Client) error {
return func(c *Client) error { return func(c *Client) error {
if transport, ok := c.client.Transport.(*http.Transport); ok { if transport, ok := c.client.Transport.(*http.Transport); ok {
transport.DialContext = dialer.DialContext transport.DialContext = dialContext
return nil return nil
} }
return errors.Errorf("cannot apply dialer to transport: %T", c.client.Transport) return errors.Errorf("cannot apply dialer to transport: %T", c.client.Transport)
@ -400,3 +407,16 @@ func (cli *Client) CustomHTTPHeaders() map[string]string {
func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) { func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) {
cli.customHTTPHeaders = headers cli.customHTTPHeaders = headers
} }
// Dialer returns a dialer for a raw stream connection, with HTTP/1.1 header, that can be used for proxying the daemon connection.
// Used by `docker dial-stdio` (docker/cli#889).
func (cli *Client) Dialer() func(context.Context) (net.Conn, error) {
return func(ctx context.Context) (net.Conn, error) {
if transport, ok := cli.client.Transport.(*http.Transport); ok {
if transport.DialContext != nil && transport.TLSClientConfig == nil {
return transport.DialContext(ctx, cli.proto, cli.addr)
}
}
return fallbackDial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport))
}
}

View file

@ -30,7 +30,7 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu
} }
req = cli.addHeaders(req, headers) req = cli.addHeaders(req, headers)
conn, err := cli.setupHijackConn(req, "tcp") conn, err := cli.setupHijackConn(ctx, req, "tcp")
if err != nil { if err != nil {
return types.HijackedResponse{}, err return types.HijackedResponse{}, err
} }
@ -38,7 +38,9 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu
return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err
} }
func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) { // fallbackDial is used when WithDialer() was not called.
// See cli.Dialer().
func fallbackDial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
if tlsConfig != nil && proto != "unix" && proto != "npipe" { if tlsConfig != nil && proto != "unix" && proto != "npipe" {
return tls.Dial(proto, addr, tlsConfig) return tls.Dial(proto, addr, tlsConfig)
} }
@ -48,12 +50,13 @@ func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
return net.Dial(proto, addr) return net.Dial(proto, addr)
} }
func (cli *Client) setupHijackConn(req *http.Request, proto string) (net.Conn, error) { func (cli *Client) setupHijackConn(ctx context.Context, req *http.Request, proto string) (net.Conn, error) {
req.Host = cli.addr req.Host = cli.addr
req.Header.Set("Connection", "Upgrade") req.Header.Set("Connection", "Upgrade")
req.Header.Set("Upgrade", proto) req.Header.Set("Upgrade", proto)
conn, err := dial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport)) dialer := cli.Dialer()
conn, err := dialer(ctx)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?") return nil, errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
} }

View file

@ -39,6 +39,7 @@ type CommonAPIClient interface {
NegotiateAPIVersion(ctx context.Context) NegotiateAPIVersion(ctx context.Context)
NegotiateAPIVersionPing(types.Ping) NegotiateAPIVersionPing(types.Ping)
DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error)
Dialer() func(context.Context) (net.Conn, error)
Close() error Close() error
} }
@ -85,7 +86,7 @@ type DistributionAPIClient interface {
// ImageAPIClient defines API client methods for the images // ImageAPIClient defines API client methods for the images
type ImageAPIClient interface { type ImageAPIClient interface {
ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error)
BuildCachePrune(ctx context.Context) (*types.BuildCachePruneReport, error) BuildCachePrune(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error)
BuildCancel(ctx context.Context, id string) error BuildCancel(ctx context.Context, id string) error
ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error)
ImageHistory(ctx context.Context, image string) ([]image.HistoryResponseItem, error) ImageHistory(ctx context.Context, image string) ([]image.HistoryResponseItem, error)

View file

@ -7,7 +7,7 @@ import (
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
) )
// Ping pings the server and returns the value of the "Docker-Experimental", "OS-Type" & "API-Version" headers // Ping pings the server and returns the value of the "Docker-Experimental", "Builder-Version", "OS-Type" & "API-Version" headers
func (cli *Client) Ping(ctx context.Context) (types.Ping, error) { func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
var ping types.Ping var ping types.Ping
req, err := cli.buildRequest("GET", path.Join(cli.basePath, "/_ping"), nil, nil) req, err := cli.buildRequest("GET", path.Join(cli.basePath, "/_ping"), nil, nil)
@ -27,6 +27,9 @@ func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
ping.Experimental = true ping.Experimental = true
} }
ping.OSType = serverResp.header.Get("OSType") ping.OSType = serverResp.header.Get("OSType")
if bv := serverResp.header.Get("Builder-Version"); bv != "" {
ping.BuilderVersion = types.BuilderVersion(bv)
}
} }
return ping, cli.checkResponseErr(serverResp) return ping, cli.checkResponseErr(serverResp)
} }

View file

@ -14,5 +14,5 @@ func (cli *Client) DialSession(ctx context.Context, proto string, meta map[strin
} }
req = cli.addHeaders(req, meta) req = cli.addHeaders(req, meta)
return cli.setupHijackConn(req, proto) return cli.setupHijackConn(ctx, req, proto)
} }

View file

@ -37,23 +37,23 @@ const (
// MkdirAllAndChown creates a directory (include any along the path) and then modifies // MkdirAllAndChown creates a directory (include any along the path) and then modifies
// ownership to the requested uid/gid. If the directory already exists, this // ownership to the requested uid/gid. If the directory already exists, this
// function will still change ownership to the requested uid/gid pair. // function will still change ownership to the requested uid/gid pair.
func MkdirAllAndChown(path string, mode os.FileMode, owner IDPair) error { func MkdirAllAndChown(path string, mode os.FileMode, owner Identity) error {
return mkdirAs(path, mode, owner.UID, owner.GID, true, true) return mkdirAs(path, mode, owner, true, true)
} }
// MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid. // MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid.
// If the directory already exists, this function still changes ownership. // If the directory already exists, this function still changes ownership.
// Note that unlike os.Mkdir(), this function does not return IsExist error // Note that unlike os.Mkdir(), this function does not return IsExist error
// in case path already exists. // in case path already exists.
func MkdirAndChown(path string, mode os.FileMode, owner IDPair) error { func MkdirAndChown(path string, mode os.FileMode, owner Identity) error {
return mkdirAs(path, mode, owner.UID, owner.GID, false, true) return mkdirAs(path, mode, owner, false, true)
} }
// MkdirAllAndChownNew creates a directory (include any along the path) and then modifies // MkdirAllAndChownNew creates a directory (include any along the path) and then modifies
// ownership ONLY of newly created directories to the requested uid/gid. If the // ownership ONLY of newly created directories to the requested uid/gid. If the
// directories along the path exist, no change of ownership will be performed // directories along the path exist, no change of ownership will be performed
func MkdirAllAndChownNew(path string, mode os.FileMode, owner IDPair) error { func MkdirAllAndChownNew(path string, mode os.FileMode, owner Identity) error {
return mkdirAs(path, mode, owner.UID, owner.GID, true, false) return mkdirAs(path, mode, owner, true, false)
} }
// GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps. // GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
@ -102,22 +102,23 @@ func toHost(contID int, idMap []IDMap) (int, error) {
return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID) return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID)
} }
// IDPair is a UID and GID pair // Identity is either a UID and GID pair or a SID (but not both)
type IDPair struct { type Identity struct {
UID int UID int
GID int GID int
SID string
} }
// IDMappings contains a mappings of UIDs and GIDs // IdentityMapping contains a mappings of UIDs and GIDs
type IDMappings struct { type IdentityMapping struct {
uids []IDMap uids []IDMap
gids []IDMap gids []IDMap
} }
// NewIDMappings takes a requested user and group name and // NewIdentityMapping takes a requested user and group name and
// using the data from /etc/sub{uid,gid} ranges, creates the // using the data from /etc/sub{uid,gid} ranges, creates the
// proper uid and gid remapping ranges for that user/group pair // proper uid and gid remapping ranges for that user/group pair
func NewIDMappings(username, groupname string) (*IDMappings, error) { func NewIdentityMapping(username, groupname string) (*IdentityMapping, error) {
subuidRanges, err := parseSubuid(username) subuidRanges, err := parseSubuid(username)
if err != nil { if err != nil {
return nil, err return nil, err
@ -133,7 +134,7 @@ func NewIDMappings(username, groupname string) (*IDMappings, error) {
return nil, fmt.Errorf("No subgid ranges found for group %q", groupname) return nil, fmt.Errorf("No subgid ranges found for group %q", groupname)
} }
return &IDMappings{ return &IdentityMapping{
uids: createIDMap(subuidRanges), uids: createIDMap(subuidRanges),
gids: createIDMap(subgidRanges), gids: createIDMap(subgidRanges),
}, nil }, nil
@ -141,21 +142,21 @@ func NewIDMappings(username, groupname string) (*IDMappings, error) {
// NewIDMappingsFromMaps creates a new mapping from two slices // NewIDMappingsFromMaps creates a new mapping from two slices
// Deprecated: this is a temporary shim while transitioning to IDMapping // Deprecated: this is a temporary shim while transitioning to IDMapping
func NewIDMappingsFromMaps(uids []IDMap, gids []IDMap) *IDMappings { func NewIDMappingsFromMaps(uids []IDMap, gids []IDMap) *IdentityMapping {
return &IDMappings{uids: uids, gids: gids} return &IdentityMapping{uids: uids, gids: gids}
} }
// RootPair returns a uid and gid pair for the root user. The error is ignored // RootPair returns a uid and gid pair for the root user. The error is ignored
// because a root user always exists, and the defaults are correct when the uid // because a root user always exists, and the defaults are correct when the uid
// and gid maps are empty. // and gid maps are empty.
func (i *IDMappings) RootPair() IDPair { func (i *IdentityMapping) RootPair() Identity {
uid, gid, _ := GetRootUIDGID(i.uids, i.gids) uid, gid, _ := GetRootUIDGID(i.uids, i.gids)
return IDPair{UID: uid, GID: gid} return Identity{UID: uid, GID: gid}
} }
// ToHost returns the host UID and GID for the container uid, gid. // ToHost returns the host UID and GID for the container uid, gid.
// Remapping is only performed if the ids aren't already the remapped root ids // Remapping is only performed if the ids aren't already the remapped root ids
func (i *IDMappings) ToHost(pair IDPair) (IDPair, error) { func (i *IdentityMapping) ToHost(pair Identity) (Identity, error) {
var err error var err error
target := i.RootPair() target := i.RootPair()
@ -173,7 +174,7 @@ func (i *IDMappings) ToHost(pair IDPair) (IDPair, error) {
} }
// ToContainer returns the container UID and GID for the host uid and gid // ToContainer returns the container UID and GID for the host uid and gid
func (i *IDMappings) ToContainer(pair IDPair) (int, int, error) { func (i *IdentityMapping) ToContainer(pair Identity) (int, int, error) {
uid, err := toContainer(pair.UID, i.uids) uid, err := toContainer(pair.UID, i.uids)
if err != nil { if err != nil {
return -1, -1, err return -1, -1, err
@ -183,19 +184,19 @@ func (i *IDMappings) ToContainer(pair IDPair) (int, int, error) {
} }
// Empty returns true if there are no id mappings // Empty returns true if there are no id mappings
func (i *IDMappings) Empty() bool { func (i *IdentityMapping) Empty() bool {
return len(i.uids) == 0 && len(i.gids) == 0 return len(i.uids) == 0 && len(i.gids) == 0
} }
// UIDs return the UID mapping // UIDs return the UID mapping
// TODO: remove this once everything has been refactored to use pairs // TODO: remove this once everything has been refactored to use pairs
func (i *IDMappings) UIDs() []IDMap { func (i *IdentityMapping) UIDs() []IDMap {
return i.uids return i.uids
} }
// GIDs return the UID mapping // GIDs return the UID mapping
// TODO: remove this once everything has been refactored to use pairs // TODO: remove this once everything has been refactored to use pairs
func (i *IDMappings) GIDs() []IDMap { func (i *IdentityMapping) GIDs() []IDMap {
return i.gids return i.gids
} }

View file

@ -21,11 +21,12 @@ var (
getentCmd string getentCmd string
) )
func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error { func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error {
// make an array containing the original path asked for, plus (for mkAll == true) // make an array containing the original path asked for, plus (for mkAll == true)
// all path components leading up to the complete path that don't exist before we MkdirAll // all path components leading up to the complete path that don't exist before we MkdirAll
// so that we can chown all of them properly at the end. If chownExisting is false, we won't // so that we can chown all of them properly at the end. If chownExisting is false, we won't
// chown the full directory path if it exists // chown the full directory path if it exists
var paths []string var paths []string
stat, err := system.Stat(path) stat, err := system.Stat(path)
@ -38,7 +39,7 @@ func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chown
} }
// short-circuit--we were called with an existing directory and chown was requested // short-circuit--we were called with an existing directory and chown was requested
return lazyChown(path, ownerUID, ownerGID, stat) return lazyChown(path, owner.UID, owner.GID, stat)
} }
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -69,7 +70,7 @@ func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chown
// even if it existed, we will chown the requested path + any subpaths that // even if it existed, we will chown the requested path + any subpaths that
// didn't exist when we called MkdirAll // didn't exist when we called MkdirAll
for _, pathComponent := range paths { for _, pathComponent := range paths {
if err := lazyChown(pathComponent, ownerUID, ownerGID, nil); err != nil { if err := lazyChown(pathComponent, owner.UID, owner.GID, nil); err != nil {
return err return err
} }
} }
@ -78,7 +79,7 @@ func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chown
// CanAccess takes a valid (existing) directory and a uid, gid pair and determines // CanAccess takes a valid (existing) directory and a uid, gid pair and determines
// if that uid, gid pair has access (execute bit) to the directory // if that uid, gid pair has access (execute bit) to the directory
func CanAccess(path string, pair IDPair) bool { func CanAccess(path string, pair Identity) bool {
statInfo, err := system.Stat(path) statInfo, err := system.Stat(path)
if err != nil { if err != nil {
return false return false

View file

@ -6,9 +6,11 @@ import (
"github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/system"
) )
// Platforms such as Windows do not support the UID/GID concept. So make this // This is currently a wrapper around MkdirAll, however, since currently
// just a wrapper around system.MkdirAll. // permissions aren't set through this path, the identity isn't utilized.
func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error { // Ownership is handled elsewhere, but in the future could be support here
// too.
func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error {
if err := system.MkdirAll(path, mode, ""); err != nil { if err := system.MkdirAll(path, mode, ""); err != nil {
return err return err
} }
@ -18,6 +20,6 @@ func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chown
// CanAccess takes a valid (existing) directory and a uid, gid pair and determines // CanAccess takes a valid (existing) directory and a uid, gid pair and determines
// if that uid, gid pair has access (execute bit) to the directory // if that uid, gid pair has access (execute bit) to the directory
// Windows does not require/support this function, so always return true // Windows does not require/support this function, so always return true
func CanAccess(path string, pair IDPair) bool { func CanAccess(path string, identity Identity) bool {
return true return true
} }

View file

@ -176,7 +176,7 @@
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
Copyright 2014-2017 Docker, Inc. Copyright 2014-2018 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
Copyright (c) 2014-2017 The Docker & Go Authors. All rights reserved. Copyright (c) 2014-2018 The Docker & Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are

View file

@ -0,0 +1,10 @@
// +build !windows
package system // import "github.com/docker/docker/pkg/system"
// GetLongPathName converts Windows short pathnames to full pathnames.
// For example C:\Users\ADMIN~1 --> C:\Users\Administrator.
// It is a no-op on non-Windows platforms
func GetLongPathName(path string) (string, error) {
return path, nil
}

View file

@ -0,0 +1,24 @@
package system // import "github.com/docker/docker/pkg/system"
import "syscall"
// GetLongPathName converts Windows short pathnames to full pathnames.
// For example C:\Users\ADMIN~1 --> C:\Users\Administrator.
// It is a no-op on non-Windows platforms
func GetLongPathName(path string) (string, error) {
// See https://groups.google.com/forum/#!topic/golang-dev/1tufzkruoTg
p := syscall.StringToUTF16(path)
b := p // GetLongPathName says we can reuse buffer
n, err := syscall.GetLongPathName(&p[0], &b[0], uint32(len(b)))
if err != nil {
return "", err
}
if n > uint32(len(b)) {
b = make([]uint16, n)
_, err = syscall.GetLongPathName(&p[0], &b[0], uint32(len(b)))
if err != nil {
return "", err
}
}
return syscall.UTF16ToString(b), nil
}

View file

@ -34,7 +34,7 @@ func EnsureRemoveAll(dir string) error {
for { for {
err := os.RemoveAll(dir) err := os.RemoveAll(dir)
if err == nil { if err == nil {
return err return nil
} }
pe, ok := err.(*os.PathError) pe, ok := err.(*os.PathError)

View file

@ -2,16 +2,62 @@ package system // import "github.com/docker/docker/pkg/system"
import ( import (
"fmt" "fmt"
"syscall"
"unsafe" "unsafe"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
const (
OWNER_SECURITY_INFORMATION = 0x00000001
GROUP_SECURITY_INFORMATION = 0x00000002
DACL_SECURITY_INFORMATION = 0x00000004
SACL_SECURITY_INFORMATION = 0x00000008
LABEL_SECURITY_INFORMATION = 0x00000010
ATTRIBUTE_SECURITY_INFORMATION = 0x00000020
SCOPE_SECURITY_INFORMATION = 0x00000040
PROCESS_TRUST_LABEL_SECURITY_INFORMATION = 0x00000080
ACCESS_FILTER_SECURITY_INFORMATION = 0x00000100
BACKUP_SECURITY_INFORMATION = 0x00010000
PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000
PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000
UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000
UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000
)
const (
SE_UNKNOWN_OBJECT_TYPE = iota
SE_FILE_OBJECT
SE_SERVICE
SE_PRINTER
SE_REGISTRY_KEY
SE_LMSHARE
SE_KERNEL_OBJECT
SE_WINDOW_OBJECT
SE_DS_OBJECT
SE_DS_OBJECT_ALL
SE_PROVIDER_DEFINED_OBJECT
SE_WMIGUID_OBJECT
SE_REGISTRY_WOW64_32KEY
)
const (
SeTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege"
)
const (
ContainerAdministratorSidString = "S-1-5-93-2-1"
ContainerUserSidString = "S-1-5-93-2-2"
)
var ( var (
ntuserApiset = windows.NewLazyDLL("ext-ms-win-ntuser-window-l1-1-0") ntuserApiset = windows.NewLazyDLL("ext-ms-win-ntuser-window-l1-1-0")
procGetVersionExW = modkernel32.NewProc("GetVersionExW") modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
procGetProductInfo = modkernel32.NewProc("GetProductInfo") procGetVersionExW = modkernel32.NewProc("GetVersionExW")
procGetProductInfo = modkernel32.NewProc("GetProductInfo")
procSetNamedSecurityInfo = modadvapi32.NewProc("SetNamedSecurityInfoW")
procGetSecurityDescriptorDacl = modadvapi32.NewProc("GetSecurityDescriptorDacl")
) )
// OSVersion is a wrapper for Windows version information // OSVersion is a wrapper for Windows version information
@ -125,3 +171,23 @@ func HasWin32KSupport() bool {
// APIs. // APIs.
return ntuserApiset.Load() == nil return ntuserApiset.Load() == nil
} }
func SetNamedSecurityInfo(objectName *uint16, objectType uint32, securityInformation uint32, sidOwner *windows.SID, sidGroup *windows.SID, dacl *byte, sacl *byte) (result error) {
r0, _, _ := syscall.Syscall9(procSetNamedSecurityInfo.Addr(), 7, uintptr(unsafe.Pointer(objectName)), uintptr(objectType), uintptr(securityInformation), uintptr(unsafe.Pointer(sidOwner)), uintptr(unsafe.Pointer(sidGroup)), uintptr(unsafe.Pointer(dacl)), uintptr(unsafe.Pointer(sacl)), 0, 0)
if r0 != 0 {
result = syscall.Errno(r0)
}
return
}
func GetSecurityDescriptorDacl(securityDescriptor *byte, daclPresent *uint32, dacl **byte, daclDefaulted *uint32) (result error) {
r1, _, e1 := syscall.Syscall6(procGetSecurityDescriptorDacl.Addr(), 4, uintptr(unsafe.Pointer(securityDescriptor)), uintptr(unsafe.Pointer(daclPresent)), uintptr(unsafe.Pointer(dacl)), uintptr(unsafe.Pointer(daclDefaulted)), 0, 0)
if r1 == 0 {
if e1 != 0 {
result = syscall.Errno(e1)
} else {
result = syscall.EINVAL
}
}
return
}

View file

@ -62,13 +62,6 @@ func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
} }
} }
if os.Getenv("ConEmuANSI") == "ON" || os.Getenv("ConsoleZVersion") != "" {
// The ConEmu and ConsoleZ terminals emulate ANSI on output streams well.
emulateStdin = true
emulateStdout = false
emulateStderr = false
}
// Temporarily use STD_INPUT_HANDLE, STD_OUTPUT_HANDLE and // Temporarily use STD_INPUT_HANDLE, STD_OUTPUT_HANDLE and
// STD_ERROR_HANDLE from syscall rather than x/sys/windows as long as // STD_ERROR_HANDLE from syscall rather than x/sys/windows as long as
// go-ansiterm hasn't switch to x/sys/windows. // go-ansiterm hasn't switch to x/sys/windows.

View file

@ -57,7 +57,7 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
Scheme: "https", Scheme: "https",
Host: hostname, Host: hostname,
}, },
Version: APIVersion2, Version: APIVersion2,
AllowNondistributableArtifacts: ana, AllowNondistributableArtifacts: ana,
TrimHostname: true, TrimHostname: true,
TLSConfig: tlsConfig, TLSConfig: tlsConfig,
@ -70,7 +70,7 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
Scheme: "http", Scheme: "http",
Host: hostname, Host: hostname,
}, },
Version: APIVersion2, Version: APIVersion2,
AllowNondistributableArtifacts: ana, AllowNondistributableArtifacts: ana,
TrimHostname: true, TrimHostname: true,
// used to check if supposed to be secure via InsecureSkipVerify // used to check if supposed to be secure via InsecureSkipVerify

View file

@ -1,5 +1,51 @@
// +build !windows // +build !windows
/*
Package sockets is a simple unix domain socket wrapper.
Usage
For example:
import(
"fmt"
"net"
"os"
"github.com/docker/go-connections/sockets"
)
func main() {
l, err := sockets.NewUnixSocketWithOpts("/path/to/sockets",
sockets.WithChown(0,0),sockets.WithChmod(0660))
if err != nil {
panic(err)
}
echoStr := "hello"
go func() {
for {
conn, err := l.Accept()
if err != nil {
return
}
conn.Write([]byte(echoStr))
conn.Close()
}
}()
conn, err := net.Dial("unix", path)
if err != nil {
t.Fatal(err)
}
buf := make([]byte, 5)
if _, err := conn.Read(buf); err != nil {
panic(err)
} else if string(buf) != echoStr {
panic(fmt.Errorf("Msg may lost"))
}
}
*/
package sockets package sockets
import ( import (
@ -8,8 +54,31 @@ import (
"syscall" "syscall"
) )
// NewUnixSocket creates a unix socket with the specified path and group. // SockOption sets up socket file's creating option
func NewUnixSocket(path string, gid int) (net.Listener, error) { type SockOption func(string) error
// WithChown modifies the socket file's uid and gid
func WithChown(uid, gid int) SockOption {
return func(path string) error {
if err := os.Chown(path, uid, gid); err != nil {
return err
}
return nil
}
}
// WithChmod modifies socket file's access mode
func WithChmod(mask os.FileMode) SockOption {
return func(path string) error {
if err := os.Chmod(path, mask); err != nil {
return err
}
return nil
}
}
// NewUnixSocketWithOpts creates a unix socket with the specified options
func NewUnixSocketWithOpts(path string, opts ...SockOption) (net.Listener, error) {
if err := syscall.Unlink(path); err != nil && !os.IsNotExist(err) { if err := syscall.Unlink(path); err != nil && !os.IsNotExist(err) {
return nil, err return nil, err
} }
@ -20,13 +89,18 @@ func NewUnixSocket(path string, gid int) (net.Listener, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := os.Chown(path, 0, gid); err != nil {
l.Close() for _, op := range opts {
return nil, err if err := op(path); err != nil {
} l.Close()
if err := os.Chmod(path, 0660); err != nil { return nil, err
l.Close() }
return nil, err
} }
return l, nil return l, nil
} }
// NewUnixSocket creates a unix socket with the specified path and group.
func NewUnixSocket(path string, gid int) (net.Listener, error) {
return NewUnixSocketWithOpts(path, WithChown(0, gid), WithChmod(0660))
}

View file

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"strings" "strings"
"text/tabwriter" "text/tabwriter"
) )
@ -14,6 +15,8 @@ import (
const ( const (
// GitCommitKey is the key for the program's GitCommit data. // GitCommitKey is the key for the program's GitCommit data.
GitCommitKey ContextKey = "program.GitCommit" GitCommitKey ContextKey = "program.GitCommit"
// NameKey is the key for the program's name.
NameKey ContextKey = "program.Name"
// VersionKey is the key for the program's Version data. // VersionKey is the key for the program's Version data.
VersionKey ContextKey = "program.Version" VersionKey ContextKey = "program.Version"
) )
@ -41,9 +44,9 @@ type Program struct {
// but after the context is ready. // but after the context is ready.
// If a non-nil error is returned, no subcommands are run. // If a non-nil error is returned, no subcommands are run.
Before func(context.Context) error Before func(context.Context) error
// After defines a function to execute after any subcommands are run, // After defines a function to execute after any commands or action is run
// but after the subcommand has finished. // and has finished.
// It is run even if the subcommand returns an error. // It is run _only_ if the subcommand exits without an error.
After func(context.Context) error After func(context.Context) error
// Action is the function to execute when no subcommands are specified. // Action is the function to execute when no subcommands are specified.
@ -80,118 +83,31 @@ func NewProgram() *Program {
// Run is the entry point for the program. It parses the arguments and executes // Run is the entry point for the program. It parses the arguments and executes
// the commands. // the commands.
func (p *Program) Run() { func (p *Program) Run() {
// Create the context with the values we need to pass to the version command. ctx := p.defaultContext()
ctx := context.WithValue(context.Background(), GitCommitKey, p.GitCommit)
ctx = context.WithValue(ctx, VersionKey, p.Version)
// Pass the os.Args through so we can more easily unit test. // Pass the os.Args through so we can more easily unit test.
printUsage, err := p.run(ctx, os.Args) err := p.run(ctx, os.Args)
if err == nil && !printUsage { if err == nil {
// Return early if there was no error.
return return
} }
if err != nil { if err != flag.ErrHelp {
// We did not return the error to print the usage, so let's print the
// error and exit.
fmt.Fprintln(os.Stderr, err.Error()) fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
} }
if printUsage {
if err != nil { // Print the usage.
// Print an extra new line to seperate from the usage output. p.FlagSet.Usage()
fmt.Fprintln(os.Stderr)
}
p.usage(ctx)
}
os.Exit(1) os.Exit(1)
} }
func (p *Program) run(ctx context.Context, args []string) (bool, error) { func (p *Program) run(ctx context.Context, args []string) error {
// Append the version command to the list of commands by default. // Append the version command to the list of commands by default.
p.Commands = append(p.Commands, &versionCommand{}) p.Commands = append(p.Commands, &versionCommand{})
// TODO(jessfraz): Find a better way to tell that they passed -h through as a flag.
if len(args) > 1 &&
(strings.Contains(strings.ToLower(args[1]), "help") ||
strings.ToLower(args[1]) == "-h") ||
args == nil || len(args) < 1 {
return true, nil
}
// If we do not have an action set and we have no commands, print the usage
// and exit.
if p.Action == nil && len(p.Commands) < 2 {
return true, nil
}
// Check if the command exists.
var commandExists bool
if len(args) > 1 && in(args[1], p.Commands) {
commandExists = true
}
// If we are not running a commands we know, then automatically
// run the main action of the program instead.
// Also enter this loop if we weren't passed any arguments.
if p.Action != nil &&
(len(args) < 2 || !commandExists) {
return p.runAction(ctx, args)
}
// Return early if we didn't enter the single action logic and
// the command does not exist or we were passed no commands.
if len(args) < 2 {
return true, nil
}
if !commandExists {
return true, fmt.Errorf("%s: no such command", args[1])
}
// Iterate over the commands in the program.
for _, command := range p.Commands {
if args[1] == command.Name() {
// Set the default flagset if our flagset is undefined.
if p.FlagSet == nil {
p.FlagSet = defaultFlagSet(p.Name)
}
// Register the subcommand flags in with the common/global flags.
command.Register(p.FlagSet)
// Override the usage text to something nicer.
p.resetCommandUsage(command)
// Parse the flags the user gave us.
if err := p.FlagSet.Parse(args[2:]); err != nil {
return false, err
}
if p.Before != nil {
if err := p.Before(ctx); err != nil {
return false, err
}
}
// Run the command with the context and post-flag-processing args.
if err := command.Run(ctx, p.FlagSet.Args()); err != nil {
if p.After != nil {
p.After(ctx)
}
return false, err
}
// Run the after function.
if p.After != nil {
if err := p.After(ctx); err != nil {
return false, err
}
}
}
}
// Done.
return false, nil
}
func (p *Program) runAction(ctx context.Context, args []string) (bool, error) {
// Set the default flagset if our flagset is undefined. // Set the default flagset if our flagset is undefined.
if p.FlagSet == nil { if p.FlagSet == nil {
p.FlagSet = defaultFlagSet(p.Name) p.FlagSet = defaultFlagSet(p.Name)
@ -202,38 +118,111 @@ func (p *Program) runAction(ctx context.Context, args []string) (bool, error) {
p.usage(ctx) p.usage(ctx)
} }
// Parse the flags the user gave us. // IF
if err := p.FlagSet.Parse(args[1:]); err != nil { // args is <nil>
return true, nil // OR
// args is less than 1
// OR
// we have more than one arg and it equals help OR is a help flag
// THEN
// print the usage
if args == nil ||
len(args) < 1 ||
(len(args) > 1 && contains([]string{"-h", "--help", "help"}, args[1])) {
return flag.ErrHelp
} }
// Run the main action _if_ we are not in the loop for the version command // If we do not have an action set and we have no commands, print the usage
// that is added by default. // and exit.
if p.Before != nil { if p.Action == nil && len(p.Commands) < 2 {
if err := p.Before(ctx); err != nil { return flag.ErrHelp
return false, err }
// Check if the command exists.
var (
command Command
commandExists bool
)
if len(args) > 1 {
command = p.findCommand(args[1])
commandExists = command != nil
}
// Return early if we didn't enter the single action logic and
// the command does not exist or we were passed no commands.
if p.Action == nil && len(args) < 2 {
return flag.ErrHelp
}
if p.Action == nil && !commandExists {
return fmt.Errorf("%s: no such command", args[1])
}
// If we are not running a command we know, then automatically
// run the main action of the program instead.
// Also enter this loop if we weren't passed any arguments.
if p.Action != nil &&
(len(args) < 2 || !commandExists) {
// Parse the flags the user gave us.
if err := p.FlagSet.Parse(args[1:]); err != nil {
return err
}
// Run the main action _if_ we are not in the loop for the version command
// that is added by default.
if p.Before != nil {
if err := p.Before(ctx); err != nil {
return err
}
}
// Run the action with the context and post-flag-processing args.
if err := p.Action(ctx, p.FlagSet.Args()); err != nil {
return err
} }
} }
// Run the action with the context and post-flag-processing args. if commandExists {
if err := p.Action(ctx, p.FlagSet.Args()); err != nil { // Register the subcommand flags in with the common/global flags.
// Run the after function. command.Register(p.FlagSet)
if p.After != nil {
p.After(ctx) // Override the usage text to something nicer.
p.resetCommandUsage(command)
// Parse the flags the user gave us.
if err := p.FlagSet.Parse(args[2:]); err != nil {
return err
} }
return false, err // Check that they didn't add a -h or --help flag after the subcommand's
// commands, like `cmd sub other thing -h`.
if contains([]string{"-h", "--help"}, args...) {
// Print the flag usage and exit.
return flag.ErrHelp
}
// Only execute the Before function for user-supplied commands.
// This excludes the version command we supply.
if p.Before != nil && command.Name() != "version" {
if err := p.Before(ctx); err != nil {
return err
}
}
// Run the command with the context and post-flag-processing args.
if err := command.Run(ctx, p.FlagSet.Args()); err != nil {
return err
}
} }
// Run the after function. // Run the after function.
if p.After != nil { if p.After != nil {
if err := p.After(ctx); err != nil { if err := p.After(ctx); err != nil {
return false, err return err
} }
} }
// Done. // Done.
return false, nil return nil
} }
func (p *Program) usage(ctx context.Context) error { func (p *Program) usage(ctx context.Context) error {
@ -275,13 +264,23 @@ func (p *Program) resetCommandUsage(command Command) {
type mflag struct { type mflag struct {
name string name string
defValue string defValue string
usage string
}
// byName implements sort.Interface for []mflag based on the name field.
type byName []mflag
func (n byName) Len() int { return len(n) }
func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
func (n byName) Less(i, j int) bool {
return strings.TrimPrefix(n[i].name, "-") < strings.TrimPrefix(n[j].name, "-")
} }
func resetFlagUsage(fs *flag.FlagSet) { func resetFlagUsage(fs *flag.FlagSet) {
var ( var (
hasFlags bool hasFlags bool
flagBlock bytes.Buffer flagBlock bytes.Buffer
flagMap = map[string]mflag{} flagMap = []mflag{}
flagWriter = tabwriter.NewWriter(&flagBlock, 0, 4, 2, ' ', 0) flagWriter = tabwriter.NewWriter(&flagBlock, 0, 4, 2, ' ', 0)
) )
@ -303,32 +302,32 @@ func resetFlagUsage(fs *flag.FlagSet) {
// Try and find duplicates (or the shortcode flags and combine them. // Try and find duplicates (or the shortcode flags and combine them.
// Like: -, --password // Like: -, --password
v, ok := flagMap[f.Usage] for k, v := range flagMap {
if !ok { if v.usage == f.Usage {
flagMap[f.Usage] = mflag{ if len(v.name) <= 2 {
name: name, // We already had the shortcode, let's append.
defValue: defValue, v.name = fmt.Sprintf("%s, -%s", v.name, name)
} else {
v.name = fmt.Sprintf("%s, -%s", name, v.name)
}
flagMap[k].name = v.name
// Return here.
return
} }
// Return here.
return
} }
if len(v.name) <= 2 { flagMap = append(flagMap, mflag{
// We already had the shortcode, let's append. name: name,
v.name = fmt.Sprintf("%s, -%s", v.name, name)
} else {
v.name = fmt.Sprintf("%s, -%s", name, v.name)
}
flagMap[f.Usage] = mflag{
name: v.name,
defValue: defValue, defValue: defValue,
} usage: f.Usage,
})
}) })
for desc, fm := range flagMap { // Sort by name and preserve order on output.
fmt.Fprintf(flagWriter, "\t-%s\t%s (default: %s)\n", fm.name, desc, fm.defValue) sort.Sort(byName(flagMap))
for i := 0; i < len(flagMap); i++ {
fmt.Fprintf(flagWriter, "\t-%s\t%s (default: %s)\n", flagMap[i].name, flagMap[i].usage, flagMap[i].defValue)
} }
flagWriter.Flush() flagWriter.Flush()
@ -347,11 +346,32 @@ func defaultFlagSet(n string) *flag.FlagSet {
return flag.NewFlagSet(n, flag.ExitOnError) return flag.NewFlagSet(n, flag.ExitOnError)
} }
func in(a string, c []Command) bool { func (p *Program) findCommand(name string) Command {
for _, b := range c { // Iterate over the commands in the program.
if b.Name() == a { for _, command := range p.Commands {
return true if command.Name() == name {
return command
}
}
return nil
}
func contains(match []string, a ...string) bool {
// Iterate over the items in the slice.
for _, s := range a {
// Iterate over the items to match.
for _, m := range match {
if s == m {
return true
}
} }
} }
return false return false
} }
func (p *Program) defaultContext() context.Context {
// Create the context with the values we need to pass to the version command.
ctx := context.WithValue(context.Background(), GitCommitKey, p.GitCommit)
ctx = context.WithValue(ctx, NameKey, p.Name)
return context.WithValue(ctx, VersionKey, p.Version)
}

View file

@ -26,7 +26,7 @@ func (cmd *versionCommand) Run(ctx context.Context, args []string) error {
go version : %s go version : %s
go compiler : %s go compiler : %s
platform : %s/%s platform : %s/%s
`, "ship", ctx.Value(VersionKey).(string), ctx.Value(GitCommitKey).(string), `, ctx.Value(NameKey).(string), ctx.Value(VersionKey).(string), ctx.Value(GitCommitKey).(string),
runtime.Version(), runtime.Compiler, runtime.GOOS, runtime.GOARCH) runtime.Version(), runtime.Compiler, runtime.GOOS, runtime.GOARCH)
return nil return nil
} }

View file

@ -1,7 +1,4 @@
Go support for Protocol Buffers - Google's data interchange format
Copyright 2010 The Go Authors. All rights reserved. Copyright 2010 The Go Authors. All rights reserved.
https://github.com/golang/protobuf
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are

View file

@ -106,6 +106,9 @@ func defaultResolveAny(typeUrl string) (proto.Message, error) {
// way they are marshaled to JSON. Messages that implement this should // way they are marshaled to JSON. Messages that implement this should
// also implement JSONPBUnmarshaler so that the custom format can be // also implement JSONPBUnmarshaler so that the custom format can be
// parsed. // parsed.
//
// The JSON marshaling must follow the proto to JSON specification:
// https://developers.google.com/protocol-buffers/docs/proto3#json
type JSONPBMarshaler interface { type JSONPBMarshaler interface {
MarshalJSONPB(*Marshaler) ([]byte, error) MarshalJSONPB(*Marshaler) ([]byte, error)
} }
@ -114,6 +117,9 @@ type JSONPBMarshaler interface {
// the way they are unmarshaled from JSON. Messages that implement this // the way they are unmarshaled from JSON. Messages that implement this
// should also implement JSONPBMarshaler so that the custom format can be // should also implement JSONPBMarshaler so that the custom format can be
// produced. // produced.
//
// The JSON unmarshaling must follow the JSON to proto specification:
// https://developers.google.com/protocol-buffers/docs/proto3#json
type JSONPBUnmarshaler interface { type JSONPBUnmarshaler interface {
UnmarshalJSONPB(*Unmarshaler, []byte) error UnmarshalJSONPB(*Unmarshaler, []byte) error
} }
@ -565,6 +571,7 @@ func (m *Marshaler) marshalValue(out *errWriter, prop *proto.Properties, v refle
out.write(m.Indent) out.write(m.Indent)
} }
// TODO handle map key prop properly
b, err := json.Marshal(k.Interface()) b, err := json.Marshal(k.Interface())
if err != nil { if err != nil {
return err return err
@ -586,7 +593,11 @@ func (m *Marshaler) marshalValue(out *errWriter, prop *proto.Properties, v refle
out.write(` `) out.write(` `)
} }
if err := m.marshalValue(out, prop, v.MapIndex(k), indent+m.Indent); err != nil { vprop := prop
if prop != nil && prop.MapValProp != nil {
vprop = prop.MapValProp
}
if err := m.marshalValue(out, vprop, v.MapIndex(k), indent+m.Indent); err != nil {
return err return err
} }
} }
@ -778,7 +789,7 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
return nil return nil
case "Duration": case "Duration":
unq, err := strconv.Unquote(string(inputValue)) unq, err := unquote(string(inputValue))
if err != nil { if err != nil {
return err return err
} }
@ -795,7 +806,7 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
target.Field(1).SetInt(ns) target.Field(1).SetInt(ns)
return nil return nil
case "Timestamp": case "Timestamp":
unq, err := strconv.Unquote(string(inputValue)) unq, err := unquote(string(inputValue))
if err != nil { if err != nil {
return err return err
} }
@ -842,7 +853,7 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
target.Field(0).Set(reflect.ValueOf(&stpb.Value_NullValue{})) target.Field(0).Set(reflect.ValueOf(&stpb.Value_NullValue{}))
} else if v, err := strconv.ParseFloat(ivStr, 0); err == nil { } else if v, err := strconv.ParseFloat(ivStr, 0); err == nil {
target.Field(0).Set(reflect.ValueOf(&stpb.Value_NumberValue{v})) target.Field(0).Set(reflect.ValueOf(&stpb.Value_NumberValue{v}))
} else if v, err := strconv.Unquote(ivStr); err == nil { } else if v, err := unquote(ivStr); err == nil {
target.Field(0).Set(reflect.ValueOf(&stpb.Value_StringValue{v})) target.Field(0).Set(reflect.ValueOf(&stpb.Value_StringValue{v}))
} else if v, err := strconv.ParseBool(ivStr); err == nil { } else if v, err := strconv.ParseBool(ivStr); err == nil {
target.Field(0).Set(reflect.ValueOf(&stpb.Value_BoolValue{v})) target.Field(0).Set(reflect.ValueOf(&stpb.Value_BoolValue{v}))
@ -878,6 +889,9 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
target.Set(reflect.New(targetType.Elem())) target.Set(reflect.New(targetType.Elem()))
target = target.Elem() target = target.Elem()
} }
if targetType.Kind() != reflect.Int32 {
return fmt.Errorf("invalid target %q for enum %s", targetType.Kind(), prop.Enum)
}
target.SetInt(int64(n)) target.SetInt(int64(n))
return nil return nil
} }
@ -1007,16 +1021,22 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
k = reflect.ValueOf(ks) k = reflect.ValueOf(ks)
} else { } else {
k = reflect.New(targetType.Key()).Elem() k = reflect.New(targetType.Key()).Elem()
// TODO: pass the correct Properties if needed. var kprop *proto.Properties
if err := u.unmarshalValue(k, json.RawMessage(ks), nil); err != nil { if prop != nil && prop.MapKeyProp != nil {
kprop = prop.MapKeyProp
}
if err := u.unmarshalValue(k, json.RawMessage(ks), kprop); err != nil {
return err return err
} }
} }
// Unmarshal map value. // Unmarshal map value.
v := reflect.New(targetType.Elem()).Elem() v := reflect.New(targetType.Elem()).Elem()
// TODO: pass the correct Properties if needed. var vprop *proto.Properties
if err := u.unmarshalValue(v, raw, nil); err != nil { if prop != nil && prop.MapValProp != nil {
vprop = prop.MapValProp
}
if err := u.unmarshalValue(v, raw, vprop); err != nil {
return err return err
} }
target.SetMapIndex(k, v) target.SetMapIndex(k, v)
@ -1025,13 +1045,6 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
return nil return nil
} }
// 64-bit integers can be encoded as strings. In this case we drop
// the quotes and proceed as normal.
isNum := targetType.Kind() == reflect.Int64 || targetType.Kind() == reflect.Uint64
if isNum && strings.HasPrefix(string(inputValue), `"`) {
inputValue = inputValue[1 : len(inputValue)-1]
}
// Non-finite numbers can be encoded as strings. // Non-finite numbers can be encoded as strings.
isFloat := targetType.Kind() == reflect.Float32 || targetType.Kind() == reflect.Float64 isFloat := targetType.Kind() == reflect.Float32 || targetType.Kind() == reflect.Float64
if isFloat { if isFloat {
@ -1041,10 +1054,25 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
} }
} }
// integers & floats can be encoded as strings. In this case we drop
// the quotes and proceed as normal.
isNum := targetType.Kind() == reflect.Int64 || targetType.Kind() == reflect.Uint64 ||
targetType.Kind() == reflect.Int32 || targetType.Kind() == reflect.Uint32 ||
targetType.Kind() == reflect.Float32 || targetType.Kind() == reflect.Float64
if isNum && strings.HasPrefix(string(inputValue), `"`) {
inputValue = inputValue[1 : len(inputValue)-1]
}
// Use the encoding/json for parsing other value types. // Use the encoding/json for parsing other value types.
return json.Unmarshal(inputValue, target.Addr().Interface()) return json.Unmarshal(inputValue, target.Addr().Interface())
} }
func unquote(s string) (string, error) {
var ret string
err := json.Unmarshal([]byte(s), &ret)
return ret, err
}
// jsonProperties returns parsed proto.Properties for the field and corrects JSONName attribute. // jsonProperties returns parsed proto.Properties for the field and corrects JSONName attribute.
func jsonProperties(f reflect.StructField, origName bool) *proto.Properties { func jsonProperties(f reflect.StructField, origName bool) *proto.Properties {
var prop proto.Properties var prop proto.Properties
@ -1094,6 +1122,8 @@ func (s mapKeys) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s mapKeys) Less(i, j int) bool { func (s mapKeys) Less(i, j int) bool {
if k := s[i].Kind(); k == s[j].Kind() { if k := s[i].Kind(); k == s[j].Kind() {
switch k { switch k {
case reflect.String:
return s[i].String() < s[j].String()
case reflect.Int32, reflect.Int64: case reflect.Int32, reflect.Int64:
return s[i].Int() < s[j].Int() return s[i].Int() < s[j].Int()
case reflect.Uint32, reflect.Uint64: case reflect.Uint32, reflect.Uint64:

View file

@ -37,27 +37,9 @@ package proto
import ( import (
"errors" "errors"
"fmt"
"reflect" "reflect"
) )
// RequiredNotSetError is the error returned if Marshal is called with
// a protocol buffer struct whose required fields have not
// all been initialized. It is also the error returned if Unmarshal is
// called with an encoded protocol buffer that does not include all the
// required fields.
//
// When printed, RequiredNotSetError reports the first unset required field in a
// message. If the field cannot be precisely determined, it is reported as
// "{Unknown}".
type RequiredNotSetError struct {
field string
}
func (e *RequiredNotSetError) Error() string {
return fmt.Sprintf("proto: required field %q not set", e.field)
}
var ( var (
// errRepeatedHasNil is the error returned if Marshal is called with // errRepeatedHasNil is the error returned if Marshal is called with
// a struct with a repeated field containing a nil element. // a struct with a repeated field containing a nil element.

View file

@ -265,7 +265,6 @@ package proto
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"log" "log"
"reflect" "reflect"
@ -274,7 +273,66 @@ import (
"sync" "sync"
) )
var errInvalidUTF8 = errors.New("proto: invalid UTF-8 string") // RequiredNotSetError is an error type returned by either Marshal or Unmarshal.
// Marshal reports this when a required field is not initialized.
// Unmarshal reports this when a required field is missing from the wire data.
type RequiredNotSetError struct{ field string }
func (e *RequiredNotSetError) Error() string {
if e.field == "" {
return fmt.Sprintf("proto: required field not set")
}
return fmt.Sprintf("proto: required field %q not set", e.field)
}
func (e *RequiredNotSetError) RequiredNotSet() bool {
return true
}
type invalidUTF8Error struct{ field string }
func (e *invalidUTF8Error) Error() string {
if e.field == "" {
return "proto: invalid UTF-8 detected"
}
return fmt.Sprintf("proto: field %q contains invalid UTF-8", e.field)
}
func (e *invalidUTF8Error) InvalidUTF8() bool {
return true
}
// errInvalidUTF8 is a sentinel error to identify fields with invalid UTF-8.
// This error should not be exposed to the external API as such errors should
// be recreated with the field information.
var errInvalidUTF8 = &invalidUTF8Error{}
// isNonFatal reports whether the error is either a RequiredNotSet error
// or a InvalidUTF8 error.
func isNonFatal(err error) bool {
if re, ok := err.(interface{ RequiredNotSet() bool }); ok && re.RequiredNotSet() {
return true
}
if re, ok := err.(interface{ InvalidUTF8() bool }); ok && re.InvalidUTF8() {
return true
}
return false
}
type nonFatal struct{ E error }
// Merge merges err into nf and reports whether it was successful.
// Otherwise it returns false for any fatal non-nil errors.
func (nf *nonFatal) Merge(err error) (ok bool) {
if err == nil {
return true // not an error
}
if !isNonFatal(err) {
return false // fatal error
}
if nf.E == nil {
nf.E = err // store first instance of non-fatal error
}
return true
}
// Message is implemented by generated protocol buffer messages. // Message is implemented by generated protocol buffer messages.
type Message interface { type Message interface {

View file

@ -139,7 +139,7 @@ type Properties struct {
Repeated bool Repeated bool
Packed bool // relevant for repeated primitives only Packed bool // relevant for repeated primitives only
Enum string // set for enum types only Enum string // set for enum types only
proto3 bool // whether this is known to be a proto3 field; set for []byte only proto3 bool // whether this is known to be a proto3 field
oneof bool // whether this is a oneof field oneof bool // whether this is a oneof field
Default string // default value Default string // default value
@ -148,9 +148,9 @@ type Properties struct {
stype reflect.Type // set for struct types only stype reflect.Type // set for struct types only
sprop *StructProperties // set for struct types only sprop *StructProperties // set for struct types only
mtype reflect.Type // set for map types only mtype reflect.Type // set for map types only
mkeyprop *Properties // set for map types only MapKeyProp *Properties // set for map types only
mvalprop *Properties // set for map types only MapValProp *Properties // set for map types only
} }
// String formats the properties in the protobuf struct field tag style. // String formats the properties in the protobuf struct field tag style.
@ -275,16 +275,16 @@ func (p *Properties) setFieldProps(typ reflect.Type, f *reflect.StructField, loc
case reflect.Map: case reflect.Map:
p.mtype = t1 p.mtype = t1
p.mkeyprop = &Properties{} p.MapKeyProp = &Properties{}
p.mkeyprop.init(reflect.PtrTo(p.mtype.Key()), "Key", f.Tag.Get("protobuf_key"), nil, lockGetProp) p.MapKeyProp.init(reflect.PtrTo(p.mtype.Key()), "Key", f.Tag.Get("protobuf_key"), nil, lockGetProp)
p.mvalprop = &Properties{} p.MapValProp = &Properties{}
vtype := p.mtype.Elem() vtype := p.mtype.Elem()
if vtype.Kind() != reflect.Ptr && vtype.Kind() != reflect.Slice { if vtype.Kind() != reflect.Ptr && vtype.Kind() != reflect.Slice {
// The value type is not a message (*T) or bytes ([]byte), // The value type is not a message (*T) or bytes ([]byte),
// so we need encoders for the pointer to this type. // so we need encoders for the pointer to this type.
vtype = reflect.PtrTo(vtype) vtype = reflect.PtrTo(vtype)
} }
p.mvalprop.init(vtype, "Value", f.Tag.Get("protobuf_val"), nil, lockGetProp) p.MapValProp.init(vtype, "Value", f.Tag.Get("protobuf_val"), nil, lockGetProp)
} }
if p.stype != nil { if p.stype != nil {

View file

@ -231,7 +231,7 @@ func (u *marshalInfo) marshal(b []byte, ptr pointer, deterministic bool) ([]byte
return b, err return b, err
} }
var err, errreq error var err, errLater error
// The old marshaler encodes extensions at beginning. // The old marshaler encodes extensions at beginning.
if u.extensions.IsValid() { if u.extensions.IsValid() {
e := ptr.offset(u.extensions).toExtensions() e := ptr.offset(u.extensions).toExtensions()
@ -252,11 +252,13 @@ func (u *marshalInfo) marshal(b []byte, ptr pointer, deterministic bool) ([]byte
} }
} }
for _, f := range u.fields { for _, f := range u.fields {
if f.required && errreq == nil { if f.required {
if ptr.offset(f.field).getPointer().isNil() { if ptr.offset(f.field).getPointer().isNil() {
// Required field is not set. // Required field is not set.
// We record the error but keep going, to give a complete marshaling. // We record the error but keep going, to give a complete marshaling.
errreq = &RequiredNotSetError{f.name} if errLater == nil {
errLater = &RequiredNotSetError{f.name}
}
continue continue
} }
} }
@ -269,14 +271,21 @@ func (u *marshalInfo) marshal(b []byte, ptr pointer, deterministic bool) ([]byte
if err1, ok := err.(*RequiredNotSetError); ok { if err1, ok := err.(*RequiredNotSetError); ok {
// Required field in submessage is not set. // Required field in submessage is not set.
// We record the error but keep going, to give a complete marshaling. // We record the error but keep going, to give a complete marshaling.
if errreq == nil { if errLater == nil {
errreq = &RequiredNotSetError{f.name + "." + err1.field} errLater = &RequiredNotSetError{f.name + "." + err1.field}
} }
continue continue
} }
if err == errRepeatedHasNil { if err == errRepeatedHasNil {
err = errors.New("proto: repeated field " + f.name + " has nil element") err = errors.New("proto: repeated field " + f.name + " has nil element")
} }
if err == errInvalidUTF8 {
if errLater == nil {
fullName := revProtoTypes[reflect.PtrTo(u.typ)] + "." + f.name
errLater = &invalidUTF8Error{fullName}
}
continue
}
return b, err return b, err
} }
} }
@ -284,7 +293,7 @@ func (u *marshalInfo) marshal(b []byte, ptr pointer, deterministic bool) ([]byte
s := *ptr.offset(u.unrecognized).toBytes() s := *ptr.offset(u.unrecognized).toBytes()
b = append(b, s...) b = append(b, s...)
} }
return b, errreq return b, errLater
} }
// computeMarshalInfo initializes the marshal info. // computeMarshalInfo initializes the marshal info.
@ -530,6 +539,7 @@ func typeMarshaler(t reflect.Type, tags []string, nozero, oneof bool) (sizer, ma
packed := false packed := false
proto3 := false proto3 := false
validateUTF8 := true
for i := 2; i < len(tags); i++ { for i := 2; i < len(tags); i++ {
if tags[i] == "packed" { if tags[i] == "packed" {
packed = true packed = true
@ -538,6 +548,7 @@ func typeMarshaler(t reflect.Type, tags []string, nozero, oneof bool) (sizer, ma
proto3 = true proto3 = true
} }
} }
validateUTF8 = validateUTF8 && proto3
switch t.Kind() { switch t.Kind() {
case reflect.Bool: case reflect.Bool:
@ -735,6 +746,18 @@ func typeMarshaler(t reflect.Type, tags []string, nozero, oneof bool) (sizer, ma
} }
return sizeFloat64Value, appendFloat64Value return sizeFloat64Value, appendFloat64Value
case reflect.String: case reflect.String:
if validateUTF8 {
if pointer {
return sizeStringPtr, appendUTF8StringPtr
}
if slice {
return sizeStringSlice, appendUTF8StringSlice
}
if nozero {
return sizeStringValueNoZero, appendUTF8StringValueNoZero
}
return sizeStringValue, appendUTF8StringValue
}
if pointer { if pointer {
return sizeStringPtr, appendStringPtr return sizeStringPtr, appendStringPtr
} }
@ -1984,9 +2007,6 @@ func appendBoolPackedSlice(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byt
} }
func appendStringValue(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) { func appendStringValue(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
v := *ptr.toString() v := *ptr.toString()
if !utf8.ValidString(v) {
return nil, errInvalidUTF8
}
b = appendVarint(b, wiretag) b = appendVarint(b, wiretag)
b = appendVarint(b, uint64(len(v))) b = appendVarint(b, uint64(len(v)))
b = append(b, v...) b = append(b, v...)
@ -1997,9 +2017,6 @@ func appendStringValueNoZero(b []byte, ptr pointer, wiretag uint64, _ bool) ([]b
if v == "" { if v == "" {
return b, nil return b, nil
} }
if !utf8.ValidString(v) {
return nil, errInvalidUTF8
}
b = appendVarint(b, wiretag) b = appendVarint(b, wiretag)
b = appendVarint(b, uint64(len(v))) b = appendVarint(b, uint64(len(v)))
b = append(b, v...) b = append(b, v...)
@ -2011,24 +2028,83 @@ func appendStringPtr(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, err
return b, nil return b, nil
} }
v := *p v := *p
if !utf8.ValidString(v) {
return nil, errInvalidUTF8
}
b = appendVarint(b, wiretag) b = appendVarint(b, wiretag)
b = appendVarint(b, uint64(len(v))) b = appendVarint(b, uint64(len(v)))
b = append(b, v...) b = append(b, v...)
return b, nil return b, nil
} }
func appendStringSlice(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) { func appendStringSlice(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
s := *ptr.toStringSlice()
for _, v := range s {
b = appendVarint(b, wiretag)
b = appendVarint(b, uint64(len(v)))
b = append(b, v...)
}
return b, nil
}
func appendUTF8StringValue(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
var invalidUTF8 bool
v := *ptr.toString()
if !utf8.ValidString(v) {
invalidUTF8 = true
}
b = appendVarint(b, wiretag)
b = appendVarint(b, uint64(len(v)))
b = append(b, v...)
if invalidUTF8 {
return b, errInvalidUTF8
}
return b, nil
}
func appendUTF8StringValueNoZero(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
var invalidUTF8 bool
v := *ptr.toString()
if v == "" {
return b, nil
}
if !utf8.ValidString(v) {
invalidUTF8 = true
}
b = appendVarint(b, wiretag)
b = appendVarint(b, uint64(len(v)))
b = append(b, v...)
if invalidUTF8 {
return b, errInvalidUTF8
}
return b, nil
}
func appendUTF8StringPtr(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
var invalidUTF8 bool
p := *ptr.toStringPtr()
if p == nil {
return b, nil
}
v := *p
if !utf8.ValidString(v) {
invalidUTF8 = true
}
b = appendVarint(b, wiretag)
b = appendVarint(b, uint64(len(v)))
b = append(b, v...)
if invalidUTF8 {
return b, errInvalidUTF8
}
return b, nil
}
func appendUTF8StringSlice(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
var invalidUTF8 bool
s := *ptr.toStringSlice() s := *ptr.toStringSlice()
for _, v := range s { for _, v := range s {
if !utf8.ValidString(v) { if !utf8.ValidString(v) {
return nil, errInvalidUTF8 invalidUTF8 = true
} }
b = appendVarint(b, wiretag) b = appendVarint(b, wiretag)
b = appendVarint(b, uint64(len(v))) b = appendVarint(b, uint64(len(v)))
b = append(b, v...) b = append(b, v...)
} }
if invalidUTF8 {
return b, errInvalidUTF8
}
return b, nil return b, nil
} }
func appendBytes(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) { func appendBytes(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
@ -2107,7 +2183,8 @@ func makeGroupSliceMarshaler(u *marshalInfo) (sizer, marshaler) {
}, },
func(b []byte, ptr pointer, wiretag uint64, deterministic bool) ([]byte, error) { func(b []byte, ptr pointer, wiretag uint64, deterministic bool) ([]byte, error) {
s := ptr.getPointerSlice() s := ptr.getPointerSlice()
var err, errreq error var err error
var nerr nonFatal
for _, v := range s { for _, v := range s {
if v.isNil() { if v.isNil() {
return b, errRepeatedHasNil return b, errRepeatedHasNil
@ -2115,22 +2192,14 @@ func makeGroupSliceMarshaler(u *marshalInfo) (sizer, marshaler) {
b = appendVarint(b, wiretag) // start group b = appendVarint(b, wiretag) // start group
b, err = u.marshal(b, v, deterministic) b, err = u.marshal(b, v, deterministic)
b = appendVarint(b, wiretag+(WireEndGroup-WireStartGroup)) // end group b = appendVarint(b, wiretag+(WireEndGroup-WireStartGroup)) // end group
if err != nil { if !nerr.Merge(err) {
if _, ok := err.(*RequiredNotSetError); ok {
// Required field in submessage is not set.
// We record the error but keep going, to give a complete marshaling.
if errreq == nil {
errreq = err
}
continue
}
if err == ErrNil { if err == ErrNil {
err = errRepeatedHasNil err = errRepeatedHasNil
} }
return b, err return b, err
} }
} }
return b, errreq return b, nerr.E
} }
} }
@ -2174,7 +2243,8 @@ func makeMessageSliceMarshaler(u *marshalInfo) (sizer, marshaler) {
}, },
func(b []byte, ptr pointer, wiretag uint64, deterministic bool) ([]byte, error) { func(b []byte, ptr pointer, wiretag uint64, deterministic bool) ([]byte, error) {
s := ptr.getPointerSlice() s := ptr.getPointerSlice()
var err, errreq error var err error
var nerr nonFatal
for _, v := range s { for _, v := range s {
if v.isNil() { if v.isNil() {
return b, errRepeatedHasNil return b, errRepeatedHasNil
@ -2184,22 +2254,14 @@ func makeMessageSliceMarshaler(u *marshalInfo) (sizer, marshaler) {
b = appendVarint(b, uint64(siz)) b = appendVarint(b, uint64(siz))
b, err = u.marshal(b, v, deterministic) b, err = u.marshal(b, v, deterministic)
if err != nil { if !nerr.Merge(err) {
if _, ok := err.(*RequiredNotSetError); ok {
// Required field in submessage is not set.
// We record the error but keep going, to give a complete marshaling.
if errreq == nil {
errreq = err
}
continue
}
if err == ErrNil { if err == ErrNil {
err = errRepeatedHasNil err = errRepeatedHasNil
} }
return b, err return b, err
} }
} }
return b, errreq return b, nerr.E
} }
} }
@ -2223,6 +2285,25 @@ func makeMapMarshaler(f *reflect.StructField) (sizer, marshaler) {
// value. // value.
// Key cannot be pointer-typed. // Key cannot be pointer-typed.
valIsPtr := valType.Kind() == reflect.Ptr valIsPtr := valType.Kind() == reflect.Ptr
// If value is a message with nested maps, calling
// valSizer in marshal may be quadratic. We should use
// cached version in marshal (but not in size).
// If value is not message type, we don't have size cache,
// but it cannot be nested either. Just use valSizer.
valCachedSizer := valSizer
if valIsPtr && valType.Elem().Kind() == reflect.Struct {
u := getMarshalInfo(valType.Elem())
valCachedSizer = func(ptr pointer, tagsize int) int {
// Same as message sizer, but use cache.
p := ptr.getPointer()
if p.isNil() {
return 0
}
siz := u.cachedsize(p)
return siz + SizeVarint(uint64(siz)) + tagsize
}
}
return func(ptr pointer, tagsize int) int { return func(ptr pointer, tagsize int) int {
m := ptr.asPointerTo(t).Elem() // the map m := ptr.asPointerTo(t).Elem() // the map
n := 0 n := 0
@ -2243,24 +2324,26 @@ func makeMapMarshaler(f *reflect.StructField) (sizer, marshaler) {
if len(keys) > 1 && deterministic { if len(keys) > 1 && deterministic {
sort.Sort(mapKeys(keys)) sort.Sort(mapKeys(keys))
} }
var nerr nonFatal
for _, k := range keys { for _, k := range keys {
ki := k.Interface() ki := k.Interface()
vi := m.MapIndex(k).Interface() vi := m.MapIndex(k).Interface()
kaddr := toAddrPointer(&ki, false) // pointer to key kaddr := toAddrPointer(&ki, false) // pointer to key
vaddr := toAddrPointer(&vi, valIsPtr) // pointer to value vaddr := toAddrPointer(&vi, valIsPtr) // pointer to value
b = appendVarint(b, tag) b = appendVarint(b, tag)
siz := keySizer(kaddr, 1) + valSizer(vaddr, 1) // tag of key = 1 (size=1), tag of val = 2 (size=1) siz := keySizer(kaddr, 1) + valCachedSizer(vaddr, 1) // tag of key = 1 (size=1), tag of val = 2 (size=1)
b = appendVarint(b, uint64(siz)) b = appendVarint(b, uint64(siz))
b, err = keyMarshaler(b, kaddr, keyWireTag, deterministic) b, err = keyMarshaler(b, kaddr, keyWireTag, deterministic)
if err != nil { if !nerr.Merge(err) {
return b, err return b, err
} }
b, err = valMarshaler(b, vaddr, valWireTag, deterministic) b, err = valMarshaler(b, vaddr, valWireTag, deterministic)
if err != nil && err != ErrNil { // allow nil value in map if err != ErrNil && !nerr.Merge(err) { // allow nil value in map
return b, err return b, err
} }
} }
return b, nil return b, nerr.E
} }
} }
@ -2333,6 +2416,7 @@ func (u *marshalInfo) appendExtensions(b []byte, ext *XXX_InternalExtensions, de
defer mu.Unlock() defer mu.Unlock()
var err error var err error
var nerr nonFatal
// Fast-path for common cases: zero or one extensions. // Fast-path for common cases: zero or one extensions.
// Don't bother sorting the keys. // Don't bother sorting the keys.
@ -2352,11 +2436,11 @@ func (u *marshalInfo) appendExtensions(b []byte, ext *XXX_InternalExtensions, de
v := e.value v := e.value
p := toAddrPointer(&v, ei.isptr) p := toAddrPointer(&v, ei.isptr)
b, err = ei.marshaler(b, p, ei.wiretag, deterministic) b, err = ei.marshaler(b, p, ei.wiretag, deterministic)
if err != nil { if !nerr.Merge(err) {
return b, err return b, err
} }
} }
return b, nil return b, nerr.E
} }
// Sort the keys to provide a deterministic encoding. // Sort the keys to provide a deterministic encoding.
@ -2383,11 +2467,11 @@ func (u *marshalInfo) appendExtensions(b []byte, ext *XXX_InternalExtensions, de
v := e.value v := e.value
p := toAddrPointer(&v, ei.isptr) p := toAddrPointer(&v, ei.isptr)
b, err = ei.marshaler(b, p, ei.wiretag, deterministic) b, err = ei.marshaler(b, p, ei.wiretag, deterministic)
if err != nil { if !nerr.Merge(err) {
return b, err return b, err
} }
} }
return b, nil return b, nerr.E
} }
// message set format is: // message set format is:
@ -2444,6 +2528,7 @@ func (u *marshalInfo) appendMessageSet(b []byte, ext *XXX_InternalExtensions, de
defer mu.Unlock() defer mu.Unlock()
var err error var err error
var nerr nonFatal
// Fast-path for common cases: zero or one extensions. // Fast-path for common cases: zero or one extensions.
// Don't bother sorting the keys. // Don't bother sorting the keys.
@ -2470,12 +2555,12 @@ func (u *marshalInfo) appendMessageSet(b []byte, ext *XXX_InternalExtensions, de
v := e.value v := e.value
p := toAddrPointer(&v, ei.isptr) p := toAddrPointer(&v, ei.isptr)
b, err = ei.marshaler(b, p, 3<<3|WireBytes, deterministic) b, err = ei.marshaler(b, p, 3<<3|WireBytes, deterministic)
if err != nil { if !nerr.Merge(err) {
return b, err return b, err
} }
b = append(b, 1<<3|WireEndGroup) b = append(b, 1<<3|WireEndGroup)
} }
return b, nil return b, nerr.E
} }
// Sort the keys to provide a deterministic encoding. // Sort the keys to provide a deterministic encoding.
@ -2509,11 +2594,11 @@ func (u *marshalInfo) appendMessageSet(b []byte, ext *XXX_InternalExtensions, de
p := toAddrPointer(&v, ei.isptr) p := toAddrPointer(&v, ei.isptr)
b, err = ei.marshaler(b, p, 3<<3|WireBytes, deterministic) b, err = ei.marshaler(b, p, 3<<3|WireBytes, deterministic)
b = append(b, 1<<3|WireEndGroup) b = append(b, 1<<3|WireEndGroup)
if err != nil { if !nerr.Merge(err) {
return b, err return b, err
} }
} }
return b, nil return b, nerr.E
} }
// sizeV1Extensions computes the size of encoded data for a V1-API extension field. // sizeV1Extensions computes the size of encoded data for a V1-API extension field.
@ -2556,6 +2641,7 @@ func (u *marshalInfo) appendV1Extensions(b []byte, m map[int32]Extension, determ
sort.Ints(keys) sort.Ints(keys)
var err error var err error
var nerr nonFatal
for _, k := range keys { for _, k := range keys {
e := m[int32(k)] e := m[int32(k)]
if e.value == nil || e.desc == nil { if e.value == nil || e.desc == nil {
@ -2572,11 +2658,11 @@ func (u *marshalInfo) appendV1Extensions(b []byte, m map[int32]Extension, determ
v := e.value v := e.value
p := toAddrPointer(&v, ei.isptr) p := toAddrPointer(&v, ei.isptr)
b, err = ei.marshaler(b, p, ei.wiretag, deterministic) b, err = ei.marshaler(b, p, ei.wiretag, deterministic)
if err != nil { if !nerr.Merge(err) {
return b, err return b, err
} }
} }
return b, nil return b, nerr.E
} }
// newMarshaler is the interface representing objects that can marshal themselves. // newMarshaler is the interface representing objects that can marshal themselves.

View file

@ -97,6 +97,8 @@ type unmarshalFieldInfo struct {
// if a required field, contains a single set bit at this field's index in the required field list. // if a required field, contains a single set bit at this field's index in the required field list.
reqMask uint64 reqMask uint64
name string // name of the field, for error reporting
} }
var ( var (
@ -136,8 +138,8 @@ func (u *unmarshalInfo) unmarshal(m pointer, b []byte) error {
if u.isMessageSet { if u.isMessageSet {
return UnmarshalMessageSet(b, m.offset(u.extensions).toExtensions()) return UnmarshalMessageSet(b, m.offset(u.extensions).toExtensions())
} }
var reqMask uint64 // bitmask of required fields we've seen. var reqMask uint64 // bitmask of required fields we've seen.
var rnse *RequiredNotSetError // an instance of a RequiredNotSetError returned by a submessage. var errLater error
for len(b) > 0 { for len(b) > 0 {
// Read tag and wire type. // Read tag and wire type.
// Special case 1 and 2 byte varints. // Special case 1 and 2 byte varints.
@ -176,11 +178,20 @@ func (u *unmarshalInfo) unmarshal(m pointer, b []byte) error {
if r, ok := err.(*RequiredNotSetError); ok { if r, ok := err.(*RequiredNotSetError); ok {
// Remember this error, but keep parsing. We need to produce // Remember this error, but keep parsing. We need to produce
// a full parse even if a required field is missing. // a full parse even if a required field is missing.
rnse = r if errLater == nil {
errLater = r
}
reqMask |= f.reqMask reqMask |= f.reqMask
continue continue
} }
if err != errInternalBadWireType { if err != errInternalBadWireType {
if err == errInvalidUTF8 {
if errLater == nil {
fullName := revProtoTypes[reflect.PtrTo(u.typ)] + "." + f.name
errLater = &invalidUTF8Error{fullName}
}
continue
}
return err return err
} }
// Fragments with bad wire type are treated as unknown fields. // Fragments with bad wire type are treated as unknown fields.
@ -239,20 +250,16 @@ func (u *unmarshalInfo) unmarshal(m pointer, b []byte) error {
emap[int32(tag)] = e emap[int32(tag)] = e
} }
} }
if rnse != nil { if reqMask != u.reqMask && errLater == nil {
// A required field of a submessage/group is missing. Return that error.
return rnse
}
if reqMask != u.reqMask {
// A required field of this message is missing. // A required field of this message is missing.
for _, n := range u.reqFields { for _, n := range u.reqFields {
if reqMask&1 == 0 { if reqMask&1 == 0 {
return &RequiredNotSetError{n} errLater = &RequiredNotSetError{n}
} }
reqMask >>= 1 reqMask >>= 1
} }
} }
return nil return errLater
} }
// computeUnmarshalInfo fills in u with information for use // computeUnmarshalInfo fills in u with information for use
@ -351,7 +358,7 @@ func (u *unmarshalInfo) computeUnmarshalInfo() {
} }
// Store the info in the correct slot in the message. // Store the info in the correct slot in the message.
u.setTag(tag, toField(&f), unmarshal, reqMask) u.setTag(tag, toField(&f), unmarshal, reqMask, name)
} }
// Find any types associated with oneof fields. // Find any types associated with oneof fields.
@ -366,10 +373,17 @@ func (u *unmarshalInfo) computeUnmarshalInfo() {
f := typ.Field(0) // oneof implementers have one field f := typ.Field(0) // oneof implementers have one field
baseUnmarshal := fieldUnmarshaler(&f) baseUnmarshal := fieldUnmarshaler(&f)
tagstr := strings.Split(f.Tag.Get("protobuf"), ",")[1] tags := strings.Split(f.Tag.Get("protobuf"), ",")
tag, err := strconv.Atoi(tagstr) fieldNum, err := strconv.Atoi(tags[1])
if err != nil { if err != nil {
panic("protobuf tag field not an integer: " + tagstr) panic("protobuf tag field not an integer: " + tags[1])
}
var name string
for _, tag := range tags {
if strings.HasPrefix(tag, "name=") {
name = strings.TrimPrefix(tag, "name=")
break
}
} }
// Find the oneof field that this struct implements. // Find the oneof field that this struct implements.
@ -380,7 +394,7 @@ func (u *unmarshalInfo) computeUnmarshalInfo() {
// That lets us know where this struct should be stored // That lets us know where this struct should be stored
// when we encounter it during unmarshaling. // when we encounter it during unmarshaling.
unmarshal := makeUnmarshalOneof(typ, of.ityp, baseUnmarshal) unmarshal := makeUnmarshalOneof(typ, of.ityp, baseUnmarshal)
u.setTag(tag, of.field, unmarshal, 0) u.setTag(fieldNum, of.field, unmarshal, 0, name)
} }
} }
} }
@ -401,7 +415,7 @@ func (u *unmarshalInfo) computeUnmarshalInfo() {
// [0 0] is [tag=0/wiretype=varint varint-encoded-0]. // [0 0] is [tag=0/wiretype=varint varint-encoded-0].
u.setTag(0, zeroField, func(b []byte, f pointer, w int) ([]byte, error) { u.setTag(0, zeroField, func(b []byte, f pointer, w int) ([]byte, error) {
return nil, fmt.Errorf("proto: %s: illegal tag 0 (wire type %d)", t, w) return nil, fmt.Errorf("proto: %s: illegal tag 0 (wire type %d)", t, w)
}, 0) }, 0, "")
// Set mask for required field check. // Set mask for required field check.
u.reqMask = uint64(1)<<uint(len(u.reqFields)) - 1 u.reqMask = uint64(1)<<uint(len(u.reqFields)) - 1
@ -413,8 +427,9 @@ func (u *unmarshalInfo) computeUnmarshalInfo() {
// tag = tag # for field // tag = tag # for field
// field/unmarshal = unmarshal info for that field. // field/unmarshal = unmarshal info for that field.
// reqMask = if required, bitmask for field position in required field list. 0 otherwise. // reqMask = if required, bitmask for field position in required field list. 0 otherwise.
func (u *unmarshalInfo) setTag(tag int, field field, unmarshal unmarshaler, reqMask uint64) { // name = short name of the field.
i := unmarshalFieldInfo{field: field, unmarshal: unmarshal, reqMask: reqMask} func (u *unmarshalInfo) setTag(tag int, field field, unmarshal unmarshaler, reqMask uint64, name string) {
i := unmarshalFieldInfo{field: field, unmarshal: unmarshal, reqMask: reqMask, name: name}
n := u.typ.NumField() n := u.typ.NumField()
if tag >= 0 && (tag < 16 || tag < 2*n) { // TODO: what are the right numbers here? if tag >= 0 && (tag < 16 || tag < 2*n) { // TODO: what are the right numbers here?
for len(u.dense) <= tag { for len(u.dense) <= tag {
@ -442,11 +457,17 @@ func typeUnmarshaler(t reflect.Type, tags string) unmarshaler {
tagArray := strings.Split(tags, ",") tagArray := strings.Split(tags, ",")
encoding := tagArray[0] encoding := tagArray[0]
name := "unknown" name := "unknown"
proto3 := false
validateUTF8 := true
for _, tag := range tagArray[3:] { for _, tag := range tagArray[3:] {
if strings.HasPrefix(tag, "name=") { if strings.HasPrefix(tag, "name=") {
name = tag[5:] name = tag[5:]
} }
if tag == "proto3" {
proto3 = true
}
} }
validateUTF8 = validateUTF8 && proto3
// Figure out packaging (pointer, slice, or both) // Figure out packaging (pointer, slice, or both)
slice := false slice := false
@ -594,6 +615,15 @@ func typeUnmarshaler(t reflect.Type, tags string) unmarshaler {
} }
return unmarshalBytesValue return unmarshalBytesValue
case reflect.String: case reflect.String:
if validateUTF8 {
if pointer {
return unmarshalUTF8StringPtr
}
if slice {
return unmarshalUTF8StringSlice
}
return unmarshalUTF8StringValue
}
if pointer { if pointer {
return unmarshalStringPtr return unmarshalStringPtr
} }
@ -1448,9 +1478,6 @@ func unmarshalStringValue(b []byte, f pointer, w int) ([]byte, error) {
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
v := string(b[:x]) v := string(b[:x])
if !utf8.ValidString(v) {
return nil, errInvalidUTF8
}
*f.toString() = v *f.toString() = v
return b[x:], nil return b[x:], nil
} }
@ -1468,9 +1495,6 @@ func unmarshalStringPtr(b []byte, f pointer, w int) ([]byte, error) {
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
v := string(b[:x]) v := string(b[:x])
if !utf8.ValidString(v) {
return nil, errInvalidUTF8
}
*f.toStringPtr() = &v *f.toStringPtr() = &v
return b[x:], nil return b[x:], nil
} }
@ -1488,14 +1512,72 @@ func unmarshalStringSlice(b []byte, f pointer, w int) ([]byte, error) {
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
v := string(b[:x]) v := string(b[:x])
if !utf8.ValidString(v) {
return nil, errInvalidUTF8
}
s := f.toStringSlice() s := f.toStringSlice()
*s = append(*s, v) *s = append(*s, v)
return b[x:], nil return b[x:], nil
} }
func unmarshalUTF8StringValue(b []byte, f pointer, w int) ([]byte, error) {
if w != WireBytes {
return b, errInternalBadWireType
}
x, n := decodeVarint(b)
if n == 0 {
return nil, io.ErrUnexpectedEOF
}
b = b[n:]
if x > uint64(len(b)) {
return nil, io.ErrUnexpectedEOF
}
v := string(b[:x])
*f.toString() = v
if !utf8.ValidString(v) {
return b[x:], errInvalidUTF8
}
return b[x:], nil
}
func unmarshalUTF8StringPtr(b []byte, f pointer, w int) ([]byte, error) {
if w != WireBytes {
return b, errInternalBadWireType
}
x, n := decodeVarint(b)
if n == 0 {
return nil, io.ErrUnexpectedEOF
}
b = b[n:]
if x > uint64(len(b)) {
return nil, io.ErrUnexpectedEOF
}
v := string(b[:x])
*f.toStringPtr() = &v
if !utf8.ValidString(v) {
return b[x:], errInvalidUTF8
}
return b[x:], nil
}
func unmarshalUTF8StringSlice(b []byte, f pointer, w int) ([]byte, error) {
if w != WireBytes {
return b, errInternalBadWireType
}
x, n := decodeVarint(b)
if n == 0 {
return nil, io.ErrUnexpectedEOF
}
b = b[n:]
if x > uint64(len(b)) {
return nil, io.ErrUnexpectedEOF
}
v := string(b[:x])
s := f.toStringSlice()
*s = append(*s, v)
if !utf8.ValidString(v) {
return b[x:], errInvalidUTF8
}
return b[x:], nil
}
var emptyBuf [0]byte var emptyBuf [0]byte
func unmarshalBytesValue(b []byte, f pointer, w int) ([]byte, error) { func unmarshalBytesValue(b []byte, f pointer, w int) ([]byte, error) {
@ -1674,6 +1756,7 @@ func makeUnmarshalMap(f *reflect.StructField) unmarshaler {
// Maps will be somewhat slow. Oh well. // Maps will be somewhat slow. Oh well.
// Read key and value from data. // Read key and value from data.
var nerr nonFatal
k := reflect.New(kt) k := reflect.New(kt)
v := reflect.New(vt) v := reflect.New(vt)
for len(b) > 0 { for len(b) > 0 {
@ -1694,7 +1777,7 @@ func makeUnmarshalMap(f *reflect.StructField) unmarshaler {
err = errInternalBadWireType // skip unknown tag err = errInternalBadWireType // skip unknown tag
} }
if err == nil { if nerr.Merge(err) {
continue continue
} }
if err != errInternalBadWireType { if err != errInternalBadWireType {
@ -1717,7 +1800,7 @@ func makeUnmarshalMap(f *reflect.StructField) unmarshaler {
// Insert into map. // Insert into map.
m.SetMapIndex(k.Elem(), v.Elem()) m.SetMapIndex(k.Elem(), v.Elem())
return r, nil return r, nerr.E
} }
} }
@ -1743,15 +1826,16 @@ func makeUnmarshalOneof(typ, ityp reflect.Type, unmarshal unmarshaler) unmarshal
// Unmarshal data into holder. // Unmarshal data into holder.
// We unmarshal into the first field of the holder object. // We unmarshal into the first field of the holder object.
var err error var err error
var nerr nonFatal
b, err = unmarshal(b, valToPointer(v).offset(field0), w) b, err = unmarshal(b, valToPointer(v).offset(field0), w)
if err != nil { if !nerr.Merge(err) {
return nil, err return nil, err
} }
// Write pointer to holder into target field. // Write pointer to holder into target field.
f.asPointerTo(ityp).Elem().Set(v) f.asPointerTo(ityp).Elem().Set(v)
return b, nil return b, nerr.E
} }
} }

View file

@ -353,7 +353,7 @@ func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
return err return err
} }
} }
if err := tm.writeAny(w, key, props.mkeyprop); err != nil { if err := tm.writeAny(w, key, props.MapKeyProp); err != nil {
return err return err
} }
if err := w.WriteByte('\n'); err != nil { if err := w.WriteByte('\n'); err != nil {
@ -370,7 +370,7 @@ func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
return err return err
} }
} }
if err := tm.writeAny(w, val, props.mvalprop); err != nil { if err := tm.writeAny(w, val, props.MapValProp); err != nil {
return err return err
} }
if err := w.WriteByte('\n'); err != nil { if err := w.WriteByte('\n'); err != nil {

View file

@ -630,17 +630,17 @@ func (p *textParser) readStruct(sv reflect.Value, terminator string) error {
if err := p.consumeToken(":"); err != nil { if err := p.consumeToken(":"); err != nil {
return err return err
} }
if err := p.readAny(key, props.mkeyprop); err != nil { if err := p.readAny(key, props.MapKeyProp); err != nil {
return err return err
} }
if err := p.consumeOptionalSeparator(); err != nil { if err := p.consumeOptionalSeparator(); err != nil {
return err return err
} }
case "value": case "value":
if err := p.checkForColon(props.mvalprop, dst.Type().Elem()); err != nil { if err := p.checkForColon(props.MapValProp, dst.Type().Elem()); err != nil {
return err return err
} }
if err := p.readAny(val, props.mvalprop); err != nil { if err := p.readAny(val, props.MapValProp); err != nil {
return err return err
} }
if err := p.consumeOptionalSeparator(); err != nil { if err := p.consumeOptionalSeparator(); err != nil {

View file

@ -130,10 +130,12 @@ func UnmarshalAny(any *any.Any, pb proto.Message) error {
// Is returns true if any value contains a given message type. // Is returns true if any value contains a given message type.
func Is(any *any.Any, pb proto.Message) bool { func Is(any *any.Any, pb proto.Message) bool {
aname, err := AnyMessageName(any) // The following is equivalent to AnyMessageName(any) == proto.MessageName(pb),
if err != nil { // but it avoids scanning TypeUrl for the slash.
if any == nil {
return false return false
} }
name := proto.MessageName(pb)
return aname == proto.MessageName(pb) prefix := len(any.TypeUrl) - len(name)
return prefix >= 1 && any.TypeUrl[prefix-1] == '/' && any.TypeUrl[prefix:] == name
} }

View file

@ -121,7 +121,7 @@ type Any struct {
// Schemes other than `http`, `https` (or the empty scheme) might be // Schemes other than `http`, `https` (or the empty scheme) might be
// used with implementation specific semantics. // used with implementation specific semantics.
// //
TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl" json:"type_url,omitempty"` TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl,proto3" json:"type_url,omitempty"`
// Must be a valid serialized protocol buffer of the above specified type. // Must be a valid serialized protocol buffer of the above specified type.
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`

View file

@ -82,14 +82,14 @@ type Duration struct {
// Signed seconds of the span of time. Must be from -315,576,000,000 // Signed seconds of the span of time. Must be from -315,576,000,000
// to +315,576,000,000 inclusive. Note: these bounds are computed from: // to +315,576,000,000 inclusive. Note: these bounds are computed from:
// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
Seconds int64 `protobuf:"varint,1,opt,name=seconds" json:"seconds,omitempty"` Seconds int64 `protobuf:"varint,1,opt,name=seconds,proto3" json:"seconds,omitempty"`
// Signed fractions of a second at nanosecond resolution of the span // Signed fractions of a second at nanosecond resolution of the span
// of time. Durations less than one second are represented with a 0 // of time. Durations less than one second are represented with a 0
// `seconds` field and a positive or negative `nanos` field. For durations // `seconds` field and a positive or negative `nanos` field. For durations
// of one second or more, a non-zero value for the `nanos` field must be // of one second or more, a non-zero value for the `nanos` field must be
// of the same sign as the `seconds` field. Must be from -999,999,999 // of the same sign as the `seconds` field. Must be from -999,999,999
// to +999,999,999 inclusive. // to +999,999,999 inclusive.
Nanos int32 `protobuf:"varint,2,opt,name=nanos" json:"nanos,omitempty"` Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`

View file

@ -54,7 +54,7 @@ func (NullValue) XXX_WellKnownType() string { return "NullValue" }
// The JSON representation for `Struct` is JSON object. // The JSON representation for `Struct` is JSON object.
type Struct struct { type Struct struct {
// Unordered map of dynamically typed values. // Unordered map of dynamically typed values.
Fields map[string]*Value `protobuf:"bytes,1,rep,name=fields" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` Fields map[string]*Value `protobuf:"bytes,1,rep,name=fields,proto3" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
@ -144,30 +144,40 @@ type isValue_Kind interface {
} }
type Value_NullValue struct { type Value_NullValue struct {
NullValue NullValue `protobuf:"varint,1,opt,name=null_value,json=nullValue,enum=google.protobuf.NullValue,oneof"` NullValue NullValue `protobuf:"varint,1,opt,name=null_value,json=nullValue,proto3,enum=google.protobuf.NullValue,oneof"`
}
type Value_NumberValue struct {
NumberValue float64 `protobuf:"fixed64,2,opt,name=number_value,json=numberValue,oneof"`
}
type Value_StringValue struct {
StringValue string `protobuf:"bytes,3,opt,name=string_value,json=stringValue,oneof"`
}
type Value_BoolValue struct {
BoolValue bool `protobuf:"varint,4,opt,name=bool_value,json=boolValue,oneof"`
}
type Value_StructValue struct {
StructValue *Struct `protobuf:"bytes,5,opt,name=struct_value,json=structValue,oneof"`
}
type Value_ListValue struct {
ListValue *ListValue `protobuf:"bytes,6,opt,name=list_value,json=listValue,oneof"`
} }
func (*Value_NullValue) isValue_Kind() {} type Value_NumberValue struct {
NumberValue float64 `protobuf:"fixed64,2,opt,name=number_value,json=numberValue,proto3,oneof"`
}
type Value_StringValue struct {
StringValue string `protobuf:"bytes,3,opt,name=string_value,json=stringValue,proto3,oneof"`
}
type Value_BoolValue struct {
BoolValue bool `protobuf:"varint,4,opt,name=bool_value,json=boolValue,proto3,oneof"`
}
type Value_StructValue struct {
StructValue *Struct `protobuf:"bytes,5,opt,name=struct_value,json=structValue,proto3,oneof"`
}
type Value_ListValue struct {
ListValue *ListValue `protobuf:"bytes,6,opt,name=list_value,json=listValue,proto3,oneof"`
}
func (*Value_NullValue) isValue_Kind() {}
func (*Value_NumberValue) isValue_Kind() {} func (*Value_NumberValue) isValue_Kind() {}
func (*Value_StringValue) isValue_Kind() {} func (*Value_StringValue) isValue_Kind() {}
func (*Value_BoolValue) isValue_Kind() {}
func (*Value_BoolValue) isValue_Kind() {}
func (*Value_StructValue) isValue_Kind() {} func (*Value_StructValue) isValue_Kind() {}
func (*Value_ListValue) isValue_Kind() {}
func (*Value_ListValue) isValue_Kind() {}
func (m *Value) GetKind() isValue_Kind { func (m *Value) GetKind() isValue_Kind {
if m != nil { if m != nil {
@ -358,7 +368,7 @@ func _Value_OneofSizer(msg proto.Message) (n int) {
// The JSON representation for `ListValue` is JSON array. // The JSON representation for `ListValue` is JSON array.
type ListValue struct { type ListValue struct {
// Repeated field of dynamically typed values. // Repeated field of dynamically typed values.
Values []*Value `protobuf:"bytes,1,rep,name=values" json:"values,omitempty"` Values []*Value `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`

View file

@ -100,12 +100,12 @@ type Timestamp struct {
// Represents seconds of UTC time since Unix epoch // Represents seconds of UTC time since Unix epoch
// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
// 9999-12-31T23:59:59Z inclusive. // 9999-12-31T23:59:59Z inclusive.
Seconds int64 `protobuf:"varint,1,opt,name=seconds" json:"seconds,omitempty"` Seconds int64 `protobuf:"varint,1,opt,name=seconds,proto3" json:"seconds,omitempty"`
// Non-negative fractions of a second at nanosecond resolution. Negative // Non-negative fractions of a second at nanosecond resolution. Negative
// second values with fractions must still have non-negative nanos values // second values with fractions must still have non-negative nanos values
// that count forward in time. Must be from 0 to 999,999,999 // that count forward in time. Must be from 0 to 999,999,999
// inclusive. // inclusive.
Nanos int32 `protobuf:"varint,2,opt,name=nanos" json:"nanos,omitempty"` Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`

View file

@ -1,14 +1,16 @@
package runtime package runtime
import ( import (
"context"
"encoding/base64"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"net/textproto"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"context"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
@ -19,7 +21,8 @@ import (
// parameters to or from a gRPC call. // parameters to or from a gRPC call.
const MetadataHeaderPrefix = "Grpc-Metadata-" const MetadataHeaderPrefix = "Grpc-Metadata-"
// MetadataPrefix is the prefix for grpc-gateway supplied custom metadata fields. // MetadataPrefix is prepended to permanent HTTP header keys (as specified
// by the IANA) when added to the gRPC context.
const MetadataPrefix = "grpcgateway-" const MetadataPrefix = "grpcgateway-"
// MetadataTrailerPrefix is prepended to gRPC metadata as it is converted to // MetadataTrailerPrefix is prepended to gRPC metadata as it is converted to
@ -27,6 +30,7 @@ const MetadataPrefix = "grpcgateway-"
const MetadataTrailerPrefix = "Grpc-Trailer-" const MetadataTrailerPrefix = "Grpc-Trailer-"
const metadataGrpcTimeout = "Grpc-Timeout" const metadataGrpcTimeout = "Grpc-Timeout"
const metadataHeaderBinarySuffix = "-Bin"
const xForwardedFor = "X-Forwarded-For" const xForwardedFor = "X-Forwarded-For"
const xForwardedHost = "X-Forwarded-Host" const xForwardedHost = "X-Forwarded-Host"
@ -37,6 +41,14 @@ var (
DefaultContextTimeout = 0 * time.Second DefaultContextTimeout = 0 * time.Second
) )
func decodeBinHeader(v string) ([]byte, error) {
if len(v)%4 == 0 {
// Input was padded, or padding was not necessary.
return base64.StdEncoding.DecodeString(v)
}
return base64.RawStdEncoding.DecodeString(v)
}
/* /*
AnnotateContext adds context information such as metadata from the request. AnnotateContext adds context information such as metadata from the request.
@ -57,11 +69,22 @@ func AnnotateContext(ctx context.Context, mux *ServeMux, req *http.Request) (con
for key, vals := range req.Header { for key, vals := range req.Header {
for _, val := range vals { for _, val := range vals {
key = textproto.CanonicalMIMEHeaderKey(key)
// For backwards-compatibility, pass through 'authorization' header with no prefix. // For backwards-compatibility, pass through 'authorization' header with no prefix.
if strings.ToLower(key) == "authorization" { if key == "Authorization" {
pairs = append(pairs, "authorization", val) pairs = append(pairs, "authorization", val)
} }
if h, ok := mux.incomingHeaderMatcher(key); ok { if h, ok := mux.incomingHeaderMatcher(key); ok {
// Handles "-bin" metadata in grpc, since grpc will do another base64
// encode before sending to server, we need to decode it first.
if strings.HasSuffix(key, metadataHeaderBinarySuffix) {
b, err := decodeBinHeader(val)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid binary header %s: %s", key, err)
}
val = string(b)
}
pairs = append(pairs, h, val) pairs = append(pairs, h, val)
} }
} }
@ -80,7 +103,7 @@ func AnnotateContext(ctx context.Context, mux *ServeMux, req *http.Request) (con
pairs = append(pairs, strings.ToLower(xForwardedFor), fmt.Sprintf("%s, %s", fwd, remoteIP)) pairs = append(pairs, strings.ToLower(xForwardedFor), fmt.Sprintf("%s, %s", fwd, remoteIP))
} }
} else { } else {
grpclog.Printf("invalid remote addr: %s", addr) grpclog.Infof("invalid remote addr: %s", addr)
} }
} }

View file

@ -2,7 +2,9 @@ package runtime
import ( import (
"encoding/base64" "encoding/base64"
"fmt"
"strconv" "strconv"
"strings"
"github.com/golang/protobuf/jsonpb" "github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/ptypes/duration" "github.com/golang/protobuf/ptypes/duration"
@ -15,16 +17,52 @@ func String(val string) (string, error) {
return val, nil return val, nil
} }
// StringSlice converts 'val' where individual strings are separated by
// 'sep' into a string slice.
func StringSlice(val, sep string) ([]string, error) {
return strings.Split(val, sep), nil
}
// Bool converts the given string representation of a boolean value into bool. // Bool converts the given string representation of a boolean value into bool.
func Bool(val string) (bool, error) { func Bool(val string) (bool, error) {
return strconv.ParseBool(val) return strconv.ParseBool(val)
} }
// BoolSlice converts 'val' where individual booleans are separated by
// 'sep' into a bool slice.
func BoolSlice(val, sep string) ([]bool, error) {
s := strings.Split(val, sep)
values := make([]bool, len(s))
for i, v := range s {
value, err := Bool(v)
if err != nil {
return values, err
}
values[i] = value
}
return values, nil
}
// Float64 converts the given string representation into representation of a floating point number into float64. // Float64 converts the given string representation into representation of a floating point number into float64.
func Float64(val string) (float64, error) { func Float64(val string) (float64, error) {
return strconv.ParseFloat(val, 64) return strconv.ParseFloat(val, 64)
} }
// Float64Slice converts 'val' where individual floating point numbers are separated by
// 'sep' into a float64 slice.
func Float64Slice(val, sep string) ([]float64, error) {
s := strings.Split(val, sep)
values := make([]float64, len(s))
for i, v := range s {
value, err := Float64(v)
if err != nil {
return values, err
}
values[i] = value
}
return values, nil
}
// Float32 converts the given string representation of a floating point number into float32. // Float32 converts the given string representation of a floating point number into float32.
func Float32(val string) (float32, error) { func Float32(val string) (float32, error) {
f, err := strconv.ParseFloat(val, 32) f, err := strconv.ParseFloat(val, 32)
@ -34,11 +72,41 @@ func Float32(val string) (float32, error) {
return float32(f), nil return float32(f), nil
} }
// Float32Slice converts 'val' where individual floating point numbers are separated by
// 'sep' into a float32 slice.
func Float32Slice(val, sep string) ([]float32, error) {
s := strings.Split(val, sep)
values := make([]float32, len(s))
for i, v := range s {
value, err := Float32(v)
if err != nil {
return values, err
}
values[i] = value
}
return values, nil
}
// Int64 converts the given string representation of an integer into int64. // Int64 converts the given string representation of an integer into int64.
func Int64(val string) (int64, error) { func Int64(val string) (int64, error) {
return strconv.ParseInt(val, 0, 64) return strconv.ParseInt(val, 0, 64)
} }
// Int64Slice converts 'val' where individual integers are separated by
// 'sep' into a int64 slice.
func Int64Slice(val, sep string) ([]int64, error) {
s := strings.Split(val, sep)
values := make([]int64, len(s))
for i, v := range s {
value, err := Int64(v)
if err != nil {
return values, err
}
values[i] = value
}
return values, nil
}
// Int32 converts the given string representation of an integer into int32. // Int32 converts the given string representation of an integer into int32.
func Int32(val string) (int32, error) { func Int32(val string) (int32, error) {
i, err := strconv.ParseInt(val, 0, 32) i, err := strconv.ParseInt(val, 0, 32)
@ -48,11 +116,41 @@ func Int32(val string) (int32, error) {
return int32(i), nil return int32(i), nil
} }
// Int32Slice converts 'val' where individual integers are separated by
// 'sep' into a int32 slice.
func Int32Slice(val, sep string) ([]int32, error) {
s := strings.Split(val, sep)
values := make([]int32, len(s))
for i, v := range s {
value, err := Int32(v)
if err != nil {
return values, err
}
values[i] = value
}
return values, nil
}
// Uint64 converts the given string representation of an integer into uint64. // Uint64 converts the given string representation of an integer into uint64.
func Uint64(val string) (uint64, error) { func Uint64(val string) (uint64, error) {
return strconv.ParseUint(val, 0, 64) return strconv.ParseUint(val, 0, 64)
} }
// Uint64Slice converts 'val' where individual integers are separated by
// 'sep' into a uint64 slice.
func Uint64Slice(val, sep string) ([]uint64, error) {
s := strings.Split(val, sep)
values := make([]uint64, len(s))
for i, v := range s {
value, err := Uint64(v)
if err != nil {
return values, err
}
values[i] = value
}
return values, nil
}
// Uint32 converts the given string representation of an integer into uint32. // Uint32 converts the given string representation of an integer into uint32.
func Uint32(val string) (uint32, error) { func Uint32(val string) (uint32, error) {
i, err := strconv.ParseUint(val, 0, 32) i, err := strconv.ParseUint(val, 0, 32)
@ -62,16 +160,49 @@ func Uint32(val string) (uint32, error) {
return uint32(i), nil return uint32(i), nil
} }
// Uint32Slice converts 'val' where individual integers are separated by
// 'sep' into a uint32 slice.
func Uint32Slice(val, sep string) ([]uint32, error) {
s := strings.Split(val, sep)
values := make([]uint32, len(s))
for i, v := range s {
value, err := Uint32(v)
if err != nil {
return values, err
}
values[i] = value
}
return values, nil
}
// Bytes converts the given string representation of a byte sequence into a slice of bytes // Bytes converts the given string representation of a byte sequence into a slice of bytes
// A bytes sequence is encoded in URL-safe base64 without padding // A bytes sequence is encoded in URL-safe base64 without padding
func Bytes(val string) ([]byte, error) { func Bytes(val string) ([]byte, error) {
b, err := base64.StdEncoding.DecodeString(val) b, err := base64.StdEncoding.DecodeString(val)
if err != nil { if err != nil {
return nil, err b, err = base64.URLEncoding.DecodeString(val)
if err != nil {
return nil, err
}
} }
return b, nil return b, nil
} }
// BytesSlice converts 'val' where individual bytes sequences, encoded in URL-safe
// base64 without padding, are separated by 'sep' into a slice of bytes slices slice.
func BytesSlice(val, sep string) ([][]byte, error) {
s := strings.Split(val, sep)
values := make([][]byte, len(s))
for i, v := range s {
value, err := Bytes(v)
if err != nil {
return values, err
}
values[i] = value
}
return values, nil
}
// Timestamp converts the given RFC3339 formatted string into a timestamp.Timestamp. // Timestamp converts the given RFC3339 formatted string into a timestamp.Timestamp.
func Timestamp(val string) (*timestamp.Timestamp, error) { func Timestamp(val string) (*timestamp.Timestamp, error) {
var r *timestamp.Timestamp var r *timestamp.Timestamp
@ -85,3 +216,39 @@ func Duration(val string) (*duration.Duration, error) {
err := jsonpb.UnmarshalString(val, r) err := jsonpb.UnmarshalString(val, r)
return r, err return r, err
} }
// Enum converts the given string into an int32 that should be type casted into the
// correct enum proto type.
func Enum(val string, enumValMap map[string]int32) (int32, error) {
e, ok := enumValMap[val]
if ok {
return e, nil
}
i, err := Int32(val)
if err != nil {
return 0, fmt.Errorf("%s is not valid", val)
}
for _, v := range enumValMap {
if v == i {
return i, nil
}
}
return 0, fmt.Errorf("%s is not valid", val)
}
// EnumSlice converts 'val' where individual enums are separated by 'sep'
// into a int32 slice. Each individual int32 should be type casted into the
// correct enum proto type.
func EnumSlice(val, sep string, enumValMap map[string]int32) ([]int32, error) {
s := strings.Split(val, sep)
values := make([]int32, len(s))
for i, v := range s {
value, err := Enum(v, enumValMap)
if err != nil {
return values, err
}
values[i] = value
}
return values, nil
}

View file

@ -6,7 +6,6 @@ import (
"net/http" "net/http"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
"github.com/golang/protobuf/ptypes/any" "github.com/golang/protobuf/ptypes/any"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
@ -53,7 +52,7 @@ func HTTPStatusFromCode(code codes.Code) int {
return http.StatusInternalServerError return http.StatusInternalServerError
} }
grpclog.Printf("Unknown gRPC error code: %v", code) grpclog.Infof("Unknown gRPC error code: %v", code)
return http.StatusInternalServerError return http.StatusInternalServerError
} }
@ -67,6 +66,10 @@ var (
type errorBody struct { type errorBody struct {
Error string `protobuf:"bytes,1,name=error" json:"error"` Error string `protobuf:"bytes,1,name=error" json:"error"`
// This is to make the error more compatible with users that expect errors to be Status objects:
// https://github.com/grpc/grpc/blob/master/src/proto/grpc/status/status.proto
// It should be the exact same message as the Error field.
Message string `protobuf:"bytes,1,name=message" json:"message"`
Code int32 `protobuf:"varint,2,name=code" json:"code"` Code int32 `protobuf:"varint,2,name=code" json:"code"`
Details []*any.Any `protobuf:"bytes,3,rep,name=details" json:"details,omitempty"` Details []*any.Any `protobuf:"bytes,3,rep,name=details" json:"details,omitempty"`
} }
@ -94,34 +97,25 @@ func DefaultHTTPError(ctx context.Context, mux *ServeMux, marshaler Marshaler, w
} }
body := &errorBody{ body := &errorBody{
Error: s.Message(), Error: s.Message(),
Code: int32(s.Code()), Message: s.Message(),
} Code: int32(s.Code()),
Details: s.Proto().GetDetails(),
for _, detail := range s.Details() {
if det, ok := detail.(proto.Message); ok {
a, err := ptypes.MarshalAny(det)
if err != nil {
grpclog.Printf("Failed to marshal any: %v", err)
} else {
body.Details = append(body.Details, a)
}
}
} }
buf, merr := marshaler.Marshal(body) buf, merr := marshaler.Marshal(body)
if merr != nil { if merr != nil {
grpclog.Printf("Failed to marshal error message %q: %v", body, merr) grpclog.Infof("Failed to marshal error message %q: %v", body, merr)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
if _, err := io.WriteString(w, fallback); err != nil { if _, err := io.WriteString(w, fallback); err != nil {
grpclog.Printf("Failed to write response: %v", err) grpclog.Infof("Failed to write response: %v", err)
} }
return return
} }
md, ok := ServerMetadataFromContext(ctx) md, ok := ServerMetadataFromContext(ctx)
if !ok { if !ok {
grpclog.Printf("Failed to extract ServerMetadata from context") grpclog.Infof("Failed to extract ServerMetadata from context")
} }
handleForwardResponseServerMetadata(w, mux, md) handleForwardResponseServerMetadata(w, mux, md)
@ -129,7 +123,7 @@ func DefaultHTTPError(ctx context.Context, mux *ServeMux, marshaler Marshaler, w
st := HTTPStatusFromCode(s.Code()) st := HTTPStatusFromCode(s.Code())
w.WriteHeader(st) w.WriteHeader(st)
if _, err := w.Write(buf); err != nil { if _, err := w.Write(buf); err != nil {
grpclog.Printf("Failed to write response: %v", err) grpclog.Infof("Failed to write response: %v", err)
} }
handleForwardResponseTrailer(w, md) handleForwardResponseTrailer(w, md)

View file

@ -19,14 +19,14 @@ import (
func ForwardResponseStream(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, req *http.Request, recv func() (proto.Message, error), opts ...func(context.Context, http.ResponseWriter, proto.Message) error) { func ForwardResponseStream(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, req *http.Request, recv func() (proto.Message, error), opts ...func(context.Context, http.ResponseWriter, proto.Message) error) {
f, ok := w.(http.Flusher) f, ok := w.(http.Flusher)
if !ok { if !ok {
grpclog.Printf("Flush not supported in %T", w) grpclog.Infof("Flush not supported in %T", w)
http.Error(w, "unexpected type of web server", http.StatusInternalServerError) http.Error(w, "unexpected type of web server", http.StatusInternalServerError)
return return
} }
md, ok := ServerMetadataFromContext(ctx) md, ok := ServerMetadataFromContext(ctx)
if !ok { if !ok {
grpclog.Printf("Failed to extract ServerMetadata from context") grpclog.Infof("Failed to extract ServerMetadata from context")
http.Error(w, "unexpected error", http.StatusInternalServerError) http.Error(w, "unexpected error", http.StatusInternalServerError)
return return
} }
@ -63,17 +63,17 @@ func ForwardResponseStream(ctx context.Context, mux *ServeMux, marshaler Marshal
buf, err := marshaler.Marshal(streamChunk(resp, nil)) buf, err := marshaler.Marshal(streamChunk(resp, nil))
if err != nil { if err != nil {
grpclog.Printf("Failed to marshal response chunk: %v", err) grpclog.Infof("Failed to marshal response chunk: %v", err)
handleForwardResponseStreamError(wroteHeader, marshaler, w, err) handleForwardResponseStreamError(wroteHeader, marshaler, w, err)
return return
} }
if _, err = w.Write(buf); err != nil { if _, err = w.Write(buf); err != nil {
grpclog.Printf("Failed to send response chunk: %v", err) grpclog.Infof("Failed to send response chunk: %v", err)
return return
} }
wroteHeader = true wroteHeader = true
if _, err = w.Write(delimiter); err != nil { if _, err = w.Write(delimiter); err != nil {
grpclog.Printf("Failed to send delimiter chunk: %v", err) grpclog.Infof("Failed to send delimiter chunk: %v", err)
return return
} }
f.Flush() f.Flush()
@ -106,11 +106,17 @@ func handleForwardResponseTrailer(w http.ResponseWriter, md ServerMetadata) {
} }
} }
// responseBody interface contains method for getting field for marshaling to the response body
// this method is generated for response struct from the value of `response_body` in the `google.api.HttpRule`
type responseBody interface {
XXX_ResponseBody() interface{}
}
// ForwardResponseMessage forwards the message "resp" from gRPC server to REST client. // ForwardResponseMessage forwards the message "resp" from gRPC server to REST client.
func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, req *http.Request, resp proto.Message, opts ...func(context.Context, http.ResponseWriter, proto.Message) error) { func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, req *http.Request, resp proto.Message, opts ...func(context.Context, http.ResponseWriter, proto.Message) error) {
md, ok := ServerMetadataFromContext(ctx) md, ok := ServerMetadataFromContext(ctx)
if !ok { if !ok {
grpclog.Printf("Failed to extract ServerMetadata from context") grpclog.Infof("Failed to extract ServerMetadata from context")
} }
handleForwardResponseServerMetadata(w, mux, md) handleForwardResponseServerMetadata(w, mux, md)
@ -120,16 +126,21 @@ func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marsha
HTTPError(ctx, mux, marshaler, w, req, err) HTTPError(ctx, mux, marshaler, w, req, err)
return return
} }
var buf []byte
buf, err := marshaler.Marshal(resp) var err error
if rb, ok := resp.(responseBody); ok {
buf, err = marshaler.Marshal(rb.XXX_ResponseBody())
} else {
buf, err = marshaler.Marshal(resp)
}
if err != nil { if err != nil {
grpclog.Printf("Marshal error: %v", err) grpclog.Infof("Marshal error: %v", err)
HTTPError(ctx, mux, marshaler, w, req, err) HTTPError(ctx, mux, marshaler, w, req, err)
return return
} }
if _, err = w.Write(buf); err != nil { if _, err = w.Write(buf); err != nil {
grpclog.Printf("Failed to write response: %v", err) grpclog.Infof("Failed to write response: %v", err)
} }
handleForwardResponseTrailer(w, md) handleForwardResponseTrailer(w, md)
@ -141,7 +152,7 @@ func handleForwardResponseOptions(ctx context.Context, w http.ResponseWriter, re
} }
for _, opt := range opts { for _, opt := range opts {
if err := opt(ctx, w, resp); err != nil { if err := opt(ctx, w, resp); err != nil {
grpclog.Printf("Error handling ForwardResponseOptions: %v", err) grpclog.Infof("Error handling ForwardResponseOptions: %v", err)
return err return err
} }
} }
@ -151,7 +162,7 @@ func handleForwardResponseOptions(ctx context.Context, w http.ResponseWriter, re
func handleForwardResponseStreamError(wroteHeader bool, marshaler Marshaler, w http.ResponseWriter, err error) { func handleForwardResponseStreamError(wroteHeader bool, marshaler Marshaler, w http.ResponseWriter, err error) {
buf, merr := marshaler.Marshal(streamChunk(nil, err)) buf, merr := marshaler.Marshal(streamChunk(nil, err))
if merr != nil { if merr != nil {
grpclog.Printf("Failed to marshal an error: %v", merr) grpclog.Infof("Failed to marshal an error: %v", merr)
return return
} }
if !wroteHeader { if !wroteHeader {
@ -162,7 +173,7 @@ func handleForwardResponseStreamError(wroteHeader bool, marshaler Marshaler, w h
w.WriteHeader(HTTPStatusFromCode(s.Code())) w.WriteHeader(HTTPStatusFromCode(s.Code()))
} }
if _, werr := w.Write(buf); werr != nil { if _, werr := w.Write(buf); werr != nil {
grpclog.Printf("Failed to notify error to client: %v", werr) grpclog.Infof("Failed to notify error to client: %v", werr)
return return
} }
} }

View file

@ -22,11 +22,11 @@ const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// StreamError is a response type which is returned when // StreamError is a response type which is returned when
// streaming rpc returns an error. // streaming rpc returns an error.
type StreamError struct { type StreamError struct {
GrpcCode int32 `protobuf:"varint,1,opt,name=grpc_code,json=grpcCode" json:"grpc_code,omitempty"` GrpcCode int32 `protobuf:"varint,1,opt,name=grpc_code,json=grpcCode,proto3" json:"grpc_code,omitempty"`
HttpCode int32 `protobuf:"varint,2,opt,name=http_code,json=httpCode" json:"http_code,omitempty"` HttpCode int32 `protobuf:"varint,2,opt,name=http_code,json=httpCode,proto3" json:"http_code,omitempty"`
Message string `protobuf:"bytes,3,opt,name=message" json:"message,omitempty"` Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
HttpStatus string `protobuf:"bytes,4,opt,name=http_status,json=httpStatus" json:"http_status,omitempty"` HttpStatus string `protobuf:"bytes,4,opt,name=http_status,json=httpStatus,proto3" json:"http_status,omitempty"`
Details []*any.Any `protobuf:"bytes,5,rep,name=details" json:"details,omitempty"` Details []*any.Any `protobuf:"bytes,5,rep,name=details,proto3" json:"details,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`

View file

@ -3,10 +3,9 @@ package runtime
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/textproto"
"strings" "strings"
"context" "context"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
@ -51,7 +50,6 @@ type HeaderMatcherFunc func(string) (string, bool)
// keys (as specified by the IANA) to gRPC context with grpcgateway- prefix. HTTP headers that start with // keys (as specified by the IANA) to gRPC context with grpcgateway- prefix. HTTP headers that start with
// 'Grpc-Metadata-' are mapped to gRPC metadata after removing prefix 'Grpc-Metadata-'. // 'Grpc-Metadata-' are mapped to gRPC metadata after removing prefix 'Grpc-Metadata-'.
func DefaultHeaderMatcher(key string) (string, bool) { func DefaultHeaderMatcher(key string) (string, bool) {
key = textproto.CanonicalMIMEHeaderKey(key)
if isPermanentHTTPHeader(key) { if isPermanentHTTPHeader(key) {
return MetadataPrefix + key, true return MetadataPrefix + key, true
} else if strings.HasPrefix(key, MetadataHeaderPrefix) { } else if strings.HasPrefix(key, MetadataHeaderPrefix) {

View file

@ -44,13 +44,13 @@ type Pattern struct {
// It returns an error if the given definition is invalid. // It returns an error if the given definition is invalid.
func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, error) { func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, error) {
if version != 1 { if version != 1 {
grpclog.Printf("unsupported version: %d", version) grpclog.Infof("unsupported version: %d", version)
return Pattern{}, ErrInvalidPattern return Pattern{}, ErrInvalidPattern
} }
l := len(ops) l := len(ops)
if l%2 != 0 { if l%2 != 0 {
grpclog.Printf("odd number of ops codes: %d", l) grpclog.Infof("odd number of ops codes: %d", l)
return Pattern{}, ErrInvalidPattern return Pattern{}, ErrInvalidPattern
} }
@ -73,14 +73,14 @@ func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, er
stack++ stack++
case utilities.OpPushM: case utilities.OpPushM:
if pushMSeen { if pushMSeen {
grpclog.Printf("pushM appears twice") grpclog.Infof("pushM appears twice")
return Pattern{}, ErrInvalidPattern return Pattern{}, ErrInvalidPattern
} }
pushMSeen = true pushMSeen = true
stack++ stack++
case utilities.OpLitPush: case utilities.OpLitPush:
if op.operand < 0 || len(pool) <= op.operand { if op.operand < 0 || len(pool) <= op.operand {
grpclog.Printf("negative literal index: %d", op.operand) grpclog.Infof("negative literal index: %d", op.operand)
return Pattern{}, ErrInvalidPattern return Pattern{}, ErrInvalidPattern
} }
if pushMSeen { if pushMSeen {
@ -89,7 +89,7 @@ func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, er
stack++ stack++
case utilities.OpConcatN: case utilities.OpConcatN:
if op.operand <= 0 { if op.operand <= 0 {
grpclog.Printf("negative concat size: %d", op.operand) grpclog.Infof("negative concat size: %d", op.operand)
return Pattern{}, ErrInvalidPattern return Pattern{}, ErrInvalidPattern
} }
stack -= op.operand stack -= op.operand
@ -100,7 +100,7 @@ func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, er
stack++ stack++
case utilities.OpCapture: case utilities.OpCapture:
if op.operand < 0 || len(pool) <= op.operand { if op.operand < 0 || len(pool) <= op.operand {
grpclog.Printf("variable name index out of bound: %d", op.operand) grpclog.Infof("variable name index out of bound: %d", op.operand)
return Pattern{}, ErrInvalidPattern return Pattern{}, ErrInvalidPattern
} }
v := pool[op.operand] v := pool[op.operand]
@ -108,11 +108,11 @@ func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, er
vars = append(vars, v) vars = append(vars, v)
stack-- stack--
if stack < 0 { if stack < 0 {
grpclog.Printf("stack underflow") grpclog.Infof("stack underflow")
return Pattern{}, ErrInvalidPattern return Pattern{}, ErrInvalidPattern
} }
default: default:
grpclog.Printf("invalid opcode: %d", op.code) grpclog.Infof("invalid opcode: %d", op.code)
return Pattern{}, ErrInvalidPattern return Pattern{}, ErrInvalidPattern
} }
@ -144,7 +144,16 @@ func MustPattern(p Pattern, err error) Pattern {
// If otherwise, the function returns an error. // If otherwise, the function returns an error.
func (p Pattern) Match(components []string, verb string) (map[string]string, error) { func (p Pattern) Match(components []string, verb string) (map[string]string, error) {
if p.verb != verb { if p.verb != verb {
return nil, ErrNotMatch if p.verb != "" {
return nil, ErrNotMatch
}
if len(components) == 0 {
components = []string{":" + verb}
} else {
components = append([]string{}, components...)
components[len(components)-1] += ":" + verb
}
verb = ""
} }
var pos int var pos int

View file

@ -36,17 +36,17 @@ func DefaultHTTPProtoErrorHandler(ctx context.Context, mux *ServeMux, marshaler
buf, merr := marshaler.Marshal(s.Proto()) buf, merr := marshaler.Marshal(s.Proto())
if merr != nil { if merr != nil {
grpclog.Printf("Failed to marshal error message %q: %v", s.Proto(), merr) grpclog.Infof("Failed to marshal error message %q: %v", s.Proto(), merr)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
if _, err := io.WriteString(w, fallback); err != nil { if _, err := io.WriteString(w, fallback); err != nil {
grpclog.Printf("Failed to write response: %v", err) grpclog.Infof("Failed to write response: %v", err)
} }
return return
} }
md, ok := ServerMetadataFromContext(ctx) md, ok := ServerMetadataFromContext(ctx)
if !ok { if !ok {
grpclog.Printf("Failed to extract ServerMetadata from context") grpclog.Infof("Failed to extract ServerMetadata from context")
} }
handleForwardResponseServerMetadata(w, mux, md) handleForwardResponseServerMetadata(w, mux, md)
@ -54,7 +54,7 @@ func DefaultHTTPProtoErrorHandler(ctx context.Context, mux *ServeMux, marshaler
st := HTTPStatusFromCode(s.Code()) st := HTTPStatusFromCode(s.Code())
w.WriteHeader(st) w.WriteHeader(st)
if _, err := w.Write(buf); err != nil { if _, err := w.Write(buf); err != nil {
grpclog.Printf("Failed to write response: %v", err) grpclog.Infof("Failed to write response: %v", err)
} }
handleForwardResponseTrailer(w, md) handleForwardResponseTrailer(w, md)

View file

@ -64,7 +64,7 @@ func populateFieldValueFromPath(msg proto.Message, fieldPath []string, values []
if err != nil { if err != nil {
return err return err
} else if !f.IsValid() { } else if !f.IsValid() {
grpclog.Printf("field not found in %T: %s", msg, strings.Join(fieldPath, ".")) grpclog.Infof("field not found in %T: %s", msg, strings.Join(fieldPath, "."))
return nil return nil
} }
@ -108,7 +108,7 @@ func populateFieldValueFromPath(msg proto.Message, fieldPath []string, values []
return fmt.Errorf("no value of field: %s", strings.Join(fieldPath, ".")) return fmt.Errorf("no value of field: %s", strings.Join(fieldPath, "."))
case 1: case 1:
default: default:
grpclog.Printf("too many field values: %s", strings.Join(fieldPath, ".")) grpclog.Infof("too many field values: %s", strings.Join(fieldPath, "."))
} }
return populateField(m, values[0], props) return populateField(m, values[0], props)
} }
@ -221,6 +221,23 @@ func populateField(f reflect.Value, value string, props *proto.Properties) error
f.Field(0).SetInt(int64(t.Unix())) f.Field(0).SetInt(int64(t.Unix()))
f.Field(1).SetInt(int64(t.Nanosecond())) f.Field(1).SetInt(int64(t.Nanosecond()))
return nil return nil
case "Duration":
if value == "null" {
f.Field(0).SetInt(0)
f.Field(1).SetInt(0)
return nil
}
d, err := time.ParseDuration(value)
if err != nil {
return fmt.Errorf("bad Duration: %v", err)
}
ns := d.Nanoseconds()
s := ns / 1e9
ns %= 1e9
f.Field(0).SetInt(s)
f.Field(1).SetInt(ns)
return nil
case "DoubleValue": case "DoubleValue":
fallthrough fallthrough
case "FloatValue": case "FloatValue":
@ -284,6 +301,24 @@ func populateField(f reflect.Value, value string, props *proto.Properties) error
} }
} }
// Handle Time and Duration stdlib types
switch t := i.(type) {
case *time.Time:
pt, err := time.Parse(time.RFC3339Nano, value)
if err != nil {
return fmt.Errorf("bad Timestamp: %v", err)
}
*t = pt
return nil
case *time.Duration:
d, err := time.ParseDuration(value)
if err != nil {
return fmt.Errorf("bad Duration: %v", err)
}
*t = d
return nil
}
// is the destination field an enumeration type? // is the destination field an enumeration type?
if enumValMap := proto.EnumValueMap(props.Enum); enumValMap != nil { if enumValMap := proto.EnumValueMap(props.Enum); enumValMap != nil {
return populateFieldEnum(f, value, enumValMap) return populateFieldEnum(f, value, enumValMap)
@ -291,7 +326,7 @@ func populateField(f reflect.Value, value string, props *proto.Properties) error
conv, ok := convFromType[f.Kind()] conv, ok := convFromType[f.Kind()]
if !ok { if !ok {
return fmt.Errorf("unsupported field type %T", f) return fmt.Errorf("field type %T is not supported in query parameters", i)
} }
result := conv.Call([]reflect.Value{reflect.ValueOf(value)}) result := conv.Call([]reflect.Value{reflect.ValueOf(value)})
if err := result[1].Interface(); err != nil { if err := result[1].Interface(); err != nil {

View file

@ -61,6 +61,39 @@ type Collector interface {
Collect(chan<- Metric) Collect(chan<- Metric)
} }
// DescribeByCollect is a helper to implement the Describe method of a custom
// Collector. It collects the metrics from the provided Collector and sends
// their descriptors to the provided channel.
//
// If a Collector collects the same metrics throughout its lifetime, its
// Describe method can simply be implemented as:
//
// func (c customCollector) Describe(ch chan<- *Desc) {
// DescribeByCollect(c, ch)
// }
//
// However, this will not work if the metrics collected change dynamically over
// the lifetime of the Collector in a way that their combined set of descriptors
// changes as well. The shortcut implementation will then violate the contract
// of the Describe method. If a Collector sometimes collects no metrics at all
// (for example vectors like CounterVec, GaugeVec, etc., which only collect
// metrics after a metric with a fully specified label set has been accessed),
// it might even get registered as an unchecked Collecter (cf. the Register
// method of the Registerer interface). Hence, only use this shortcut
// implementation of Describe if you are certain to fulfill the contract.
//
// The Collector example demonstrates a use of DescribeByCollect.
func DescribeByCollect(c Collector, descs chan<- *Desc) {
metrics := make(chan Metric)
go func() {
c.Collect(metrics)
close(metrics)
}()
for m := range metrics {
descs <- m.Desc()
}
}
// selfCollector implements Collector for a single Metric so that the Metric // selfCollector implements Collector for a single Metric so that the Metric
// collects itself. Add it as an anonymous field to a struct that implements // collects itself. Add it as an anonymous field to a struct that implements
// Metric, and call init with the Metric itself as an argument. // Metric, and call init with the Metric itself as an argument.

View file

@ -67,7 +67,7 @@ type Desc struct {
// NewDesc allocates and initializes a new Desc. Errors are recorded in the Desc // NewDesc allocates and initializes a new Desc. Errors are recorded in the Desc
// and will be reported on registration time. variableLabels and constLabels can // and will be reported on registration time. variableLabels and constLabels can
// be nil if no such labels should be set. fqName and help must not be empty. // be nil if no such labels should be set. fqName must not be empty.
// //
// variableLabels only contain the label names. Their label values are variable // variableLabels only contain the label names. Their label values are variable
// and therefore not part of the Desc. (They are managed within the Metric.) // and therefore not part of the Desc. (They are managed within the Metric.)
@ -80,10 +80,6 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
help: help, help: help,
variableLabels: variableLabels, variableLabels: variableLabels,
} }
if help == "" {
d.err = errors.New("empty help string")
return d
}
if !model.IsValidMetricName(model.LabelValue(fqName)) { if !model.IsValidMetricName(model.LabelValue(fqName)) {
d.err = fmt.Errorf("%q is not a valid metric name", fqName) d.err = fmt.Errorf("%q is not a valid metric name", fqName)
return d return d
@ -156,7 +152,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
Value: proto.String(v), Value: proto.String(v),
}) })
} }
sort.Sort(LabelPairSorter(d.constLabelPairs)) sort.Sort(labelPairSorter(d.constLabelPairs))
return d return d
} }

View file

@ -1,3 +1,16 @@
// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prometheus package prometheus
// Inline and byte-free variant of hash/fnv's fnv64a. // Inline and byte-free variant of hash/fnv's fnv64a.

View file

@ -1,3 +1,16 @@
// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prometheus package prometheus
import ( import (

View file

@ -16,7 +16,9 @@ package prometheus
import ( import (
"fmt" "fmt"
"math" "math"
"runtime"
"sort" "sort"
"sync"
"sync/atomic" "sync/atomic"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
@ -108,8 +110,9 @@ func ExponentialBuckets(start, factor float64, count int) []float64 {
} }
// HistogramOpts bundles the options for creating a Histogram metric. It is // HistogramOpts bundles the options for creating a Histogram metric. It is
// mandatory to set Name and Help to a non-empty string. All other fields are // mandatory to set Name to a non-empty string. All other fields are optional
// optional and can safely be left at their zero value. // and can safely be left at their zero value, although it is strongly
// encouraged to set a Help string.
type HistogramOpts struct { type HistogramOpts struct {
// Namespace, Subsystem, and Name are components of the fully-qualified // Namespace, Subsystem, and Name are components of the fully-qualified
// name of the Histogram (created by joining these components with // name of the Histogram (created by joining these components with
@ -120,7 +123,7 @@ type HistogramOpts struct {
Subsystem string Subsystem string
Name string Name string
// Help provides information about this Histogram. Mandatory! // Help provides information about this Histogram.
// //
// Metrics with the same fully-qualified name must have the same Help // Metrics with the same fully-qualified name must have the same Help
// string. // string.
@ -200,28 +203,49 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
} }
} }
} }
// Finally we know the final length of h.upperBounds and can make counts. // Finally we know the final length of h.upperBounds and can make counts
h.counts = make([]uint64, len(h.upperBounds)) // for both states:
h.counts[0].buckets = make([]uint64, len(h.upperBounds))
h.counts[1].buckets = make([]uint64, len(h.upperBounds))
h.init(h) // Init self-collection. h.init(h) // Init self-collection.
return h return h
} }
type histogram struct { type histogramCounts struct {
// sumBits contains the bits of the float64 representing the sum of all // sumBits contains the bits of the float64 representing the sum of all
// observations. sumBits and count have to go first in the struct to // observations. sumBits and count have to go first in the struct to
// guarantee alignment for atomic operations. // guarantee alignment for atomic operations.
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG // http://golang.org/pkg/sync/atomic/#pkg-note-BUG
sumBits uint64 sumBits uint64
count uint64 count uint64
buckets []uint64
}
type histogram struct {
selfCollector selfCollector
// Note that there is no mutex required. desc *Desc
writeMtx sync.Mutex // Only used in the Write method.
desc *Desc
upperBounds []float64 upperBounds []float64
counts []uint64
// Two counts, one is "hot" for lock-free observations, the other is
// "cold" for writing out a dto.Metric.
counts [2]histogramCounts
hotIdx int // Index of currently-hot counts. Only used within Write.
// This is a complicated one. For lock-free yet atomic observations, we
// need to save the total count of observations again, combined with the
// index of the currently-hot counts struct, so that we can perform the
// operation on both values atomically. The least significant bit
// defines the hot counts struct. The remaining 63 bits represent the
// total count of observations. This happens under the assumption that
// the 63bit count will never overflow. Rationale: An observations takes
// about 30ns. Let's assume it could happen in 10ns. Overflowing the
// counter will then take at least (2^63)*10ns, which is about 3000
// years.
countAndHotIdx uint64
labelPairs []*dto.LabelPair labelPairs []*dto.LabelPair
} }
@ -241,36 +265,113 @@ func (h *histogram) Observe(v float64) {
// 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op // 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op
// 300 buckets: 154 ns/op linear - binary 61.6 ns/op // 300 buckets: 154 ns/op linear - binary 61.6 ns/op
i := sort.SearchFloat64s(h.upperBounds, v) i := sort.SearchFloat64s(h.upperBounds, v)
if i < len(h.counts) {
atomic.AddUint64(&h.counts[i], 1) // We increment h.countAndHotIdx by 2 so that the counter in the upper
// 63 bits gets incremented by 1. At the same time, we get the new value
// back, which we can use to find the currently-hot counts.
n := atomic.AddUint64(&h.countAndHotIdx, 2)
hotCounts := &h.counts[n%2]
if i < len(h.upperBounds) {
atomic.AddUint64(&hotCounts.buckets[i], 1)
} }
atomic.AddUint64(&h.count, 1)
for { for {
oldBits := atomic.LoadUint64(&h.sumBits) oldBits := atomic.LoadUint64(&hotCounts.sumBits)
newBits := math.Float64bits(math.Float64frombits(oldBits) + v) newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
if atomic.CompareAndSwapUint64(&h.sumBits, oldBits, newBits) { if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
break break
} }
} }
// Increment count last as we take it as a signal that the observation
// is complete.
atomic.AddUint64(&hotCounts.count, 1)
} }
func (h *histogram) Write(out *dto.Metric) error { func (h *histogram) Write(out *dto.Metric) error {
his := &dto.Histogram{} var (
buckets := make([]*dto.Bucket, len(h.upperBounds)) his = &dto.Histogram{}
buckets = make([]*dto.Bucket, len(h.upperBounds))
hotCounts, coldCounts *histogramCounts
count uint64
)
his.SampleSum = proto.Float64(math.Float64frombits(atomic.LoadUint64(&h.sumBits))) // For simplicity, we mutex the rest of this method. It is not in the
his.SampleCount = proto.Uint64(atomic.LoadUint64(&h.count)) // hot path, i.e. Observe is called much more often than Write. The
var count uint64 // complication of making Write lock-free isn't worth it.
h.writeMtx.Lock()
defer h.writeMtx.Unlock()
// This is a bit arcane, which is why the following spells out this if
// clause in English:
//
// If the currently-hot counts struct is #0, we atomically increment
// h.countAndHotIdx by 1 so that from now on Observe will use the counts
// struct #1. Furthermore, the atomic increment gives us the new value,
// which, in its most significant 63 bits, tells us the count of
// observations done so far up to and including currently ongoing
// observations still using the counts struct just changed from hot to
// cold. To have a normal uint64 for the count, we bitshift by 1 and
// save the result in count. We also set h.hotIdx to 1 for the next
// Write call, and we will refer to counts #1 as hotCounts and to counts
// #0 as coldCounts.
//
// If the currently-hot counts struct is #1, we do the corresponding
// things the other way round. We have to _decrement_ h.countAndHotIdx
// (which is a bit arcane in itself, as we have to express -1 with an
// unsigned int...).
if h.hotIdx == 0 {
count = atomic.AddUint64(&h.countAndHotIdx, 1) >> 1
h.hotIdx = 1
hotCounts = &h.counts[1]
coldCounts = &h.counts[0]
} else {
count = atomic.AddUint64(&h.countAndHotIdx, ^uint64(0)) >> 1 // Decrement.
h.hotIdx = 0
hotCounts = &h.counts[0]
coldCounts = &h.counts[1]
}
// Now we have to wait for the now-declared-cold counts to actually cool
// down, i.e. wait for all observations still using it to finish. That's
// the case once the count in the cold counts struct is the same as the
// one atomically retrieved from the upper 63bits of h.countAndHotIdx.
for {
if count == atomic.LoadUint64(&coldCounts.count) {
break
}
runtime.Gosched() // Let observations get work done.
}
his.SampleCount = proto.Uint64(count)
his.SampleSum = proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits)))
var cumCount uint64
for i, upperBound := range h.upperBounds { for i, upperBound := range h.upperBounds {
count += atomic.LoadUint64(&h.counts[i]) cumCount += atomic.LoadUint64(&coldCounts.buckets[i])
buckets[i] = &dto.Bucket{ buckets[i] = &dto.Bucket{
CumulativeCount: proto.Uint64(count), CumulativeCount: proto.Uint64(cumCount),
UpperBound: proto.Float64(upperBound), UpperBound: proto.Float64(upperBound),
} }
} }
his.Bucket = buckets his.Bucket = buckets
out.Histogram = his out.Histogram = his
out.Label = h.labelPairs out.Label = h.labelPairs
// Finally add all the cold counts to the new hot counts and reset the cold counts.
atomic.AddUint64(&hotCounts.count, count)
atomic.StoreUint64(&coldCounts.count, 0)
for {
oldBits := atomic.LoadUint64(&hotCounts.sumBits)
newBits := math.Float64bits(math.Float64frombits(oldBits) + his.GetSampleSum())
if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
atomic.StoreUint64(&coldCounts.sumBits, 0)
break
}
}
for i := range h.upperBounds {
atomic.AddUint64(&hotCounts.buckets[i], atomic.LoadUint64(&coldCounts.buckets[i]))
atomic.StoreUint64(&coldCounts.buckets[i], 0)
}
return nil return nil
} }
@ -454,7 +555,7 @@ func (h *constHistogram) Write(out *dto.Metric) error {
// bucket. // bucket.
// //
// NewConstHistogram returns an error if the length of labelValues is not // NewConstHistogram returns an error if the length of labelValues is not
// consistent with the variable labels in Desc. // consistent with the variable labels in Desc or if Desc is invalid.
func NewConstHistogram( func NewConstHistogram(
desc *Desc, desc *Desc,
count uint64, count uint64,
@ -462,6 +563,9 @@ func NewConstHistogram(
buckets map[float64]uint64, buckets map[float64]uint64,
labelValues ...string, labelValues ...string,
) (Metric, error) { ) (Metric, error) {
if desc.err != nil {
return nil, desc.err
}
if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil { if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
return nil, err return nil, err
} }

View file

@ -61,15 +61,15 @@ func giveBuf(buf *bytes.Buffer) {
// name). // name).
// //
// Deprecated: Please note the issues described in the doc comment of // Deprecated: Please note the issues described in the doc comment of
// InstrumentHandler. You might want to consider using // InstrumentHandler. You might want to consider using promhttp.Handler instead.
// promhttp.InstrumentedHandler instead.
func Handler() http.Handler { func Handler() http.Handler {
return InstrumentHandler("prometheus", UninstrumentedHandler()) return InstrumentHandler("prometheus", UninstrumentedHandler())
} }
// UninstrumentedHandler returns an HTTP handler for the DefaultGatherer. // UninstrumentedHandler returns an HTTP handler for the DefaultGatherer.
// //
// Deprecated: Use promhttp.Handler instead. See there for further documentation. // Deprecated: Use promhttp.HandlerFor(DefaultGatherer, promhttp.HandlerOpts{})
// instead. See there for further documentation.
func UninstrumentedHandler() http.Handler { func UninstrumentedHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
mfs, err := DefaultGatherer.Gather() mfs, err := DefaultGatherer.Gather()
@ -149,21 +149,14 @@ var now nower = nowFunc(func() time.Time {
// (label name "method") and HTTP status code (label name "code"). // (label name "method") and HTTP status code (label name "code").
// //
// Deprecated: InstrumentHandler has several issues. Use the tooling provided in // Deprecated: InstrumentHandler has several issues. Use the tooling provided in
// package promhttp instead. The issues are the following: // package promhttp instead. The issues are the following: (1) It uses Summaries
// // rather than Histograms. Summaries are not useful if aggregation across
// - It uses Summaries rather than Histograms. Summaries are not useful if // multiple instances is required. (2) It uses microseconds as unit, which is
// aggregation across multiple instances is required. // deprecated and should be replaced by seconds. (3) The size of the request is
// // calculated in a separate goroutine. Since this calculator requires access to
// - It uses microseconds as unit, which is deprecated and should be replaced by // the request header, it creates a race with any writes to the header performed
// seconds. // during request handling. httputil.ReverseProxy is a prominent example for a
// // handler performing such writes. (4) It has additional issues with HTTP/2, cf.
// - The size of the request is calculated in a separate goroutine. Since this
// calculator requires access to the request header, it creates a race with
// any writes to the header performed during request handling.
// httputil.ReverseProxy is a prominent example for a handler
// performing such writes.
//
// - It has additional issues with HTTP/2, cf.
// https://github.com/prometheus/client_golang/issues/272. // https://github.com/prometheus/client_golang/issues/272.
func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFunc { func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFunc {
return InstrumentHandlerFunc(handlerName, handler.ServeHTTP) return InstrumentHandlerFunc(handlerName, handler.ServeHTTP)
@ -307,7 +300,7 @@ func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.Respo
} }
func computeApproximateRequestSize(r *http.Request) <-chan int { func computeApproximateRequestSize(r *http.Request) <-chan int {
// Get URL length in current go routine for avoiding a race condition. // Get URL length in current goroutine for avoiding a race condition.
// HandlerFunc that runs in parallel may modify the URL. // HandlerFunc that runs in parallel may modify the URL.
s := 0 s := 0
if r.URL != nil { if r.URL != nil {

View file

@ -0,0 +1,85 @@
// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package internal
import (
"sort"
dto "github.com/prometheus/client_model/go"
)
// metricSorter is a sortable slice of *dto.Metric.
type metricSorter []*dto.Metric
func (s metricSorter) Len() int {
return len(s)
}
func (s metricSorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s metricSorter) Less(i, j int) bool {
if len(s[i].Label) != len(s[j].Label) {
// This should not happen. The metrics are
// inconsistent. However, we have to deal with the fact, as
// people might use custom collectors or metric family injection
// to create inconsistent metrics. So let's simply compare the
// number of labels in this case. That will still yield
// reproducible sorting.
return len(s[i].Label) < len(s[j].Label)
}
for n, lp := range s[i].Label {
vi := lp.GetValue()
vj := s[j].Label[n].GetValue()
if vi != vj {
return vi < vj
}
}
// We should never arrive here. Multiple metrics with the same
// label set in the same scrape will lead to undefined ingestion
// behavior. However, as above, we have to provide stable sorting
// here, even for inconsistent metrics. So sort equal metrics
// by their timestamp, with missing timestamps (implying "now")
// coming last.
if s[i].TimestampMs == nil {
return false
}
if s[j].TimestampMs == nil {
return true
}
return s[i].GetTimestampMs() < s[j].GetTimestampMs()
}
// NormalizeMetricFamilies returns a MetricFamily slice with empty
// MetricFamilies pruned and the remaining MetricFamilies sorted by name within
// the slice, with the contained Metrics sorted within each MetricFamily.
func NormalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
for _, mf := range metricFamiliesByName {
sort.Sort(metricSorter(mf.Metric))
}
names := make([]string, 0, len(metricFamiliesByName))
for name, mf := range metricFamiliesByName {
if len(mf.Metric) > 0 {
names = append(names, name)
}
}
sort.Strings(names)
result := make([]*dto.MetricFamily, 0, len(names))
for _, name := range names {
result = append(result, metricFamiliesByName[name])
}
return result
}

View file

@ -1,3 +1,16 @@
// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prometheus package prometheus
import ( import (

View file

@ -15,6 +15,9 @@ package prometheus
import ( import (
"strings" "strings"
"time"
"github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
) )
@ -43,9 +46,8 @@ type Metric interface {
// While populating dto.Metric, it is the responsibility of the // While populating dto.Metric, it is the responsibility of the
// implementation to ensure validity of the Metric protobuf (like valid // implementation to ensure validity of the Metric protobuf (like valid
// UTF-8 strings or syntactically valid metric and label names). It is // UTF-8 strings or syntactically valid metric and label names). It is
// recommended to sort labels lexicographically. (Implementers may find // recommended to sort labels lexicographically. Callers of Write should
// LabelPairSorter useful for that.) Callers of Write should still make // still make sure of sorting if they depend on it.
// sure of sorting if they depend on it.
Write(*dto.Metric) error Write(*dto.Metric) error
// TODO(beorn7): The original rationale of passing in a pre-allocated // TODO(beorn7): The original rationale of passing in a pre-allocated
// dto.Metric protobuf to save allocations has disappeared. The // dto.Metric protobuf to save allocations has disappeared. The
@ -57,8 +59,9 @@ type Metric interface {
// implementation XXX has its own XXXOpts type, but in most cases, it is just be // implementation XXX has its own XXXOpts type, but in most cases, it is just be
// an alias of this type (which might change when the requirement arises.) // an alias of this type (which might change when the requirement arises.)
// //
// It is mandatory to set Name and Help to a non-empty string. All other fields // It is mandatory to set Name to a non-empty string. All other fields are
// are optional and can safely be left at their zero value. // optional and can safely be left at their zero value, although it is strongly
// encouraged to set a Help string.
type Opts struct { type Opts struct {
// Namespace, Subsystem, and Name are components of the fully-qualified // Namespace, Subsystem, and Name are components of the fully-qualified
// name of the Metric (created by joining these components with // name of the Metric (created by joining these components with
@ -69,7 +72,7 @@ type Opts struct {
Subsystem string Subsystem string
Name string Name string
// Help provides information about this metric. Mandatory! // Help provides information about this metric.
// //
// Metrics with the same fully-qualified name must have the same Help // Metrics with the same fully-qualified name must have the same Help
// string. // string.
@ -110,20 +113,19 @@ func BuildFQName(namespace, subsystem, name string) string {
return name return name
} }
// LabelPairSorter implements sort.Interface. It is used to sort a slice of // labelPairSorter implements sort.Interface. It is used to sort a slice of
// dto.LabelPair pointers. This is useful for implementing the Write method of // dto.LabelPair pointers.
// custom metrics. type labelPairSorter []*dto.LabelPair
type LabelPairSorter []*dto.LabelPair
func (s LabelPairSorter) Len() int { func (s labelPairSorter) Len() int {
return len(s) return len(s)
} }
func (s LabelPairSorter) Swap(i, j int) { func (s labelPairSorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i] s[i], s[j] = s[j], s[i]
} }
func (s LabelPairSorter) Less(i, j int) bool { func (s labelPairSorter) Less(i, j int) bool {
return s[i].GetName() < s[j].GetName() return s[i].GetName() < s[j].GetName()
} }
@ -142,3 +144,31 @@ func NewInvalidMetric(desc *Desc, err error) Metric {
func (m *invalidMetric) Desc() *Desc { return m.desc } func (m *invalidMetric) Desc() *Desc { return m.desc }
func (m *invalidMetric) Write(*dto.Metric) error { return m.err } func (m *invalidMetric) Write(*dto.Metric) error { return m.err }
type timestampedMetric struct {
Metric
t time.Time
}
func (m timestampedMetric) Write(pb *dto.Metric) error {
e := m.Metric.Write(pb)
pb.TimestampMs = proto.Int64(m.t.Unix()*1000 + int64(m.t.Nanosecond()/1000000))
return e
}
// NewMetricWithTimestamp returns a new Metric wrapping the provided Metric in a
// way that it has an explicit timestamp set to the provided Time. This is only
// useful in rare cases as the timestamp of a Prometheus metric should usually
// be set by the Prometheus server during scraping. Exceptions include mirroring
// metrics with given timestamps from other metric
// sources.
//
// NewMetricWithTimestamp works best with MustNewConstMetric,
// MustNewConstHistogram, and MustNewConstSummary, see example.
//
// Currently, the exposition formats used by Prometheus are limited to
// millisecond resolution. Thus, the provided time will be rounded down to the
// next full millisecond value.
func NewMetricWithTimestamp(t time.Time, m Metric) Metric {
return timestampedMetric{Metric: m, t: t}
}

View file

@ -13,45 +13,74 @@
package prometheus package prometheus
import "github.com/prometheus/procfs" import (
"errors"
"os"
"github.com/prometheus/procfs"
)
type processCollector struct { type processCollector struct {
collectFn func(chan<- Metric) collectFn func(chan<- Metric)
pidFn func() (int, error) pidFn func() (int, error)
reportErrors bool
cpuTotal *Desc cpuTotal *Desc
openFDs, maxFDs *Desc openFDs, maxFDs *Desc
vsize, rss *Desc vsize, maxVsize *Desc
rss *Desc
startTime *Desc startTime *Desc
} }
// ProcessCollectorOpts defines the behavior of a process metrics collector
// created with NewProcessCollector.
type ProcessCollectorOpts struct {
// PidFn returns the PID of the process the collector collects metrics
// for. It is called upon each collection. By default, the PID of the
// current process is used, as determined on construction time by
// calling os.Getpid().
PidFn func() (int, error)
// If non-empty, each of the collected metrics is prefixed by the
// provided string and an underscore ("_").
Namespace string
// If true, any error encountered during collection is reported as an
// invalid metric (see NewInvalidMetric). Otherwise, errors are ignored
// and the collected metrics will be incomplete. (Possibly, no metrics
// will be collected at all.) While that's usually not desired, it is
// appropriate for the common "mix-in" of process metrics, where process
// metrics are nice to have, but failing to collect them should not
// disrupt the collection of the remaining metrics.
ReportErrors bool
}
// NewProcessCollector returns a collector which exports the current state of // NewProcessCollector returns a collector which exports the current state of
// process metrics including CPU, memory and file descriptor usage as well as // process metrics including CPU, memory and file descriptor usage as well as
// the process start time for the given process ID under the given namespace. // the process start time. The detailed behavior is defined by the provided
// ProcessCollectorOpts. The zero value of ProcessCollectorOpts creates a
// collector for the current process with an empty namespace string and no error
// reporting.
// //
// Currently, the collector depends on a Linux-style proc filesystem and // Currently, the collector depends on a Linux-style proc filesystem and
// therefore only exports metrics for Linux. // therefore only exports metrics for Linux.
func NewProcessCollector(pid int, namespace string) Collector { //
return NewProcessCollectorPIDFn( // Note: An older version of this function had the following signature:
func() (int, error) { return pid, nil }, //
namespace, // NewProcessCollector(pid int, namespace string) Collector
) //
} // Most commonly, it was called as
//
// NewProcessCollectorPIDFn works like NewProcessCollector but the process ID is // NewProcessCollector(os.Getpid(), "")
// determined on each collect anew by calling the given pidFn function. //
func NewProcessCollectorPIDFn( // The following call of the current version is equivalent to the above:
pidFn func() (int, error), //
namespace string, // NewProcessCollector(ProcessCollectorOpts{})
) Collector { func NewProcessCollector(opts ProcessCollectorOpts) Collector {
ns := "" ns := ""
if len(namespace) > 0 { if len(opts.Namespace) > 0 {
ns = namespace + "_" ns = opts.Namespace + "_"
} }
c := processCollector{ c := &processCollector{
pidFn: pidFn, reportErrors: opts.ReportErrors,
collectFn: func(chan<- Metric) {},
cpuTotal: NewDesc( cpuTotal: NewDesc(
ns+"process_cpu_seconds_total", ns+"process_cpu_seconds_total",
"Total user and system CPU time spent in seconds.", "Total user and system CPU time spent in seconds.",
@ -72,6 +101,11 @@ func NewProcessCollectorPIDFn(
"Virtual memory size in bytes.", "Virtual memory size in bytes.",
nil, nil, nil, nil,
), ),
maxVsize: NewDesc(
ns+"process_virtual_memory_max_bytes",
"Maximum amount of virtual memory available in bytes.",
nil, nil,
),
rss: NewDesc( rss: NewDesc(
ns+"process_resident_memory_bytes", ns+"process_resident_memory_bytes",
"Resident memory size in bytes.", "Resident memory size in bytes.",
@ -84,12 +118,23 @@ func NewProcessCollectorPIDFn(
), ),
} }
if opts.PidFn == nil {
pid := os.Getpid()
c.pidFn = func() (int, error) { return pid, nil }
} else {
c.pidFn = opts.PidFn
}
// Set up process metric collection if supported by the runtime. // Set up process metric collection if supported by the runtime.
if _, err := procfs.NewStat(); err == nil { if _, err := procfs.NewStat(); err == nil {
c.collectFn = c.processCollect c.collectFn = c.processCollect
} else {
c.collectFn = func(ch chan<- Metric) {
c.reportError(ch, nil, errors.New("process metrics not supported on this platform"))
}
} }
return &c return c
} }
// Describe returns all descriptions of the collector. // Describe returns all descriptions of the collector.
@ -98,6 +143,7 @@ func (c *processCollector) Describe(ch chan<- *Desc) {
ch <- c.openFDs ch <- c.openFDs
ch <- c.maxFDs ch <- c.maxFDs
ch <- c.vsize ch <- c.vsize
ch <- c.maxVsize
ch <- c.rss ch <- c.rss
ch <- c.startTime ch <- c.startTime
} }
@ -107,16 +153,16 @@ func (c *processCollector) Collect(ch chan<- Metric) {
c.collectFn(ch) c.collectFn(ch)
} }
// TODO(ts): Bring back error reporting by reverting 7faf9e7 as soon as the
// client allows users to configure the error behavior.
func (c *processCollector) processCollect(ch chan<- Metric) { func (c *processCollector) processCollect(ch chan<- Metric) {
pid, err := c.pidFn() pid, err := c.pidFn()
if err != nil { if err != nil {
c.reportError(ch, nil, err)
return return
} }
p, err := procfs.NewProc(pid) p, err := procfs.NewProc(pid)
if err != nil { if err != nil {
c.reportError(ch, nil, err)
return return
} }
@ -126,14 +172,33 @@ func (c *processCollector) processCollect(ch chan<- Metric) {
ch <- MustNewConstMetric(c.rss, GaugeValue, float64(stat.ResidentMemory())) ch <- MustNewConstMetric(c.rss, GaugeValue, float64(stat.ResidentMemory()))
if startTime, err := stat.StartTime(); err == nil { if startTime, err := stat.StartTime(); err == nil {
ch <- MustNewConstMetric(c.startTime, GaugeValue, startTime) ch <- MustNewConstMetric(c.startTime, GaugeValue, startTime)
} else {
c.reportError(ch, c.startTime, err)
} }
} else {
c.reportError(ch, nil, err)
} }
if fds, err := p.FileDescriptorsLen(); err == nil { if fds, err := p.FileDescriptorsLen(); err == nil {
ch <- MustNewConstMetric(c.openFDs, GaugeValue, float64(fds)) ch <- MustNewConstMetric(c.openFDs, GaugeValue, float64(fds))
} else {
c.reportError(ch, c.openFDs, err)
} }
if limits, err := p.NewLimits(); err == nil { if limits, err := p.NewLimits(); err == nil {
ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(limits.OpenFiles)) ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(limits.OpenFiles))
ch <- MustNewConstMetric(c.maxVsize, GaugeValue, float64(limits.AddressSpace))
} else {
c.reportError(ch, nil, err)
} }
} }
func (c *processCollector) reportError(ch chan<- Metric, desc *Desc, err error) {
if !c.reportErrors {
return
}
if desc == nil {
desc = NewInvalidDesc(err)
}
ch <- NewInvalidMetric(desc, err)
}

View file

@ -16,7 +16,6 @@ package prometheus
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"os"
"runtime" "runtime"
"sort" "sort"
"strings" "strings"
@ -26,6 +25,8 @@ import (
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
"github.com/prometheus/client_golang/prometheus/internal"
) )
const ( const (
@ -52,7 +53,7 @@ var (
) )
func init() { func init() {
MustRegister(NewProcessCollector(os.Getpid(), "")) MustRegister(NewProcessCollector(ProcessCollectorOpts{}))
MustRegister(NewGoCollector()) MustRegister(NewGoCollector())
} }
@ -527,7 +528,7 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
break break
} }
} }
return normalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap() return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
} }
// processMetric is an internal helper method only used by the Gather method. // processMetric is an internal helper method only used by the Gather method.
@ -538,6 +539,11 @@ func processMetric(
registeredDescIDs map[uint64]struct{}, registeredDescIDs map[uint64]struct{},
) error { ) error {
desc := metric.Desc() desc := metric.Desc()
// Wrapped metrics collected by an unchecked Collector can have an
// invalid Desc.
if desc.err != nil {
return desc.err
}
dtoMetric := &dto.Metric{} dtoMetric := &dto.Metric{}
if err := metric.Write(dtoMetric); err != nil { if err := metric.Write(dtoMetric); err != nil {
return fmt.Errorf("error collecting metric %v: %s", desc, err) return fmt.Errorf("error collecting metric %v: %s", desc, err)
@ -707,72 +713,7 @@ func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
} }
} }
} }
return normalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap() return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
}
// metricSorter is a sortable slice of *dto.Metric.
type metricSorter []*dto.Metric
func (s metricSorter) Len() int {
return len(s)
}
func (s metricSorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s metricSorter) Less(i, j int) bool {
if len(s[i].Label) != len(s[j].Label) {
// This should not happen. The metrics are
// inconsistent. However, we have to deal with the fact, as
// people might use custom collectors or metric family injection
// to create inconsistent metrics. So let's simply compare the
// number of labels in this case. That will still yield
// reproducible sorting.
return len(s[i].Label) < len(s[j].Label)
}
for n, lp := range s[i].Label {
vi := lp.GetValue()
vj := s[j].Label[n].GetValue()
if vi != vj {
return vi < vj
}
}
// We should never arrive here. Multiple metrics with the same
// label set in the same scrape will lead to undefined ingestion
// behavior. However, as above, we have to provide stable sorting
// here, even for inconsistent metrics. So sort equal metrics
// by their timestamp, with missing timestamps (implying "now")
// coming last.
if s[i].TimestampMs == nil {
return false
}
if s[j].TimestampMs == nil {
return true
}
return s[i].GetTimestampMs() < s[j].GetTimestampMs()
}
// normalizeMetricFamilies returns a MetricFamily slice with empty
// MetricFamilies pruned and the remaining MetricFamilies sorted by name within
// the slice, with the contained Metrics sorted within each MetricFamily.
func normalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
for _, mf := range metricFamiliesByName {
sort.Sort(metricSorter(mf.Metric))
}
names := make([]string, 0, len(metricFamiliesByName))
for name, mf := range metricFamiliesByName {
if len(mf.Metric) > 0 {
names = append(names, name)
}
}
sort.Strings(names)
result := make([]*dto.MetricFamily, 0, len(names))
for _, name := range names {
result = append(result, metricFamiliesByName[name])
}
return result
} }
// checkSuffixCollisions checks for collisions with the “magic” suffixes the // checkSuffixCollisions checks for collisions with the “magic” suffixes the
@ -882,7 +823,7 @@ func checkMetricConsistency(
h = hashAddByte(h, separatorByte) h = hashAddByte(h, separatorByte)
// Make sure label pairs are sorted. We depend on it for the consistency // Make sure label pairs are sorted. We depend on it for the consistency
// check. // check.
sort.Sort(LabelPairSorter(dtoMetric.Label)) sort.Sort(labelPairSorter(dtoMetric.Label))
for _, lp := range dtoMetric.Label { for _, lp := range dtoMetric.Label {
h = hashAdd(h, lp.GetName()) h = hashAdd(h, lp.GetName())
h = hashAddByte(h, separatorByte) h = hashAddByte(h, separatorByte)
@ -926,7 +867,7 @@ func checkDescConsistency(
metricFamily.GetName(), dtoMetric, desc, metricFamily.GetName(), dtoMetric, desc,
) )
} }
sort.Sort(LabelPairSorter(lpsFromDesc)) sort.Sort(labelPairSorter(lpsFromDesc))
for i, lpFromDesc := range lpsFromDesc { for i, lpFromDesc := range lpsFromDesc {
lpFromMetric := dtoMetric.Label[i] lpFromMetric := dtoMetric.Label[i]
if lpFromDesc.GetName() != lpFromMetric.GetName() || if lpFromDesc.GetName() != lpFromMetric.GetName() ||

View file

@ -37,7 +37,7 @@ const quantileLabel = "quantile"
// A typical use-case is the observation of request latencies. By default, a // A typical use-case is the observation of request latencies. By default, a
// Summary provides the median, the 90th and the 99th percentile of the latency // Summary provides the median, the 90th and the 99th percentile of the latency
// as rank estimations. However, the default behavior will change in the // as rank estimations. However, the default behavior will change in the
// upcoming v0.10 of the library. There will be no rank estiamtions at all by // upcoming v0.10 of the library. There will be no rank estimations at all by
// default. For a sane transition, it is recommended to set the desired rank // default. For a sane transition, it is recommended to set the desired rank
// estimations explicitly. // estimations explicitly.
// //
@ -81,10 +81,10 @@ const (
) )
// SummaryOpts bundles the options for creating a Summary metric. It is // SummaryOpts bundles the options for creating a Summary metric. It is
// mandatory to set Name and Help to a non-empty string. While all other fields // mandatory to set Name to a non-empty string. While all other fields are
// are optional and can safely be left at their zero value, it is recommended to // optional and can safely be left at their zero value, it is recommended to set
// explicitly set the Objectives field to the desired value as the default value // a help string and to explicitly set the Objectives field to the desired value
// will change in the upcoming v0.10 of the library. // as the default value will change in the upcoming v0.10 of the library.
type SummaryOpts struct { type SummaryOpts struct {
// Namespace, Subsystem, and Name are components of the fully-qualified // Namespace, Subsystem, and Name are components of the fully-qualified
// name of the Summary (created by joining these components with // name of the Summary (created by joining these components with
@ -95,7 +95,7 @@ type SummaryOpts struct {
Subsystem string Subsystem string
Name string Name string
// Help provides information about this Summary. Mandatory! // Help provides information about this Summary.
// //
// Metrics with the same fully-qualified name must have the same Help // Metrics with the same fully-qualified name must have the same Help
// string. // string.
@ -586,7 +586,7 @@ func (s *constSummary) Write(out *dto.Metric) error {
// map[float64]float64{0.5: 0.23, 0.99: 0.56} // map[float64]float64{0.5: 0.23, 0.99: 0.56}
// //
// NewConstSummary returns an error if the length of labelValues is not // NewConstSummary returns an error if the length of labelValues is not
// consistent with the variable labels in Desc. // consistent with the variable labels in Desc or if Desc is invalid.
func NewConstSummary( func NewConstSummary(
desc *Desc, desc *Desc,
count uint64, count uint64,
@ -594,6 +594,9 @@ func NewConstSummary(
quantiles map[float64]float64, quantiles map[float64]float64,
labelValues ...string, labelValues ...string,
) (Metric, error) { ) (Metric, error) {
if desc.err != nil {
return nil, desc.err
}
if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil { if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
return nil, err return nil, err
} }

View file

@ -17,9 +17,9 @@ import (
"fmt" "fmt"
"sort" "sort"
dto "github.com/prometheus/client_model/go"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go"
) )
// ValueType is an enumeration of metric types that represent a simple value. // ValueType is an enumeration of metric types that represent a simple value.
@ -77,8 +77,12 @@ func (v *valueFunc) Write(out *dto.Metric) error {
// operations. However, when implementing custom Collectors, it is useful as a // operations. However, when implementing custom Collectors, it is useful as a
// throw-away metric that is generated on the fly to send it to Prometheus in // throw-away metric that is generated on the fly to send it to Prometheus in
// the Collect method. NewConstMetric returns an error if the length of // the Collect method. NewConstMetric returns an error if the length of
// labelValues is not consistent with the variable labels in Desc. // labelValues is not consistent with the variable labels in Desc or if Desc is
// invalid.
func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues ...string) (Metric, error) { func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues ...string) (Metric, error) {
if desc.err != nil {
return nil, desc.err
}
if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil { if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
return nil, err return nil, err
} }
@ -153,6 +157,6 @@ func makeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair {
}) })
} }
labelPairs = append(labelPairs, desc.constLabelPairs...) labelPairs = append(labelPairs, desc.constLabelPairs...)
sort.Sort(LabelPairSorter(labelPairs)) sort.Sort(labelPairSorter(labelPairs))
return labelPairs return labelPairs
} }

View file

@ -0,0 +1,179 @@
// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prometheus
import (
"fmt"
"sort"
"github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go"
)
// WrapRegistererWith returns a Registerer wrapping the provided
// Registerer. Collectors registered with the returned Registerer will be
// registered with the wrapped Registerer in a modified way. The modified
// Collector adds the provided Labels to all Metrics it collects (as
// ConstLabels). The Metrics collected by the unmodified Collector must not
// duplicate any of those labels.
//
// WrapRegistererWith provides a way to add fixed labels to a subset of
// Collectors. It should not be used to add fixed labels to all metrics exposed.
//
// The Collector example demonstrates a use of WrapRegistererWith.
func WrapRegistererWith(labels Labels, reg Registerer) Registerer {
return &wrappingRegisterer{
wrappedRegisterer: reg,
labels: labels,
}
}
// WrapRegistererWithPrefix returns a Registerer wrapping the provided
// Registerer. Collectors registered with the returned Registerer will be
// registered with the wrapped Registerer in a modified way. The modified
// Collector adds the provided prefix to the name of all Metrics it collects.
//
// WrapRegistererWithPrefix is useful to have one place to prefix all metrics of
// a sub-system. To make this work, register metrics of the sub-system with the
// wrapping Registerer returned by WrapRegistererWithPrefix. It is rarely useful
// to use the same prefix for all metrics exposed. In particular, do not prefix
// metric names that are standardized across applications, as that would break
// horizontal monitoring, for example the metrics provided by the Go collector
// (see NewGoCollector) and the process collector (see NewProcessCollector). (In
// fact, those metrics are already prefixed with “go_” or “process_”,
// respectively.)
func WrapRegistererWithPrefix(prefix string, reg Registerer) Registerer {
return &wrappingRegisterer{
wrappedRegisterer: reg,
prefix: prefix,
}
}
type wrappingRegisterer struct {
wrappedRegisterer Registerer
prefix string
labels Labels
}
func (r *wrappingRegisterer) Register(c Collector) error {
return r.wrappedRegisterer.Register(&wrappingCollector{
wrappedCollector: c,
prefix: r.prefix,
labels: r.labels,
})
}
func (r *wrappingRegisterer) MustRegister(cs ...Collector) {
for _, c := range cs {
if err := r.Register(c); err != nil {
panic(err)
}
}
}
func (r *wrappingRegisterer) Unregister(c Collector) bool {
return r.wrappedRegisterer.Unregister(&wrappingCollector{
wrappedCollector: c,
prefix: r.prefix,
labels: r.labels,
})
}
type wrappingCollector struct {
wrappedCollector Collector
prefix string
labels Labels
}
func (c *wrappingCollector) Collect(ch chan<- Metric) {
wrappedCh := make(chan Metric)
go func() {
c.wrappedCollector.Collect(wrappedCh)
close(wrappedCh)
}()
for m := range wrappedCh {
ch <- &wrappingMetric{
wrappedMetric: m,
prefix: c.prefix,
labels: c.labels,
}
}
}
func (c *wrappingCollector) Describe(ch chan<- *Desc) {
wrappedCh := make(chan *Desc)
go func() {
c.wrappedCollector.Describe(wrappedCh)
close(wrappedCh)
}()
for desc := range wrappedCh {
ch <- wrapDesc(desc, c.prefix, c.labels)
}
}
type wrappingMetric struct {
wrappedMetric Metric
prefix string
labels Labels
}
func (m *wrappingMetric) Desc() *Desc {
return wrapDesc(m.wrappedMetric.Desc(), m.prefix, m.labels)
}
func (m *wrappingMetric) Write(out *dto.Metric) error {
if err := m.wrappedMetric.Write(out); err != nil {
return err
}
if len(m.labels) == 0 {
// No wrapping labels.
return nil
}
for ln, lv := range m.labels {
out.Label = append(out.Label, &dto.LabelPair{
Name: proto.String(ln),
Value: proto.String(lv),
})
}
sort.Sort(labelPairSorter(out.Label))
return nil
}
func wrapDesc(desc *Desc, prefix string, labels Labels) *Desc {
constLabels := Labels{}
for _, lp := range desc.constLabelPairs {
constLabels[*lp.Name] = *lp.Value
}
for ln, lv := range labels {
if _, alreadyUsed := constLabels[ln]; alreadyUsed {
return &Desc{
fqName: desc.fqName,
help: desc.help,
variableLabels: desc.variableLabels,
constLabelPairs: desc.constLabelPairs,
err: fmt.Errorf("attempted wrapping with already existing label name %q", ln),
}
}
constLabels[ln] = lv
}
// NewDesc will do remaining validations.
newDesc := NewDesc(prefix+desc.fqName, desc.help, desc.variableLabels, constLabels)
// Propagate errors if there was any. This will override any errer
// created by NewDesc above, i.e. earlier errors get precedence.
if desc.err != nil {
newDesc.err = desc.err
}
return newDesc
}

View file

@ -359,7 +359,7 @@ func (p *TextParser) startLabelValue() stateFn {
} }
return p.readingValue return p.readingValue
default: default:
p.parseError(fmt.Sprintf("unexpected end of label value %q", p.currentLabelPair.Value)) p.parseError(fmt.Sprintf("unexpected end of label value %q", p.currentLabelPair.GetValue()))
return nil return nil
} }
} }

View file

@ -0,0 +1,73 @@
// Package vfstemplate offers html/template helpers that use http.FileSystem.
package vfstemplate
import (
"fmt"
"html/template"
"net/http"
"path"
"github.com/shurcooL/httpfs/path/vfspath"
"github.com/shurcooL/httpfs/vfsutil"
)
// ParseFiles creates a new Template if t is nil and parses the template definitions from
// the named files. The returned template's name will have the (base) name and
// (parsed) contents of the first file. There must be at least one file.
// If an error occurs, parsing stops and the returned *Template is nil.
func ParseFiles(fs http.FileSystem, t *template.Template, filenames ...string) (*template.Template, error) {
return parseFiles(fs, t, filenames...)
}
// ParseGlob parses the template definitions in the files identified by the
// pattern and associates the resulting templates with t. The pattern is
// processed by vfspath.Glob and must match at least one file. ParseGlob is
// equivalent to calling t.ParseFiles with the list of files matched by the
// pattern.
func ParseGlob(fs http.FileSystem, t *template.Template, pattern string) (*template.Template, error) {
filenames, err := vfspath.Glob(fs, pattern)
if err != nil {
return nil, err
}
if len(filenames) == 0 {
return nil, fmt.Errorf("vfs/html/vfstemplate: pattern matches no files: %#q", pattern)
}
return parseFiles(fs, t, filenames...)
}
// parseFiles is the helper for the method and function. If the argument
// template is nil, it is created from the first file.
func parseFiles(fs http.FileSystem, t *template.Template, filenames ...string) (*template.Template, error) {
if len(filenames) == 0 {
// Not really a problem, but be consistent.
return nil, fmt.Errorf("vfs/html/vfstemplate: no files named in call to ParseFiles")
}
for _, filename := range filenames {
b, err := vfsutil.ReadFile(fs, filename)
if err != nil {
return nil, err
}
s := string(b)
name := path.Base(filename)
// First template becomes return value if not already defined,
// and we use that one for subsequent New calls to associate
// all the templates together. Also, if this file has the same name
// as t, this file becomes the contents of t, so
// t, err := New(name).Funcs(xxx).ParseFiles(name)
// works. Otherwise we create a new template associated with t.
var tmpl *template.Template
if t == nil {
t = template.New(name)
}
if name == t.Name() {
tmpl = t
} else {
tmpl = t.New(name)
}
_, err = tmpl.Parse(s)
if err != nil {
return nil, err
}
}
return t, nil
}

105
vendor/github.com/shurcooL/httpfs/path/vfspath/match.go generated vendored Normal file
View file

@ -0,0 +1,105 @@
// Package vfspath implements utility routines for manipulating virtual file system paths.
package vfspath
import (
"net/http"
"os"
"path"
"sort"
"strings"
"github.com/shurcooL/httpfs/vfsutil"
)
const separator = "/"
// Glob returns the names of all files matching pattern or nil
// if there is no matching file. The syntax of patterns is the same
// as in path.Match. The pattern may describe hierarchical names such as
// /usr/*/bin/ed.
//
// Glob ignores file system errors such as I/O errors reading directories.
// The only possible returned error is ErrBadPattern, when pattern
// is malformed.
func Glob(fs http.FileSystem, pattern string) (matches []string, err error) {
if !hasMeta(pattern) {
if _, err = vfsutil.Stat(fs, pattern); err != nil {
return nil, nil
}
return []string{pattern}, nil
}
dir, file := path.Split(pattern)
switch dir {
case "":
dir = "."
case string(separator):
// nothing
default:
dir = dir[0 : len(dir)-1] // chop off trailing separator
}
if !hasMeta(dir) {
return glob(fs, dir, file, nil)
}
var m []string
m, err = Glob(fs, dir)
if err != nil {
return
}
for _, d := range m {
matches, err = glob(fs, d, file, matches)
if err != nil {
return
}
}
return
}
// glob searches for files matching pattern in the directory dir
// and appends them to matches. If the directory cannot be
// opened, it returns the existing matches. New matches are
// added in lexicographical order.
func glob(fs http.FileSystem, dir, pattern string, matches []string) (m []string, e error) {
m = matches
fi, err := vfsutil.Stat(fs, dir)
if err != nil {
return
}
if !fi.IsDir() {
return
}
fis, err := vfsutil.ReadDir(fs, dir)
if err != nil {
return
}
sort.Sort(byName(fis))
for _, fi := range fis {
n := fi.Name()
matched, err := path.Match(path.Clean(pattern), n)
if err != nil {
return m, err
}
if matched {
m = append(m, path.Join(dir, n))
}
}
return
}
// hasMeta reports whether path contains any of the magic characters
// recognized by Match.
func hasMeta(path string) bool {
// TODO(niemeyer): Should other magic characters be added here?
return strings.ContainsAny(path, "*?[")
}
// byName implements sort.Interface.
type byName []os.FileInfo
func (f byName) Len() int { return len(f) }
func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }

21
vendor/github.com/shurcooL/httpfs/vfsutil/file.go generated vendored Normal file
View file

@ -0,0 +1,21 @@
package vfsutil
import (
"net/http"
"os"
)
// File implements http.FileSystem using the native file system restricted to a
// specific file served at root.
//
// While the FileSystem.Open method takes '/'-separated paths, a File's string
// value is a filename on the native file system, not a URL, so it is separated
// by filepath.Separator, which isn't necessarily '/'.
type File string
func (f File) Open(name string) (http.File, error) {
if name != "/" {
return nil, &os.PathError{Op: "open", Path: name, Err: os.ErrNotExist}
}
return os.Open(string(f))
}

39
vendor/github.com/shurcooL/httpfs/vfsutil/vfsutil.go generated vendored Normal file
View file

@ -0,0 +1,39 @@
// Package vfsutil implements some I/O utility functions for http.FileSystem.
package vfsutil
import (
"io/ioutil"
"net/http"
"os"
)
// ReadDir reads the contents of the directory associated with file and
// returns a slice of FileInfo values in directory order.
func ReadDir(fs http.FileSystem, name string) ([]os.FileInfo, error) {
f, err := fs.Open(name)
if err != nil {
return nil, err
}
defer f.Close()
return f.Readdir(0)
}
// Stat returns the FileInfo structure describing file.
func Stat(fs http.FileSystem, name string) (os.FileInfo, error) {
f, err := fs.Open(name)
if err != nil {
return nil, err
}
defer f.Close()
return f.Stat()
}
// ReadFile reads the file named by path from fs and returns the contents.
func ReadFile(fs http.FileSystem, path string) ([]byte, error) {
rc, err := fs.Open(path)
if err != nil {
return nil, err
}
defer rc.Close()
return ioutil.ReadAll(rc)
}

Some files were not shown because too many files have changed in this diff Show more