Compare commits
59 Commits
v1.0.0
...
config_rel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe399e2f03 | ||
|
|
d54573e469 | ||
|
|
567a84095e | ||
|
|
6f26e86964 | ||
|
|
92c9ae3859 | ||
|
|
1d8c3936e1 | ||
|
|
e3ed374b53 | ||
|
|
8a36bb153a | ||
|
|
e1825b4581 | ||
|
|
e285d3a1c5 | ||
|
|
80d743ffa5 | ||
|
|
878ee3a63e | ||
|
|
adaa755430 | ||
|
|
2ab950667e | ||
|
|
8d3c63637c | ||
|
|
c6adbd9fb5 | ||
|
|
fa25f9430f | ||
|
|
837226952d | ||
|
|
58d37566bf | ||
|
|
f1542981d6 | ||
|
|
f883e4d4b3 | ||
|
|
a38dc2f48f | ||
|
|
04159185c9 | ||
|
|
c860f3c50e | ||
|
|
62db2e5af7 | ||
|
|
d39e46096f | ||
|
|
a99f92e9a5 | ||
|
|
5ac02c373b | ||
|
|
6fdff28716 | ||
|
|
e32ff1d52c | ||
|
|
82152d030d | ||
|
|
5821c01a7b | ||
|
|
8ca38f4b77 | ||
|
|
96b39ec9bb | ||
|
|
30303bb875 | ||
|
|
12dc52edc6 | ||
|
|
6be4d69d02 | ||
|
|
b8ec7a3391 | ||
|
|
3702339f44 | ||
|
|
d96f461375 | ||
|
|
7f542acc03 | ||
|
|
8a26f23729 | ||
|
|
4a9df038fb | ||
|
|
6e00028410 | ||
|
|
1001600a68 | ||
|
|
52a05e0317 | ||
|
|
b129bc8184 | ||
|
|
e23c242d9c | ||
|
|
62279ab31a | ||
|
|
b6f865bb73 | ||
|
|
484ae5d00e | ||
|
|
a8fd79c0e1 | ||
|
|
b49447a374 | ||
|
|
b0dbda04c9 | ||
|
|
e15172111e | ||
|
|
b641eb979e | ||
|
|
6e923a84fa | ||
|
|
2a3593aade | ||
|
|
6a184933d1 |
31
.github/workflows/go.yml
vendored
Normal file
31
.github/workflows/go.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: GoCast
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build and unit test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
CI: 1
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Set up Go 1.x
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ^1.14
|
||||||
|
id: go
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: make
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: make test
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.vscode
|
||||||
17
Dockerfile
17
Dockerfile
@@ -1,20 +1,21 @@
|
|||||||
FROM golang:alpine as builder
|
FROM golang:1.14-alpine as builder
|
||||||
RUN apk update && \
|
RUN apk update && \
|
||||||
apk upgrade && \
|
apk upgrade && \
|
||||||
apk add --no-cache git && \
|
apk add --no-cache git && \
|
||||||
apk add make
|
apk add make
|
||||||
RUN mkdir -p /opt/gocast
|
|
||||||
RUN mkdir -p /go/src/github.com/mayuresh82
|
RUN mkdir -p /go/src/github.com/mayuresh82/gocast
|
||||||
RUN cd /go/src/github.com/mayuresh82 && \
|
|
||||||
git clone https://github.com/mayuresh82/gocast
|
COPY . /go/src/github.com/mayuresh82/gocast
|
||||||
|
|
||||||
WORKDIR /go/src/github.com/mayuresh82/gocast
|
WORKDIR /go/src/github.com/mayuresh82/gocast
|
||||||
RUN make
|
|
||||||
RUN cp gocast /opt/gocast/
|
RUN make linux
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
RUN apk --no-cache add ca-certificates bash iptables netcat-openbsd sudo
|
RUN apk --no-cache add ca-certificates bash iptables netcat-openbsd sudo
|
||||||
WORKDIR /root/
|
WORKDIR /root/
|
||||||
COPY --from=builder /opt/gocast/gocast .
|
COPY --from=builder /go/src/github.com/mayuresh82/gocast .
|
||||||
|
|
||||||
EXPOSE 8080/tcp
|
EXPOSE 8080/tcp
|
||||||
|
|
||||||
|
|||||||
356
Gopkg.lock
generated
356
Gopkg.lock
generated
@@ -1,356 +0,0 @@
|
|||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:c47f4964978e211c6e566596ec6246c329912ea92e9bb99c00798bb4564c5b09"
|
|
||||||
name = "github.com/armon/go-radix"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "1a2de0c21c94309923825da3df33a4381872c795"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:8583eab935e3d99d3a7ac489cd2ee7c8e95eecd7c64ab1fc8382746dacaf8563"
|
|
||||||
name = "github.com/dgryski/go-farm"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "2de33835d10275975374b37b2dcfd22c9020a1f5"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:975a4480c40f2d0b95e1f83d3ec1aa29a2774e80179e08a9a4ba2aab86721b23"
|
|
||||||
name = "github.com/eapache/channels"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "47238d5aae8c0fefd518ef2bee46290909cf8263"
|
|
||||||
version = "v1.1.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:444b82bfe35c83bbcaf84e310fb81a1f9ece03edfed586483c869e2c046aef69"
|
|
||||||
name = "github.com/eapache/queue"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "44cc805cf13205b55f69e14bcb69867d1ae92f98"
|
|
||||||
version = "v1.1.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd"
|
|
||||||
name = "github.com/fsnotify/fsnotify"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
|
|
||||||
version = "v1.4.7"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:1ba1d79f2810270045c328ae5d674321db34e3aae468eb4233883b473c5c0467"
|
|
||||||
name = "github.com/golang/glog"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:c095e448622bb061a09cb718e3dddc59acb624a67756392282374fa091c83343"
|
|
||||||
name = "github.com/golang/protobuf"
|
|
||||||
packages = [
|
|
||||||
"proto",
|
|
||||||
"ptypes",
|
|
||||||
"ptypes/any",
|
|
||||||
"ptypes/duration",
|
|
||||||
"ptypes/empty",
|
|
||||||
"ptypes/timestamp",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "7be3631955993a734965532f776bad7093f6fc9d"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:c0d19ab64b32ce9fe5cf4ddceba78d5bc9807f0016db6b1183599da3dcc24d10"
|
|
||||||
name = "github.com/hashicorp/hcl"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"hcl/ast",
|
|
||||||
"hcl/parser",
|
|
||||||
"hcl/printer",
|
|
||||||
"hcl/scanner",
|
|
||||||
"hcl/strconv",
|
|
||||||
"hcl/token",
|
|
||||||
"json/parser",
|
|
||||||
"json/scanner",
|
|
||||||
"json/token",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "8cb6e5b959231cc1119e43259c4a608f9c51a241"
|
|
||||||
version = "v1.0.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:65b0a07f85f7b5cc019c26775efc278a155a5dea0a8aa617c980e8308d16bc55"
|
|
||||||
name = "github.com/influxdata/influxdb"
|
|
||||||
packages = [
|
|
||||||
"client/v2",
|
|
||||||
"models",
|
|
||||||
"pkg/escape",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "c75cdfdfa6f71a08473fefcec71f6cbcbdef1ff4"
|
|
||||||
version = "v1.6.4"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:0a69a1c0db3591fcefb47f115b224592c8dfa4368b7ba9fae509d5e16cdc95c8"
|
|
||||||
name = "github.com/konsorten/go-windows-terminal-sequences"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "5c8c8bd35d3832f5d134ae1e1e375b69a4d25242"
|
|
||||||
version = "v1.0.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:c568d7727aa262c32bdf8a3f7db83614f7af0ed661474b24588de635c20024c7"
|
|
||||||
name = "github.com/magiconair/properties"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "c2353362d570a7bfa228149c62842019201cfb71"
|
|
||||||
version = "v1.8.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:53bc4cd4914cd7cd52139990d5170d6dc99067ae31c56530621b18b35fc30318"
|
|
||||||
name = "github.com/mitchellh/mapstructure"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "3536a929edddb9a5b34bd6861dc4a9647cb459fe"
|
|
||||||
version = "v1.1.2"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:7261338473c27eea9593dceecf6a949c689f6dbd2226cad46139ab9d5011bc4b"
|
|
||||||
name = "github.com/osrg/gobgp"
|
|
||||||
packages = [
|
|
||||||
"api",
|
|
||||||
"internal/pkg/apiutil",
|
|
||||||
"internal/pkg/config",
|
|
||||||
"internal/pkg/table",
|
|
||||||
"internal/pkg/zebra",
|
|
||||||
"pkg/packet/bgp",
|
|
||||||
"pkg/packet/bmp",
|
|
||||||
"pkg/packet/mrt",
|
|
||||||
"pkg/packet/rtr",
|
|
||||||
"pkg/server",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "329c2d316efecfed0331e30114d7086aa58e247e"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e"
|
|
||||||
name = "github.com/pelletier/go-toml"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194"
|
|
||||||
version = "v1.2.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:dd6ba1917df517806c9dcee5c87f15643c9b1ca6260d5b3f25eb863c6fe092ce"
|
|
||||||
name = "github.com/satori/go.uuid"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "8ccf5352a842c034b1a69f28c863aff9b1cdb116"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:3f53e9e4dfbb664cd62940c9c4b65a2171c66acd0b7621a1a6b8e78513525a52"
|
|
||||||
name = "github.com/sirupsen/logrus"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "ad15b42461921f1fb3529b058c6786c6a45d5162"
|
|
||||||
version = "v1.1.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:6a4a11ba764a56d2758899ec6f3848d24698d48442ebce85ee7a3f63284526cd"
|
|
||||||
name = "github.com/spf13/afero"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"mem",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "d40851caa0d747393da1ffb28f7f9d8b4eeffebd"
|
|
||||||
version = "v1.1.2"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:516e71bed754268937f57d4ecb190e01958452336fa73dbac880894164e91c1f"
|
|
||||||
name = "github.com/spf13/cast"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "8965335b8c7107321228e3e3702cab9832751bac"
|
|
||||||
version = "v1.2.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:68ea4e23713989dc20b1bded5d9da2c5f9be14ff9885beef481848edd18c26cb"
|
|
||||||
name = "github.com/spf13/jwalterweatherman"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "4a4406e478ca629068e7768fc33f3f044173c0a6"
|
|
||||||
version = "v1.0.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:c1b1102241e7f645bc8e0c22ae352e8f0dc6484b6cb4d132fa9f24174e0119e2"
|
|
||||||
name = "github.com/spf13/pflag"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "298182f68c66c05229eb03ac171abe6e309ee79a"
|
|
||||||
version = "v1.0.3"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:748519c76ecc7b5d673d7ee8924ace736ec1717b93011c77171b8bd961ac280c"
|
|
||||||
name = "github.com/spf13/viper"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "62edee319679b6ceaec16de03b966102d2dea709"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:b046e193dd6bb64f4df01dcc12eec2ec89ba32565f025f9cb9f1d54bc3945be9"
|
|
||||||
name = "github.com/vishvananda/netlink"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"nl",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "d3a23fd178f1a0d9cf1f194af62864b1dfe02be5"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:e4e30678fb2560b5c62f6308c5023d6c294fc7713216fa379411cc74465e866f"
|
|
||||||
name = "github.com/vishvananda/netns"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "13995c7128ccc8e51e9a6bd2b551020a27180abd"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:3f3a05ae0b95893d90b9b3b5afdb79a9b3d96e4e36e099d841ae602e4aca0da8"
|
|
||||||
name = "golang.org/x/crypto"
|
|
||||||
packages = ["ssh/terminal"]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "0c41d7ab0a0ee717d4590a44bcb987dfd9e183eb"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:505dbee0833715a72a529bb57c354826ad42a4496fad787fa143699b4de1a6d0"
|
|
||||||
name = "golang.org/x/net"
|
|
||||||
packages = [
|
|
||||||
"context",
|
|
||||||
"http/httpguts",
|
|
||||||
"http2",
|
|
||||||
"http2/hpack",
|
|
||||||
"idna",
|
|
||||||
"internal/timeseries",
|
|
||||||
"trace",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "04a2e542c03f1d053ab3e4d6e5abcd4b66e2be8e"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:cfc31002d1ab36060fcd4a29d9f6bad6f9eeeab1dc6f5be78d37a0f825ba6dc1"
|
|
||||||
name = "golang.org/x/sys"
|
|
||||||
packages = [
|
|
||||||
"unix",
|
|
||||||
"windows",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "eda9bb28ed513021f3e6a2a361031adc3d8a6301"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
|
|
||||||
name = "golang.org/x/text"
|
|
||||||
packages = [
|
|
||||||
"collate",
|
|
||||||
"collate/build",
|
|
||||||
"internal/colltab",
|
|
||||||
"internal/gen",
|
|
||||||
"internal/tag",
|
|
||||||
"internal/triegen",
|
|
||||||
"internal/ucd",
|
|
||||||
"language",
|
|
||||||
"secure/bidirule",
|
|
||||||
"transform",
|
|
||||||
"unicode/bidi",
|
|
||||||
"unicode/cldr",
|
|
||||||
"unicode/norm",
|
|
||||||
"unicode/rangetable",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
|
||||||
version = "v0.3.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:56b0bca90b7e5d1facf5fbdacba23e4e0ce069d25381b8e2f70ef1e7ebfb9c1a"
|
|
||||||
name = "google.golang.org/genproto"
|
|
||||||
packages = ["googleapis/rpc/status"]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "94acd270e44e65579b9ee3cdab25034d33fed608"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:ab8e92d746fb5c4c18846b0879842ac8e53b3d352449423d0924a11f1020ae1b"
|
|
||||||
name = "google.golang.org/grpc"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"balancer",
|
|
||||||
"balancer/base",
|
|
||||||
"balancer/roundrobin",
|
|
||||||
"codes",
|
|
||||||
"connectivity",
|
|
||||||
"credentials",
|
|
||||||
"encoding",
|
|
||||||
"encoding/proto",
|
|
||||||
"grpclog",
|
|
||||||
"internal",
|
|
||||||
"internal/backoff",
|
|
||||||
"internal/channelz",
|
|
||||||
"internal/envconfig",
|
|
||||||
"internal/grpcrand",
|
|
||||||
"internal/transport",
|
|
||||||
"keepalive",
|
|
||||||
"metadata",
|
|
||||||
"naming",
|
|
||||||
"peer",
|
|
||||||
"resolver",
|
|
||||||
"resolver/dns",
|
|
||||||
"resolver/passthrough",
|
|
||||||
"stats",
|
|
||||||
"status",
|
|
||||||
"tap",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "8dea3dc473e90c8179e519d91302d0597c0ca1d1"
|
|
||||||
version = "v1.15.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "v2"
|
|
||||||
digest = "1:5bb148b78468350091db2ffbb2370f35cc6dcd74d9378a31b1c7b86ff7528f08"
|
|
||||||
name = "gopkg.in/tomb.v2"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "d5d1b5820637886def9eef33e03a27a9f166942c"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202"
|
|
||||||
name = "gopkg.in/yaml.v2"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
|
||||||
version = "v2.2.1"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
input-imports = [
|
|
||||||
"github.com/golang/glog",
|
|
||||||
"github.com/golang/protobuf/ptypes",
|
|
||||||
"github.com/golang/protobuf/ptypes/any",
|
|
||||||
"github.com/osrg/gobgp/api",
|
|
||||||
"github.com/osrg/gobgp/pkg/server",
|
|
||||||
"gopkg.in/yaml.v2",
|
|
||||||
]
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
||||||
42
Gopkg.toml
42
Gopkg.toml
@@ -1,42 +0,0 @@
|
|||||||
# Gopkg.toml example
|
|
||||||
#
|
|
||||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
|
||||||
# for detailed Gopkg.toml documentation.
|
|
||||||
#
|
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
|
||||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project"
|
|
||||||
# version = "1.0.0"
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project2"
|
|
||||||
# branch = "dev"
|
|
||||||
# source = "github.com/myfork/project2"
|
|
||||||
#
|
|
||||||
# [[override]]
|
|
||||||
# name = "github.com/x/y"
|
|
||||||
# version = "2.4.0"
|
|
||||||
#
|
|
||||||
# [prune]
|
|
||||||
# non-go = false
|
|
||||||
# go-tests = true
|
|
||||||
# unused-packages = true
|
|
||||||
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/golang/glog"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/golang/protobuf"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/osrg/gobgp"
|
|
||||||
|
|
||||||
[prune]
|
|
||||||
go-tests = true
|
|
||||||
unused-packages = true
|
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 mayuresh82
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
15
Makefile
15
Makefile
@@ -1,23 +1,16 @@
|
|||||||
.PHONY: all gocast test
|
.PHONY: all gocast test
|
||||||
|
|
||||||
all:
|
all:
|
||||||
$(MAKE) deps
|
|
||||||
$(MAKE) gocast
|
$(MAKE) gocast
|
||||||
|
|
||||||
deps:
|
|
||||||
go get -u golang.org/x/lint/golint
|
|
||||||
go get -u github.com/golang/dep/cmd/dep
|
|
||||||
dep ensure
|
|
||||||
|
|
||||||
gocast:
|
gocast:
|
||||||
go build .
|
go build -mod=vendor .
|
||||||
|
|
||||||
debug:
|
debug:
|
||||||
dep ensure
|
go build -mod=vendor -race .
|
||||||
go build -race .
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -v -race -short -failfast ./...
|
go test -v -race -short -failfast -mod=vendor ./...
|
||||||
|
|
||||||
linux:
|
linux:
|
||||||
GOOS=linux GOARCH=amd64 go build -o gocast_linux .
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o gocast -mod=vendor .
|
||||||
|
|||||||
35
README.md
35
README.md
@@ -3,6 +3,11 @@
|
|||||||
Gocast is a tool that does controller BGP route advertisements from a host. It runs custom defined healthchecks and announces or withdraws routes (most commonly VIPs or Virtual IPs) to a BGP peer.
|
Gocast is a tool that does controller BGP route advertisements from a host. It runs custom defined healthchecks and announces or withdraws routes (most commonly VIPs or Virtual IPs) to a BGP peer.
|
||||||
The most common use case for this is anycast (vip) based load balancing for infrastructure services such as DNS, Syslog etc where several instances are available in geographically diverse regions that announce the same anycast VIP, and clients then get sent to the closest instance.
|
The most common use case for this is anycast (vip) based load balancing for infrastructure services such as DNS, Syslog etc where several instances are available in geographically diverse regions that announce the same anycast VIP, and clients then get sent to the closest instance.
|
||||||
|
|
||||||
|
For some practical examples and more details, check out this blog post : https://mayuresh82.github.io/2020/11/28/automatic_service_discovery_anycast/
|
||||||
|
|
||||||
|
# Looking for code reviewers
|
||||||
|
If you are interested in being a reviewer and/or co-maintainer, please reach out to @mayuresh82 !
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
Use the docker container at mayuresh82/gocast or compile from source:
|
Use the docker container at mayuresh82/gocast or compile from source:
|
||||||
|
|
||||||
@@ -29,7 +34,7 @@ http://gocast-addr/register?name=<appName>&vip=<addr/mask>&monitor=port:tcp:5000
|
|||||||
```
|
```
|
||||||
Multiple monitors can be defined and the healthcheck succeeds only when all the monitors pass.
|
Multiple monitors can be defined and the healthcheck succeeds only when all the monitors pass.
|
||||||
|
|
||||||
2. Custom defined apps in config.yaml. See the example config.yaml for syntax examples ( not supported yet)
|
2. Custom defined apps in config.yaml. See the example config.yaml for syntax examples
|
||||||
|
|
||||||
3. Consul based auto-discovery (see below)
|
3. Consul based auto-discovery (see below)
|
||||||
|
|
||||||
@@ -50,10 +55,34 @@ GoCast supports consul for automatic service discovery and healthchecking. For t
|
|||||||
|
|
||||||
If `gocast_monitor=consul` is specified, then GoCast uses the defined healthchecks in consul as the health monitors for the service.
|
If `gocast_monitor=consul` is specified, then GoCast uses the defined healthchecks in consul as the health monitors for the service.
|
||||||
|
|
||||||
|
If `gocast_nat=protocol:listenPort:destinationPort` is specified, then GoCast will create NAT rules, via iptables, and map traffic destined to the assigned VIP and the specified `listenPort` to the physical IP and `destinationPort`.
|
||||||
|
|
||||||
|
Example: `gocast_nat=tcp:53:8053` and `gocast_nat=udp:53:8053`
|
||||||
|
|
||||||
|
Alternatively, if `gocast_nat=protocol:port` is specified, then GoCast will create NAT rules, via iptables, and map traffic destined to the assigned VIP and the specified `port` to the physical IP and `port`.
|
||||||
|
|
||||||
|
Example: `gocast_nat=tcp:53` and `gocast_nat=udp:53`
|
||||||
|
|
||||||
|
## Configuration Reload
|
||||||
|
|
||||||
|
GoCast supports dynamic configuration reloading without service restart. Send a `SIGHUP` signal to reload the configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kill -HUP $(pidof gocast)
|
||||||
|
```
|
||||||
|
|
||||||
|
**What gets reloaded:**
|
||||||
|
- BGP configuration (peers, AS numbers, MD5 passwords, communities)
|
||||||
|
- Application definitions (add/remove/update apps)
|
||||||
|
- Agent settings (Consul, timers, intervals)
|
||||||
|
|
||||||
|
**Important:** Reloading BGP configuration causes existing BGP sessions to be restarted, resulting in brief routing interruption. Routes are automatically re-announced after reload.
|
||||||
|
Consul-discovered apps are not removed during reload.
|
||||||
|
|
||||||
## Docker support
|
## Docker support
|
||||||
The docker image at mayuresh82/gocast can be used to run GoCast inside a container. In order for GoCast to manipulate the host network stack correctly, the container needs to run with NET_ADMIN capablity and host mode networking. For example:
|
The docker image at mayuresh82/gocast can be used to run GoCast inside a container. In order for GoCast to manipulate the host network stack correctly, the container needs to run with NET_ADMIN capablity and host mode networking. For example:
|
||||||
```
|
```
|
||||||
docker run -d --cap=NET_ADMIN --net=host mayuresh82/gocast --config=/path/to/config.yaml
|
docker run -d --cap-add=NET_ADMIN --net=host -v /path/to/host-config:/path/to/container-config mayuresh82/gocast -config=/path/to/config.yaml -logtostderr
|
||||||
```
|
```
|
||||||
|
|
||||||
**Caveats and workarounds**
|
**Caveats and workarounds**
|
||||||
@@ -63,7 +92,7 @@ Certain orchestration solutions such as Nomad run the docker containers with pub
|
|||||||
|
|
||||||
- Start the service container in host networking mode OR
|
- Start the service container in host networking mode OR
|
||||||
|
|
||||||
- Register NAT rules for your service with GoCast for the required protocol/port(s). GoCast will then create iptables NAT rules that map traffic destined to the assigned VIP to the physical IP address. This is achieved by adding the `nat=protocol:port` tag(s) in consul or the http query.
|
- Register NAT rules for your service with GoCast for the required protocol/port(s). GoCast will then create iptables NAT rules that map traffic destined to the assigned VIP to the physical IP address. This is achieved by adding the `nat=protocol:listenPort:destinationPort` in the http query or `gocast_nat=protocol:listenPort:destinationPort` tag(s) in consul, as shown in the Consul integration section above.
|
||||||
|
|
||||||
**Why not just use ExaBGP or something similar ?**
|
**Why not just use ExaBGP or something similar ?**
|
||||||
|
|
||||||
|
|||||||
28
config.yaml
28
config.yaml
@@ -9,14 +9,42 @@ agent:
|
|||||||
consul_addr: https://consul
|
consul_addr: https://consul
|
||||||
# interval to query consul for app discovery
|
# interval to query consul for app discovery
|
||||||
consul_query_interval: 5m
|
consul_query_interval: 5m
|
||||||
|
# token to authenticate client if consul requires it
|
||||||
|
consul_token: 00000000-0000-0000-0000-000000000000
|
||||||
|
|
||||||
bgp:
|
bgp:
|
||||||
local_as: 12345
|
local_as: 12345
|
||||||
remote_as: 6789
|
remote_as: 6789
|
||||||
# override the peer IP to use instead of auto discovering
|
# override the peer IP to use instead of auto discovering
|
||||||
peer_ip: 10.10.10.1
|
peer_ip: 10.10.10.1
|
||||||
|
|
||||||
|
# Alternatively, define multiple BGP peers for redundancy
|
||||||
|
#peers:
|
||||||
|
# - peer_ip: 10.10.10.1
|
||||||
|
# peer_as: 6789
|
||||||
|
# communities:
|
||||||
|
# - 100:100
|
||||||
|
# - 200:200
|
||||||
|
# md5_env_var: GOCAST_BGP_PEER1_PASSWORD # optional. Set via: export GOCAST_BGP_PEER1_PASSWORD="secret"
|
||||||
|
# - peer_ip: 10.10.10.2
|
||||||
|
# peer_as: 6789
|
||||||
|
# communities:
|
||||||
|
# - 100:101
|
||||||
|
# - 200:201
|
||||||
|
# multi_hop: true # optional
|
||||||
|
# md5_password: "secret123" # optional
|
||||||
|
|
||||||
communities:
|
communities:
|
||||||
- asn:nnnn
|
- asn:nnnn
|
||||||
- asn:nnnn
|
- asn:nnnn
|
||||||
origin: igp
|
origin: igp
|
||||||
|
|
||||||
|
# optional list of apps to register on startup
|
||||||
|
apps:
|
||||||
|
- name: app1
|
||||||
|
vip: 1.1.1.1/32
|
||||||
|
vip_config:
|
||||||
|
# additional per VIP BGP communities
|
||||||
|
bgp_communities: [ aaaa:bbbb ]
|
||||||
|
monitors:
|
||||||
|
- port:tcp:5000
|
||||||
|
|||||||
@@ -1,34 +1,62 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/golang/glog"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type AgentConfig struct {
|
||||||
Agent struct {
|
|
||||||
ListenAddr string `yaml:"listen_addr"`
|
ListenAddr string `yaml:"listen_addr"`
|
||||||
MonitorInterval time.Duration `yaml:"monitor_interval"`
|
MonitorInterval time.Duration `yaml:"monitor_interval"`
|
||||||
CleanupTimer time.Duration `yaml:"cleanup_timer"`
|
CleanupTimer time.Duration `yaml:"cleanup_timer"`
|
||||||
ConsulAddr string `yaml:"consul_addr"`
|
ConsulAddr string `yaml:"consul_addr"`
|
||||||
ConsulQueryInterval time.Duration `yaml:"consul_query_interval"`
|
ConsulQueryInterval time.Duration `yaml:"consul_query_interval"`
|
||||||
|
ConsulToken string `yaml:"consul_token"`
|
||||||
}
|
}
|
||||||
Bgp struct {
|
|
||||||
LocalAS int `yaml:"local_as"`
|
type PeerConfig struct {
|
||||||
PeerAS int `yaml:"peer_as"`
|
|
||||||
PeerIP string `yaml:"peer_ip"`
|
PeerIP string `yaml:"peer_ip"`
|
||||||
|
PeerAS int `yaml:"peer_as"`
|
||||||
|
MultiHop *bool `yaml:"multi_hop,omitempty"`
|
||||||
|
Communities []string `yaml:"communities,omitempty"`
|
||||||
|
MD5Password string `yaml:"md5_password,omitempty"`
|
||||||
|
MD5EnvVar string `yaml:"md5_env_var,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BgpConfig struct {
|
||||||
|
LocalAS int `yaml:"local_as"`
|
||||||
|
LocalIP string `yaml:"local_ip"`
|
||||||
|
// Legacy single-peer config (deprecated but supported for backward compatibility)
|
||||||
|
PeerAS int `yaml:"peer_as,omitempty"`
|
||||||
|
PeerIP string `yaml:"peer_ip,omitempty"`
|
||||||
|
// New multi-peer config
|
||||||
|
Peers []PeerConfig `yaml:"peers,omitempty"`
|
||||||
Communities []string
|
Communities []string
|
||||||
Origin string
|
Origin string
|
||||||
}
|
}
|
||||||
Apps []struct {
|
|
||||||
|
type VipConfig struct {
|
||||||
|
// per VIP BGP communities to announce. This is in addition to the
|
||||||
|
// global config
|
||||||
|
BgpCommunities []string `yaml:"bgp_communities"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppConfig struct {
|
||||||
Name string
|
Name string
|
||||||
Vip string
|
Vip string
|
||||||
|
VipConfig VipConfig `yaml:"vip_config"`
|
||||||
Monitors []string
|
Monitors []string
|
||||||
Nats []string
|
Nats []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Agent AgentConfig
|
||||||
|
Bgp BgpConfig
|
||||||
|
Apps []AppConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetConfig(file string) *Config {
|
func GetConfig(file string) *Config {
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/golang/glog"
|
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/mayuresh82/gocast/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MonitorType int
|
type MonitorType int
|
||||||
@@ -50,9 +52,11 @@ func (m Monitors) Contains(elem *Monitor) bool {
|
|||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
Name string
|
Name string
|
||||||
Vip *net.IPNet
|
Vip *Route
|
||||||
|
VipConfig config.VipConfig
|
||||||
Monitors Monitors
|
Monitors Monitors
|
||||||
Nats []string
|
Nats []string
|
||||||
|
Source string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) Equal(other *App) bool {
|
func (a *App) Equal(other *App) bool {
|
||||||
@@ -64,19 +68,25 @@ func (a *App) Equal(other *App) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return a.Name == other.Name && a.Vip.String() == other.Vip.String()
|
return a.Name == other.Name && a.Vip.Net.String() == other.Vip.Net.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApp(appName, vip string, monitors []string, nats []string) (*App, error) {
|
func (a *App) String() string {
|
||||||
|
return fmt.Sprintf("Name: %s, Vip: %s, VipConf: %v, Monitors: %v, Nats: %v, Source: %s",
|
||||||
|
a.Name, a.Vip.Net.String(), a.VipConfig, a.Monitors, a.Nats, a.Source)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewApp(appName, vip string, vipConfig config.VipConfig, monitors []string, nats []string, source string) (*App, error) {
|
||||||
if appName == "" {
|
if appName == "" {
|
||||||
return nil, fmt.Errorf("Invalid app name")
|
return nil, fmt.Errorf("Invalid app name")
|
||||||
}
|
}
|
||||||
app := &App{Name: appName, Nats: nats}
|
app := &App{Name: appName, Nats: nats, Source: source}
|
||||||
_, ipnet, err := net.ParseCIDR(vip)
|
_, ipnet, err := net.ParseCIDR(vip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Invalid VIP specified, need ip/mask")
|
return nil, fmt.Errorf("Invalid VIP specified, need ip/mask")
|
||||||
}
|
}
|
||||||
app.Vip = ipnet
|
app.Vip = &Route{Net: ipnet, Communities: vipConfig.BgpCommunities}
|
||||||
|
app.VipConfig = vipConfig
|
||||||
for _, m := range monitors {
|
for _, m := range monitors {
|
||||||
// valid monitor formats:
|
// valid monitor formats:
|
||||||
// "port:tcp:123" , "exec:/local/check.sh", "consul"
|
// "port:tcp:123" , "exec:/local/check.sh", "consul"
|
||||||
|
|||||||
38
controller/app_test.go
Normal file
38
controller/app_test.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mayuresh82/gocast/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppParsing(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
app1, err := NewApp("app1", "1.1.1.1/32", config.VipConfig{}, []string{"port:tcp:123"}, []string{}, "")
|
||||||
|
a.Nil(err)
|
||||||
|
app2, err := NewApp("app1", "1.1.1.1/32", config.VipConfig{BgpCommunities: []string{"111:222"}}, []string{"port:tcp:123"}, []string{}, "")
|
||||||
|
a.Nil(err)
|
||||||
|
app3, err := NewApp("app3", "2.2.2.2/32", config.VipConfig{}, []string{"exec:/bin/testme"}, []string{}, "")
|
||||||
|
a.Nil(err)
|
||||||
|
|
||||||
|
a.Equal("1.1.1.1/32", app1.Vip.Net.String())
|
||||||
|
a.Equal(Monitor_PORT, app1.Monitors[0].Type)
|
||||||
|
a.Equal("123", app1.Monitors[0].Port)
|
||||||
|
a.Equal("tcp", app1.Monitors[0].Protocol)
|
||||||
|
a.Equal(config.VipConfig{}, app1.VipConfig)
|
||||||
|
|
||||||
|
a.Equal(true, app1.Equal(app2))
|
||||||
|
|
||||||
|
a.Equal("111:222", app2.Vip.Communities[0])
|
||||||
|
|
||||||
|
a.Equal(Monitor_EXEC, app3.Monitors[0].Type)
|
||||||
|
a.Equal("/bin/testme", app3.Monitors[0].Cmd)
|
||||||
|
|
||||||
|
// test errors
|
||||||
|
_, err = NewApp("app4", "4.4.4.4", config.VipConfig{}, []string{}, []string{}, "")
|
||||||
|
a.NotNil(err)
|
||||||
|
|
||||||
|
_, err = NewApp("app4", "4.4.4.4/32", config.VipConfig{}, []string{"port:abcd::1023"}, []string{}, "")
|
||||||
|
a.NotNil(err)
|
||||||
|
}
|
||||||
@@ -3,86 +3,159 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/golang/protobuf/ptypes"
|
"github.com/golang/protobuf/ptypes"
|
||||||
"github.com/golang/protobuf/ptypes/any"
|
"github.com/golang/protobuf/ptypes/any"
|
||||||
c "github.com/mayuresh82/gocast/config"
|
c "github.com/mayuresh82/gocast/config"
|
||||||
api "github.com/osrg/gobgp/api"
|
api "github.com/osrg/gobgp/api"
|
||||||
gobgp "github.com/osrg/gobgp/pkg/server"
|
gobgp "github.com/osrg/gobgp/pkg/server"
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Route struct {
|
||||||
|
Net *net.IPNet
|
||||||
|
Communities []string
|
||||||
|
}
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
peerAS int
|
localAS int
|
||||||
localIP, peerIP net.IP
|
localIP net.IP
|
||||||
|
peers []c.PeerConfig
|
||||||
communities []string
|
communities []string
|
||||||
origin uint32
|
origin uint32
|
||||||
s *gobgp.BgpServer
|
s *gobgp.BgpServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewController(config *c.Config) (*Controller, error) {
|
func NewController(config c.BgpConfig) (*Controller, error) {
|
||||||
c := &Controller{}
|
ctrl := &Controller{}
|
||||||
if config.Bgp.PeerIP == "" {
|
var gw net.IP
|
||||||
gw, err := gateway()
|
var err error
|
||||||
|
|
||||||
|
// Normalize config: convert legacy single-peer to new multi-peer format
|
||||||
|
peers := config.Peers
|
||||||
|
if len(peers) == 0 {
|
||||||
|
// Backward compatibility: convert legacy config
|
||||||
|
if config.PeerIP != "" {
|
||||||
|
// Explicit peer IP configured
|
||||||
|
peers = []c.PeerConfig{{
|
||||||
|
PeerIP: config.PeerIP,
|
||||||
|
PeerAS: config.PeerAS,
|
||||||
|
}}
|
||||||
|
} else {
|
||||||
|
// No peer IP configured - use default gateway
|
||||||
|
gw, err = gateway()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to get gateway ip: %v", err)
|
||||||
|
}
|
||||||
|
peers = []c.PeerConfig{{
|
||||||
|
PeerIP: gw.String(),
|
||||||
|
PeerAS: config.PeerAS,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine local IP
|
||||||
|
if config.LocalIP == "" {
|
||||||
|
// Use first peer to determine local IP
|
||||||
|
firstPeerIP := net.ParseIP(peers[0].PeerIP)
|
||||||
|
if firstPeerIP == nil {
|
||||||
|
gw, err = gateway()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to get gw ip: %v", err)
|
||||||
|
}
|
||||||
|
firstPeerIP = gw
|
||||||
|
}
|
||||||
|
gw, err = via(firstPeerIP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to get gw ip: %v", err)
|
||||||
|
}
|
||||||
|
ctrl.localIP, err = localAddress(gw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.peerIP = gw
|
|
||||||
} else {
|
} else {
|
||||||
c.peerIP = net.ParseIP(config.Bgp.PeerIP)
|
ctrl.localIP = net.ParseIP(config.LocalIP)
|
||||||
}
|
}
|
||||||
if c.peerIP == nil {
|
|
||||||
return nil, fmt.Errorf("Unable to get peer IP")
|
ctrl.localAS = config.LocalAS
|
||||||
}
|
ctrl.peers = peers
|
||||||
c.communities = config.Bgp.Communities
|
ctrl.communities = config.Communities
|
||||||
switch config.Bgp.Origin {
|
|
||||||
|
switch config.Origin {
|
||||||
case "igp":
|
case "igp":
|
||||||
c.origin = 0
|
ctrl.origin = 0
|
||||||
case "egp":
|
case "egp":
|
||||||
c.origin = 1
|
ctrl.origin = 1
|
||||||
case "unknown":
|
case "unknown":
|
||||||
c.origin = 2
|
ctrl.origin = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
s := gobgp.NewBgpServer()
|
s := gobgp.NewBgpServer()
|
||||||
go s.Serve()
|
go s.Serve()
|
||||||
localAddr, err := localAddress(c.peerIP)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.localIP = localAddr
|
|
||||||
if err := s.StartBgp(context.Background(), &api.StartBgpRequest{
|
if err := s.StartBgp(context.Background(), &api.StartBgpRequest{
|
||||||
Global: &api.Global{
|
Global: &api.Global{
|
||||||
As: uint32(config.Bgp.LocalAS),
|
As: uint32(config.LocalAS),
|
||||||
RouterId: localAddr.String(),
|
RouterId: ctrl.localIP.String(),
|
||||||
ListenPort: -1, // gobgp won't listen on tcp:179
|
ListenPort: -1, // gobgp won't listen on tcp:179
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, fmt.Errorf("Unable to start bgp: %v", err)
|
return nil, fmt.Errorf("Unable to start bgp: %v", err)
|
||||||
}
|
}
|
||||||
c.s = s
|
ctrl.s = s
|
||||||
c.peerAS = config.Bgp.PeerAS
|
|
||||||
return c, nil
|
return ctrl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) AddPeer(peer string) error {
|
func (c *Controller) addPeer(peer *c.PeerConfig) error {
|
||||||
n := &api.Peer{
|
n := &api.Peer{
|
||||||
Conf: &api.PeerConf{
|
Conf: &api.PeerConf{
|
||||||
NeighborAddress: peer,
|
NeighborAddress: peer.PeerIP,
|
||||||
PeerAs: uint32(c.peerAS),
|
PeerAs: uint32(peer.PeerAS),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enable multihop only if explicitly configured
|
||||||
|
if peer.MultiHop != nil && *peer.MultiHop {
|
||||||
|
n.EbgpMultihop = &api.EbgpMultihop{Enabled: true, MultihopTtl: uint32(255)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure MD5 authentication if specified
|
||||||
|
md5Password := c.getMD5Password(peer)
|
||||||
|
if md5Password != "" {
|
||||||
|
n.Conf.AuthPassword = md5Password
|
||||||
|
}
|
||||||
|
|
||||||
return c.s.AddPeer(context.Background(), &api.AddPeerRequest{Peer: n})
|
return c.s.AddPeer(context.Background(), &api.AddPeerRequest{Peer: n})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) getApiPath(route *net.IPNet) *api.Path {
|
// getMD5Password retrieves the MD5 password from config or environment variable
|
||||||
|
func (c *Controller) getMD5Password(peer *c.PeerConfig) string {
|
||||||
|
// Priority 1: Check for environment variable
|
||||||
|
if peer.MD5EnvVar != "" {
|
||||||
|
if password := os.Getenv(peer.MD5EnvVar); password != "" {
|
||||||
|
return password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 2: Use password from config file
|
||||||
|
if peer.MD5Password != "" {
|
||||||
|
return peer.MD5Password
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) getApiPath(route *Route, peer *c.PeerConfig) *api.Path {
|
||||||
afi := api.Family_AFI_IP
|
afi := api.Family_AFI_IP
|
||||||
if route.IP.To4() == nil {
|
if route.Net.IP.To4() == nil {
|
||||||
afi = api.Family_AFI_IP6
|
afi = api.Family_AFI_IP6
|
||||||
}
|
}
|
||||||
prefixlen, _ := route.Mask.Size()
|
prefixlen, _ := route.Net.Mask.Size()
|
||||||
nlri, _ := ptypes.MarshalAny(&api.IPAddressPrefix{
|
nlri, _ := ptypes.MarshalAny(&api.IPAddressPrefix{
|
||||||
Prefix: route.IP.String(),
|
Prefix: route.Net.IP.String(),
|
||||||
PrefixLen: uint32(prefixlen),
|
PrefixLen: uint32(prefixlen),
|
||||||
})
|
})
|
||||||
a1, _ := ptypes.MarshalAny(&api.OriginAttribute{
|
a1, _ := ptypes.MarshalAny(&api.OriginAttribute{
|
||||||
@@ -91,8 +164,15 @@ func (c *Controller) getApiPath(route *net.IPNet) *api.Path {
|
|||||||
a2, _ := ptypes.MarshalAny(&api.NextHopAttribute{
|
a2, _ := ptypes.MarshalAny(&api.NextHopAttribute{
|
||||||
NextHop: c.localIP.String(),
|
NextHop: c.localIP.String(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Merge communities: global + per-peer + per-route
|
||||||
|
var allCommunities []string
|
||||||
|
allCommunities = append(allCommunities, c.communities...)
|
||||||
|
allCommunities = append(allCommunities, peer.Communities...)
|
||||||
|
allCommunities = append(allCommunities, route.Communities...)
|
||||||
|
|
||||||
var communities []uint32
|
var communities []uint32
|
||||||
for _, comm := range c.communities {
|
for _, comm := range allCommunities {
|
||||||
communities = append(communities, convertCommunity(comm))
|
communities = append(communities, convertCommunity(comm))
|
||||||
}
|
}
|
||||||
a3, _ := ptypes.MarshalAny(&api.CommunitiesAttribute{
|
a3, _ := ptypes.MarshalAny(&api.CommunitiesAttribute{
|
||||||
@@ -101,57 +181,111 @@ func (c *Controller) getApiPath(route *net.IPNet) *api.Path {
|
|||||||
attrs := []*any.Any{a1, a2, a3}
|
attrs := []*any.Any{a1, a2, a3}
|
||||||
return &api.Path{
|
return &api.Path{
|
||||||
Family: &api.Family{Afi: afi, Safi: api.Family_SAFI_UNICAST},
|
Family: &api.Family{Afi: afi, Safi: api.Family_SAFI_UNICAST},
|
||||||
AnyNlri: nlri,
|
Nlri: nlri,
|
||||||
AnyPattrs: attrs,
|
Pattrs: attrs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) Announce(route *net.IPNet) error {
|
func (c *Controller) Announce(route *Route) error {
|
||||||
peers, err := c.s.ListPeer(context.Background(), &api.ListPeerRequest{})
|
var errs []error
|
||||||
if err != nil {
|
|
||||||
return err
|
for i := range c.peers {
|
||||||
}
|
peer := &c.peers[i]
|
||||||
|
|
||||||
|
// Check if peer exists
|
||||||
var found bool
|
var found bool
|
||||||
for _, p := range peers {
|
err := c.s.ListPeer(context.Background(), &api.ListPeerRequest{}, func(p *api.Peer) {
|
||||||
if p.Conf.NeighborAddress == c.peerIP.String() {
|
if p.Conf.NeighborAddress == peer.PeerIP {
|
||||||
found = true
|
found = true
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("peer %s: list error: %v", peer.PeerIP, err))
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add peer if not found
|
||||||
if !found {
|
if !found {
|
||||||
if err := c.AddPeer(c.peerIP.String()); err != nil {
|
if err := c.addPeer(peer); err != nil {
|
||||||
return err
|
errs = append(errs, fmt.Errorf("peer %s: add error: %v", peer.PeerIP, err))
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err = c.s.AddPath(context.Background(), &api.AddPathRequest{Path: c.getApiPath(route)})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) Withdraw(route *net.IPNet) error {
|
// Announce route to this peer
|
||||||
return c.s.DeletePath(context.Background(), &api.DeletePathRequest{Path: c.getApiPath(route)})
|
path := c.getApiPath(route, peer)
|
||||||
|
if _, err := c.s.AddPath(context.Background(), &api.AddPathRequest{Path: path}); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("peer %s: announce error: %v", peer.PeerIP, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) PeerInfo() (*api.Peer, error) {
|
// Return aggregated errors if any peer failed
|
||||||
peers, err := c.s.ListPeer(context.Background(), &api.ListPeerRequest{})
|
if len(errs) > 0 {
|
||||||
|
return fmt.Errorf("announcement errors: %v", errs)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Withdraw(route *Route) error {
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
for i := range c.peers {
|
||||||
|
peer := &c.peers[i]
|
||||||
|
path := c.getApiPath(route, peer)
|
||||||
|
if err := c.s.DeletePath(context.Background(), &api.DeletePathRequest{Path: path}); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("peer %s: withdraw error: %v", peer.PeerIP, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return aggregated errors if any peer failed
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return fmt.Errorf("withdrawal errors: %v", errs)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) PeerInfo() ([]*api.Peer, error) {
|
||||||
|
var peers []*api.Peer
|
||||||
|
peerMap := make(map[string]bool)
|
||||||
|
|
||||||
|
// Build map of configured peer IPs
|
||||||
|
for _, peer := range c.peers {
|
||||||
|
peerMap[peer.PeerIP] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect info for all configured peers
|
||||||
|
err := c.s.ListPeer(context.Background(), &api.ListPeerRequest{}, func(p *api.Peer) {
|
||||||
|
if peerMap[p.Conf.NeighborAddress] {
|
||||||
|
peers = append(peers, p)
|
||||||
|
}
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, p := range peers {
|
return peers, nil
|
||||||
if p.Conf.NeighborAddress == c.peerIP.String() {
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) Shutdown() error {
|
func (c *Controller) Shutdown() error {
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
// Shutdown all peer sessions
|
||||||
|
for _, peer := range c.peers {
|
||||||
if err := c.s.ShutdownPeer(context.Background(), &api.ShutdownPeerRequest{
|
if err := c.s.ShutdownPeer(context.Background(), &api.ShutdownPeerRequest{
|
||||||
Address: c.peerIP.String(),
|
Address: peer.PeerIP,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
errs = append(errs, fmt.Errorf("peer %s: shutdown error: %v", peer.PeerIP, err))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop BGP server
|
||||||
if err := c.s.StopBgp(context.Background(), &api.StopBgpRequest{}); err != nil {
|
if err := c.s.StopBgp(context.Background(), &api.StopBgpRequest{}); err != nil {
|
||||||
return err
|
errs = append(errs, fmt.Errorf("stop bgp error: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return fmt.Errorf("shutdown errors: %v", errs)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
679
controller/bgp_test.go
Normal file
679
controller/bgp_test.go
Normal file
@@ -0,0 +1,679 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/ptypes"
|
||||||
|
"github.com/mayuresh82/gocast/config"
|
||||||
|
api "github.com/osrg/gobgp/api"
|
||||||
|
gobgp "github.com/osrg/gobgp/pkg/server"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BgpListener struct {
|
||||||
|
s *gobgp.BgpServer
|
||||||
|
recvdPaths chan string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBgpListener starts a local BGP server for testing purposes
|
||||||
|
func NewBgpListener(localAS int) (*BgpListener, error) {
|
||||||
|
s := gobgp.NewBgpServer()
|
||||||
|
go s.Serve()
|
||||||
|
if err := s.StartBgp(context.Background(), &api.StartBgpRequest{
|
||||||
|
Global: &api.Global{
|
||||||
|
As: uint32(localAS),
|
||||||
|
RouterId: "100.100.100.100",
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to start bgp: %v", err)
|
||||||
|
}
|
||||||
|
n := &BgpListener{s: s, recvdPaths: make(chan string)}
|
||||||
|
err := s.MonitorTable(context.Background(), &api.MonitorTableRequest{TableType: api.TableType_ADJ_IN}, func(p *api.Path) {
|
||||||
|
// assumes v4 only paths !
|
||||||
|
var value ptypes.DynamicAny
|
||||||
|
if err := ptypes.UnmarshalAny(p.Nlri, &value); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nlri := value.Message.(*api.IPAddressPrefix)
|
||||||
|
n.recvdPaths <- fmt.Sprintf("%s/%d", nlri.Prefix, nlri.PrefixLen)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := s.AddPeer(context.Background(), &api.AddPeerRequest{
|
||||||
|
Peer: &api.Peer{
|
||||||
|
Conf: &api.PeerConf{
|
||||||
|
NeighborAddress: "127.0.0.1",
|
||||||
|
PeerAs: 11111,
|
||||||
|
},
|
||||||
|
Transport: &api.Transport{PassiveMode: true},
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *BgpListener) Shutdown() error {
|
||||||
|
if err := l.s.StopBgp(context.Background(), &api.StopBgpRequest{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test tests the BGP controller talking to a local BGP
|
||||||
|
// listener. It needs a few seconds to pass and *may* time out
|
||||||
|
// if the test timeouts are very small. It also needs to be run as
|
||||||
|
// root (sudo)
|
||||||
|
// Disabling this test in CI currently due to https://github.com/osrg/gobgp/issues/2366
|
||||||
|
func TestBgpNew(t *testing.T) {
|
||||||
|
if os.Getenv("CI") != "" {
|
||||||
|
t.Skip("Skipping testing in CI environment")
|
||||||
|
}
|
||||||
|
listener, err := NewBgpListener(22222)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer listener.Shutdown()
|
||||||
|
a := assert.New(t)
|
||||||
|
c := config.BgpConfig{
|
||||||
|
LocalAS: 11111,
|
||||||
|
PeerAS: 22222,
|
||||||
|
PeerIP: "127.0.0.1",
|
||||||
|
LocalIP: "192.168.1.100",
|
||||||
|
Communities: []string{"100:100"},
|
||||||
|
Origin: "igp",
|
||||||
|
}
|
||||||
|
ctrl, err := NewController(c)
|
||||||
|
if err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
_, ipnet, _ := net.ParseCIDR("20.30.40.0/24")
|
||||||
|
r := &Route{Net: ipnet}
|
||||||
|
if err := ctrl.Announce(r); err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
path := <-listener.recvdPaths
|
||||||
|
a.Equal("20.30.40.0/24", path)
|
||||||
|
ctrl.Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLegacyConfigConversion(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
// Test legacy single-peer config
|
||||||
|
legacyConfig := config.BgpConfig{
|
||||||
|
LocalAS: 11111,
|
||||||
|
PeerAS: 22222,
|
||||||
|
PeerIP: "10.10.10.1",
|
||||||
|
LocalIP: "192.168.1.100",
|
||||||
|
Communities: []string{"100:100"},
|
||||||
|
Origin: "igp",
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl, err := NewController(legacyConfig)
|
||||||
|
if err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
defer ctrl.Shutdown()
|
||||||
|
|
||||||
|
// Verify legacy config was converted to multi-peer format
|
||||||
|
a.Equal(1, len(ctrl.peers), "Should have exactly 1 peer")
|
||||||
|
a.Equal("10.10.10.1", ctrl.peers[0].PeerIP)
|
||||||
|
a.Equal(22222, ctrl.peers[0].PeerAS)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiPeerConfig(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
// Test new multi-peer config
|
||||||
|
multiPeerConfig := config.BgpConfig{
|
||||||
|
LocalAS: 11111,
|
||||||
|
LocalIP: "192.168.1.100",
|
||||||
|
Peers: []config.PeerConfig{
|
||||||
|
{PeerIP: "10.10.10.1", PeerAS: 22222},
|
||||||
|
{PeerIP: "10.10.10.2", PeerAS: 22222},
|
||||||
|
},
|
||||||
|
Communities: []string{"100:100"},
|
||||||
|
Origin: "igp",
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl, err := NewController(multiPeerConfig)
|
||||||
|
if err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
defer ctrl.Shutdown()
|
||||||
|
|
||||||
|
// Verify both peers are configured
|
||||||
|
a.Equal(2, len(ctrl.peers), "Should have exactly 2 peers")
|
||||||
|
a.Equal("10.10.10.1", ctrl.peers[0].PeerIP)
|
||||||
|
a.Equal("10.10.10.2", ctrl.peers[1].PeerIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultGatewayPeer(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
// Test config with no peer_ip - should use default gateway
|
||||||
|
defaultGatewayConfig := config.BgpConfig{
|
||||||
|
LocalAS: 11111,
|
||||||
|
LocalIP: "192.168.1.100",
|
||||||
|
PeerAS: 22222,
|
||||||
|
Origin: "igp",
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl, err := NewController(defaultGatewayConfig)
|
||||||
|
a.NoError(err, "Should not error when peer_ip is not specified")
|
||||||
|
if err == nil {
|
||||||
|
defer ctrl.Shutdown()
|
||||||
|
|
||||||
|
// Verify a peer was configured using gateway
|
||||||
|
a.Equal(1, len(ctrl.peers), "Should have exactly 1 peer")
|
||||||
|
a.NotEmpty(ctrl.peers[0].PeerIP, "Peer IP should be set from gateway")
|
||||||
|
a.Equal(22222, ctrl.peers[0].PeerAS, "Peer AS should match config")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommunityMerging(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
ctrl := &Controller{
|
||||||
|
localAS: 11111,
|
||||||
|
localIP: net.ParseIP("192.168.1.100"),
|
||||||
|
communities: []string{"1000:1000", "2000:2000"}, // Global
|
||||||
|
origin: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
peer := &config.PeerConfig{
|
||||||
|
PeerIP: "10.10.10.1",
|
||||||
|
PeerAS: 22222,
|
||||||
|
Communities: []string{"100:100", "200:200"}, // Per-peer
|
||||||
|
}
|
||||||
|
|
||||||
|
route := &Route{
|
||||||
|
Net: &net.IPNet{
|
||||||
|
IP: net.ParseIP("20.30.40.0"),
|
||||||
|
Mask: net.CIDRMask(24, 32),
|
||||||
|
},
|
||||||
|
Communities: []string{"5000:5000"}, // Per-route
|
||||||
|
}
|
||||||
|
|
||||||
|
path := ctrl.getApiPath(route, peer)
|
||||||
|
|
||||||
|
// Extract communities from path
|
||||||
|
var commAttr *api.CommunitiesAttribute
|
||||||
|
for _, attr := range path.Pattrs {
|
||||||
|
var dynAny ptypes.DynamicAny
|
||||||
|
if err := ptypes.UnmarshalAny(attr, &dynAny); err == nil {
|
||||||
|
if c, ok := dynAny.Message.(*api.CommunitiesAttribute); ok {
|
||||||
|
commAttr = c
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.NotNil(commAttr, "Should have communities attribute")
|
||||||
|
a.Equal(5, len(commAttr.Communities), "Should have 5 communities (2 global + 2 peer + 1 route)")
|
||||||
|
|
||||||
|
// Verify community values (converted to uint32)
|
||||||
|
expectedCommunities := []uint32{
|
||||||
|
convertCommunity("1000:1000"), // Global
|
||||||
|
convertCommunity("2000:2000"), // Global
|
||||||
|
convertCommunity("100:100"), // Per-peer
|
||||||
|
convertCommunity("200:200"), // Per-peer
|
||||||
|
convertCommunity("5000:5000"), // Per-route
|
||||||
|
}
|
||||||
|
a.Equal(expectedCommunities, commAttr.Communities)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiHopConfiguration(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
localAS int
|
||||||
|
peerAS int
|
||||||
|
multiHopPtr *bool
|
||||||
|
expectMH bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default - multihop not enabled",
|
||||||
|
localAS: 11111,
|
||||||
|
peerAS: 22222,
|
||||||
|
multiHopPtr: nil,
|
||||||
|
expectMH: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "explicit disable",
|
||||||
|
localAS: 11111,
|
||||||
|
peerAS: 22222,
|
||||||
|
multiHopPtr: boolPtr(false),
|
||||||
|
expectMH: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "explicit enable",
|
||||||
|
localAS: 11111,
|
||||||
|
peerAS: 22222,
|
||||||
|
multiHopPtr: boolPtr(true),
|
||||||
|
expectMH: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ctrl := &Controller{
|
||||||
|
localAS: tc.localAS,
|
||||||
|
localIP: net.ParseIP("192.168.1.100"),
|
||||||
|
s: gobgp.NewBgpServer(),
|
||||||
|
}
|
||||||
|
go ctrl.s.Serve()
|
||||||
|
|
||||||
|
if err := ctrl.s.StartBgp(context.Background(), &api.StartBgpRequest{
|
||||||
|
Global: &api.Global{
|
||||||
|
As: uint32(tc.localAS),
|
||||||
|
RouterId: ctrl.localIP.String(),
|
||||||
|
ListenPort: -1,
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
defer ctrl.s.StopBgp(context.Background(), &api.StopBgpRequest{})
|
||||||
|
|
||||||
|
peer := &config.PeerConfig{
|
||||||
|
PeerIP: "10.10.10.1",
|
||||||
|
PeerAS: tc.peerAS,
|
||||||
|
MultiHop: tc.multiHopPtr,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ctrl.addPeer(peer)
|
||||||
|
a.NoError(err)
|
||||||
|
|
||||||
|
// Verify multihop setting by checking peer config
|
||||||
|
var foundPeer *api.Peer
|
||||||
|
ctrl.s.ListPeer(context.Background(), &api.ListPeerRequest{}, func(p *api.Peer) {
|
||||||
|
if p.Conf.NeighborAddress == peer.PeerIP {
|
||||||
|
foundPeer = p
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
a.NotNil(foundPeer, "Peer should be added")
|
||||||
|
if tc.expectMH {
|
||||||
|
a.NotNil(foundPeer.EbgpMultihop, "Should have multihop configured")
|
||||||
|
a.True(foundPeer.EbgpMultihop.Enabled, "Multihop should be enabled")
|
||||||
|
} else {
|
||||||
|
if foundPeer.EbgpMultihop != nil {
|
||||||
|
a.False(foundPeer.EbgpMultihop.Enabled, "Multihop should not be enabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to create bool pointer
|
||||||
|
func boolPtr(b bool) *bool {
|
||||||
|
return &b
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiPeerAnnouncement(t *testing.T) {
|
||||||
|
if os.Getenv("CI") != "" {
|
||||||
|
t.Skip("Skipping testing in CI environment")
|
||||||
|
}
|
||||||
|
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
// Create two BGP listeners
|
||||||
|
listener1, err := NewBgpListener(22222)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer listener1.Shutdown()
|
||||||
|
|
||||||
|
listener2, err := NewBgpListener(33333)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer listener2.Shutdown()
|
||||||
|
|
||||||
|
// Create controller with two peers
|
||||||
|
multiPeerConfig := config.BgpConfig{
|
||||||
|
LocalAS: 11111,
|
||||||
|
LocalIP: "192.168.1.100",
|
||||||
|
Peers: []config.PeerConfig{
|
||||||
|
{PeerIP: "127.0.0.1", PeerAS: 22222},
|
||||||
|
{PeerIP: "127.0.0.1", PeerAS: 33333},
|
||||||
|
},
|
||||||
|
Communities: []string{"100:100"},
|
||||||
|
Origin: "igp",
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl, err := NewController(multiPeerConfig)
|
||||||
|
if err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
defer ctrl.Shutdown()
|
||||||
|
|
||||||
|
// Announce a route
|
||||||
|
_, ipnet, _ := net.ParseCIDR("20.30.40.0/24")
|
||||||
|
r := &Route{Net: ipnet}
|
||||||
|
if err := ctrl.Announce(r); err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify both listeners received the route
|
||||||
|
path1 := <-listener1.recvdPaths
|
||||||
|
a.Equal("20.30.40.0/24", path1)
|
||||||
|
|
||||||
|
path2 := <-listener2.recvdPaths
|
||||||
|
a.Equal("20.30.40.0/24", path2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBestEffortAnnouncement(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
// Create controller with two peers
|
||||||
|
mixedConfig := config.BgpConfig{
|
||||||
|
LocalAS: 11111,
|
||||||
|
LocalIP: "192.168.1.100",
|
||||||
|
Peers: []config.PeerConfig{
|
||||||
|
{PeerIP: "127.0.0.1", PeerAS: 22222},
|
||||||
|
{PeerIP: "127.0.0.2", PeerAS: 33333},
|
||||||
|
},
|
||||||
|
Origin: "igp",
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl, err := NewController(mixedConfig)
|
||||||
|
if err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
defer ctrl.Shutdown()
|
||||||
|
|
||||||
|
// Announce a route - both peers will be added successfully
|
||||||
|
// (they won't have actual BGP sessions established, but peers are added to GoBGP)
|
||||||
|
_, ipnet, _ := net.ParseCIDR("20.30.40.0/24")
|
||||||
|
r := &Route{Net: ipnet}
|
||||||
|
|
||||||
|
// The announcement should succeed for both peers being added
|
||||||
|
err = ctrl.Announce(r)
|
||||||
|
a.NoError(err, "Announcement should succeed for both peers")
|
||||||
|
|
||||||
|
// Verify both peers were added
|
||||||
|
peers, err := ctrl.PeerInfo()
|
||||||
|
a.NoError(err)
|
||||||
|
a.Equal(2, len(peers), "Should have both peers configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithdrawMultiplePeers(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
ctrl := &Controller{
|
||||||
|
localAS: 11111,
|
||||||
|
localIP: net.ParseIP("192.168.1.100"),
|
||||||
|
peers: []config.PeerConfig{
|
||||||
|
{PeerIP: "10.10.10.1", PeerAS: 22222},
|
||||||
|
{PeerIP: "10.10.10.2", PeerAS: 22222},
|
||||||
|
},
|
||||||
|
origin: 0,
|
||||||
|
s: gobgp.NewBgpServer(),
|
||||||
|
}
|
||||||
|
go ctrl.s.Serve()
|
||||||
|
|
||||||
|
if err := ctrl.s.StartBgp(context.Background(), &api.StartBgpRequest{
|
||||||
|
Global: &api.Global{
|
||||||
|
As: uint32(11111),
|
||||||
|
RouterId: ctrl.localIP.String(),
|
||||||
|
ListenPort: -1,
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
defer ctrl.s.StopBgp(context.Background(), &api.StopBgpRequest{})
|
||||||
|
|
||||||
|
_, ipnet, _ := net.ParseCIDR("20.30.40.0/24")
|
||||||
|
r := &Route{Net: ipnet}
|
||||||
|
|
||||||
|
// Withdraw should iterate through all peers
|
||||||
|
// This will fail because peers aren't established, but it should try both
|
||||||
|
err := ctrl.Withdraw(r)
|
||||||
|
// We expect an error but it should have tried both peers
|
||||||
|
if err != nil {
|
||||||
|
a.Contains(err.Error(), "withdrawal errors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPeerInfoMultiplePeers(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
ctrl := &Controller{
|
||||||
|
localAS: 11111,
|
||||||
|
localIP: net.ParseIP("192.168.1.100"),
|
||||||
|
peers: []config.PeerConfig{
|
||||||
|
{PeerIP: "10.10.10.1", PeerAS: 22222},
|
||||||
|
{PeerIP: "10.10.10.2", PeerAS: 33333},
|
||||||
|
},
|
||||||
|
origin: 0,
|
||||||
|
s: gobgp.NewBgpServer(),
|
||||||
|
}
|
||||||
|
go ctrl.s.Serve()
|
||||||
|
|
||||||
|
if err := ctrl.s.StartBgp(context.Background(), &api.StartBgpRequest{
|
||||||
|
Global: &api.Global{
|
||||||
|
As: uint32(11111),
|
||||||
|
RouterId: ctrl.localIP.String(),
|
||||||
|
ListenPort: -1,
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
defer ctrl.s.StopBgp(context.Background(), &api.StopBgpRequest{})
|
||||||
|
|
||||||
|
// Add peers
|
||||||
|
for i := range ctrl.peers {
|
||||||
|
ctrl.addPeer(&ctrl.peers[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get peer info
|
||||||
|
peers, err := ctrl.PeerInfo()
|
||||||
|
a.NoError(err)
|
||||||
|
a.Equal(2, len(peers), "Should return info for both peers")
|
||||||
|
|
||||||
|
// Verify peer addresses
|
||||||
|
peerAddrs := make(map[string]bool)
|
||||||
|
for _, p := range peers {
|
||||||
|
peerAddrs[p.Conf.NeighborAddress] = true
|
||||||
|
}
|
||||||
|
a.True(peerAddrs["10.10.10.1"], "Should have first peer")
|
||||||
|
a.True(peerAddrs["10.10.10.2"], "Should have second peer")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMD5Authentication(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
md5Password string
|
||||||
|
md5EnvVar string
|
||||||
|
envValue string
|
||||||
|
expectedAuth string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "MD5 password from config",
|
||||||
|
md5Password: "secret123",
|
||||||
|
md5EnvVar: "",
|
||||||
|
envValue: "",
|
||||||
|
expectedAuth: "secret123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MD5 password from environment variable",
|
||||||
|
md5Password: "",
|
||||||
|
md5EnvVar: "BGP_PEER_PASSWORD",
|
||||||
|
envValue: "env_secret456",
|
||||||
|
expectedAuth: "env_secret456",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Environment variable takes priority over config",
|
||||||
|
md5Password: "config_password",
|
||||||
|
md5EnvVar: "BGP_PEER_PASSWORD",
|
||||||
|
envValue: "env_password",
|
||||||
|
expectedAuth: "env_password",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No authentication when neither is set",
|
||||||
|
md5Password: "",
|
||||||
|
md5EnvVar: "",
|
||||||
|
envValue: "",
|
||||||
|
expectedAuth: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Config password used when env var is set but empty",
|
||||||
|
md5Password: "fallback_password",
|
||||||
|
md5EnvVar: "EMPTY_VAR",
|
||||||
|
envValue: "",
|
||||||
|
expectedAuth: "fallback_password",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
// Set environment variable if specified
|
||||||
|
if tc.md5EnvVar != "" && tc.envValue != "" {
|
||||||
|
os.Setenv(tc.md5EnvVar, tc.envValue)
|
||||||
|
defer os.Unsetenv(tc.md5EnvVar)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl := &Controller{
|
||||||
|
localAS: 11111,
|
||||||
|
localIP: net.ParseIP("192.168.1.100"),
|
||||||
|
s: gobgp.NewBgpServer(),
|
||||||
|
}
|
||||||
|
go ctrl.s.Serve()
|
||||||
|
|
||||||
|
if err := ctrl.s.StartBgp(context.Background(), &api.StartBgpRequest{
|
||||||
|
Global: &api.Global{
|
||||||
|
As: uint32(11111),
|
||||||
|
RouterId: ctrl.localIP.String(),
|
||||||
|
ListenPort: -1,
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
defer ctrl.s.StopBgp(context.Background(), &api.StopBgpRequest{})
|
||||||
|
|
||||||
|
peer := &config.PeerConfig{
|
||||||
|
PeerIP: "10.10.10.1",
|
||||||
|
PeerAS: 22222,
|
||||||
|
MD5Password: tc.md5Password,
|
||||||
|
MD5EnvVar: tc.md5EnvVar,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ctrl.addPeer(peer)
|
||||||
|
a.NoError(err)
|
||||||
|
|
||||||
|
// Verify MD5 authentication is configured correctly
|
||||||
|
var foundPeer *api.Peer
|
||||||
|
ctrl.s.ListPeer(context.Background(), &api.ListPeerRequest{}, func(p *api.Peer) {
|
||||||
|
if p.Conf.NeighborAddress == peer.PeerIP {
|
||||||
|
foundPeer = p
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
a.NotNil(foundPeer, "Peer should be added")
|
||||||
|
a.Equal(tc.expectedAuth, foundPeer.Conf.AuthPassword, "MD5 password should match expected")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetMD5Password(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
ctrl := &Controller{}
|
||||||
|
|
||||||
|
// Test 1: Environment variable takes priority
|
||||||
|
os.Setenv("TEST_BGP_PASS", "env_password")
|
||||||
|
defer os.Unsetenv("TEST_BGP_PASS")
|
||||||
|
|
||||||
|
peer := &config.PeerConfig{
|
||||||
|
MD5Password: "config_password",
|
||||||
|
MD5EnvVar: "TEST_BGP_PASS",
|
||||||
|
}
|
||||||
|
a.Equal("env_password", ctrl.getMD5Password(peer))
|
||||||
|
|
||||||
|
// Test 2: Config password when env var not set
|
||||||
|
peer2 := &config.PeerConfig{
|
||||||
|
MD5Password: "config_only",
|
||||||
|
MD5EnvVar: "NONEXISTENT_VAR",
|
||||||
|
}
|
||||||
|
a.Equal("config_only", ctrl.getMD5Password(peer2))
|
||||||
|
|
||||||
|
// Test 3: Empty string when nothing is set
|
||||||
|
peer3 := &config.PeerConfig{}
|
||||||
|
a.Equal("", ctrl.getMD5Password(peer3))
|
||||||
|
|
||||||
|
// Test 4: Only env var specified
|
||||||
|
os.Setenv("ANOTHER_PASS", "another_env_password")
|
||||||
|
defer os.Unsetenv("ANOTHER_PASS")
|
||||||
|
|
||||||
|
peer4 := &config.PeerConfig{
|
||||||
|
MD5EnvVar: "ANOTHER_PASS",
|
||||||
|
}
|
||||||
|
a.Equal("another_env_password", ctrl.getMD5Password(peer4))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiPeerWithMD5(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
// Set environment variables for testing
|
||||||
|
os.Setenv("PEER1_PASSWORD", "peer1_secret")
|
||||||
|
os.Setenv("PEER2_PASSWORD", "peer2_secret")
|
||||||
|
defer os.Unsetenv("PEER1_PASSWORD")
|
||||||
|
defer os.Unsetenv("PEER2_PASSWORD")
|
||||||
|
|
||||||
|
// Create controller with multiple peers using different MD5 configurations
|
||||||
|
multiPeerConfig := config.BgpConfig{
|
||||||
|
LocalAS: 11111,
|
||||||
|
LocalIP: "192.168.1.100",
|
||||||
|
Peers: []config.PeerConfig{
|
||||||
|
{
|
||||||
|
PeerIP: "10.10.10.1",
|
||||||
|
PeerAS: 22222,
|
||||||
|
MD5EnvVar: "PEER1_PASSWORD",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.10.10.2",
|
||||||
|
PeerAS: 33333,
|
||||||
|
MD5Password: "peer2_config_password",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.10.10.3",
|
||||||
|
PeerAS: 44444,
|
||||||
|
// No MD5 authentication
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Origin: "igp",
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl, err := NewController(multiPeerConfig)
|
||||||
|
if err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
defer ctrl.Shutdown()
|
||||||
|
|
||||||
|
// Trigger peer addition by announcing a route (peers are added lazily)
|
||||||
|
_, ipnet, _ := net.ParseCIDR("20.30.40.0/24")
|
||||||
|
r := &Route{Net: ipnet}
|
||||||
|
ctrl.Announce(r)
|
||||||
|
|
||||||
|
// Verify all peers have correct MD5 configuration
|
||||||
|
peers := make(map[string]string) // map[peerIP]authPassword
|
||||||
|
ctrl.s.ListPeer(context.Background(), &api.ListPeerRequest{}, func(p *api.Peer) {
|
||||||
|
peers[p.Conf.NeighborAddress] = p.Conf.AuthPassword
|
||||||
|
})
|
||||||
|
|
||||||
|
a.Equal(3, len(peers), "Should have all three peers")
|
||||||
|
a.Equal("peer1_secret", peers["10.10.10.1"], "First peer should use env var password")
|
||||||
|
a.Equal("peer2_config_password", peers["10.10.10.2"], "Second peer should use config password")
|
||||||
|
a.Equal("", peers["10.10.10.3"], "Third peer should have no authentication")
|
||||||
|
}
|
||||||
@@ -3,22 +3,39 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/golang/glog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/mayuresh82/gocast/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
consulNodeEnv = "CONSUL_NODE"
|
consulNodeEnv = "CONSUL_NODE"
|
||||||
|
consulToken = "CONSUL_TOKEN"
|
||||||
|
allowStale = "CONSUL_STALE"
|
||||||
matchTag = "enable_gocast"
|
matchTag = "enable_gocast"
|
||||||
nodeUrl = "/catalog/node"
|
nodeURL = "/catalog/node"
|
||||||
healthCheckurl = "/health/checks"
|
remoteHealthCheckurl = "/health/checks"
|
||||||
|
localHealthCheckurl = "/agent/checks"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Clienter interface {
|
||||||
|
Do(req *http.Request) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
*http.Client
|
||||||
|
}
|
||||||
|
|
||||||
type ConsulMon struct {
|
type ConsulMon struct {
|
||||||
addr string
|
addr string
|
||||||
|
token string
|
||||||
node string
|
node string
|
||||||
|
client Clienter
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConsulServiceData struct {
|
type ConsulServiceData struct {
|
||||||
@@ -38,18 +55,40 @@ func contains(inp []string, elem string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConsulMon(addr string) (*ConsulMon, error) {
|
func NewConsulMon(addr string, token string) (*ConsulMon, error) {
|
||||||
node := os.Getenv(consulNodeEnv)
|
node := os.Getenv(consulNodeEnv)
|
||||||
if node == "" {
|
if node == "" {
|
||||||
return nil, fmt.Errorf("%s env variable not set", consulNodeEnv)
|
return nil, fmt.Errorf("%s env variable not set", consulNodeEnv)
|
||||||
}
|
}
|
||||||
return &ConsulMon{addr: addr, node: node}, nil
|
return &ConsulMon{addr: addr, token: token, node: node, client: &http.Client{Timeout: 10 * time.Second}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHTTPReq(httpMethod string, addr string, tokenFrmCfg string) (*http.Request, error) {
|
||||||
|
req, err := http.NewRequest(httpMethod, addr, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tokenFrmEnv := os.Getenv(consulToken)
|
||||||
|
if tokenFrmEnv != "" {
|
||||||
|
req.Header.Set("X-Consul-Token", tokenFrmEnv)
|
||||||
|
} else if tokenFrmCfg != "" {
|
||||||
|
req.Header.Set("X-Consul-Token", tokenFrmCfg)
|
||||||
|
}
|
||||||
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConsulMon) queryServices() ([]*App, error) {
|
func (c *ConsulMon) queryServices() ([]*App, error) {
|
||||||
var apps []*App
|
var apps []*App
|
||||||
addr := c.addr + fmt.Sprintf("%s/%s", nodeUrl, c.node)
|
var stale string
|
||||||
resp, err := http.Get(addr)
|
if os.Getenv(allowStale) == "true" {
|
||||||
|
stale = "stale"
|
||||||
|
}
|
||||||
|
addr := c.addr + fmt.Sprintf("%s/%s?%s", nodeURL, c.node, stale)
|
||||||
|
req, err := getHTTPReq(http.MethodGet, addr, c.token)
|
||||||
|
if err != nil {
|
||||||
|
return apps, err
|
||||||
|
}
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return apps, err
|
return apps, err
|
||||||
}
|
}
|
||||||
@@ -67,6 +106,7 @@ func (c *ConsulMon) queryServices() ([]*App, error) {
|
|||||||
monitors []string
|
monitors []string
|
||||||
nats []string
|
nats []string
|
||||||
)
|
)
|
||||||
|
var vipConf config.VipConfig
|
||||||
for _, tag := range service.Tags {
|
for _, tag := range service.Tags {
|
||||||
// try to find the requires tags. Only vip is mandatory
|
// try to find the requires tags. Only vip is mandatory
|
||||||
parts := strings.Split(tag, "=")
|
parts := strings.Split(tag, "=")
|
||||||
@@ -76,6 +116,8 @@ func (c *ConsulMon) queryServices() ([]*App, error) {
|
|||||||
switch parts[0] {
|
switch parts[0] {
|
||||||
case "gocast_vip":
|
case "gocast_vip":
|
||||||
vip = parts[1]
|
vip = parts[1]
|
||||||
|
case "gocast_vip_communities":
|
||||||
|
vipConf.BgpCommunities = strings.Split(parts[1], ",")
|
||||||
case "gocast_monitor":
|
case "gocast_monitor":
|
||||||
monitors = append(monitors, parts[1])
|
monitors = append(monitors, parts[1])
|
||||||
case "gocast_nat":
|
case "gocast_nat":
|
||||||
@@ -86,7 +128,7 @@ func (c *ConsulMon) queryServices() ([]*App, error) {
|
|||||||
glog.Errorf("No vip Tag found in matched service :%s", service.Service)
|
glog.Errorf("No vip Tag found in matched service :%s", service.Service)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
app, err := NewApp(service.Service, vip, monitors, nats)
|
app, err := NewApp(service.Service, vip, vipConf, monitors, nats, "consul")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Unable to add consul app: %v", err)
|
glog.Errorf("Unable to add consul app: %v", err)
|
||||||
continue
|
continue
|
||||||
@@ -97,22 +139,78 @@ func (c *ConsulMon) queryServices() ([]*App, error) {
|
|||||||
return apps, nil
|
return apps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConsulMon) healthCheck(service string) (bool, error) {
|
// healthCheckLocal queries a node's local consul agent to perform service healthchecks
|
||||||
addr := c.addr + fmt.Sprintf("%s/%s", healthCheckurl, service)
|
// This is the underlying api call: https://www.consul.io/api/agent/check.html
|
||||||
resp, err := http.Get(addr)
|
func (c *ConsulMon) healthCheckLocal(service string) (bool, error) {
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("filter", "enable_gocast in ServiceTags")
|
||||||
|
addr := c.addr + fmt.Sprintf("%s?%s", localHealthCheckurl, params.Encode())
|
||||||
|
req, err := getHTTPReq(http.MethodGet, addr, c.token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(2).Infof("Error getting %s with %s", addr, err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
var services map[string]interface{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&services); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
for _, sInfo := range services {
|
||||||
|
serviceInfo := sInfo.(map[string]interface{})
|
||||||
|
if serviceInfo["ServiceName"].(string) == service {
|
||||||
|
status := serviceInfo["Status"].(string)
|
||||||
|
if status == "passing" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
glog.V(2).Infof("Consul local healthcheck returned %s status", status)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("No local healthcheck info found for service %s on node %s in consul", service, c.node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// healthCheckRemote queries the consul cluster's healthcheck endpoint to perform service healthchecks
|
||||||
|
// This is the underlying api call: https://www.consul.io/api/health.html
|
||||||
|
func (c *ConsulMon) healthCheckRemote(service string) (bool, error) {
|
||||||
|
addr := c.addr + fmt.Sprintf("%s/%s", remoteHealthCheckurl, service)
|
||||||
|
req, err := getHTTPReq(http.MethodGet, addr, c.token)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(2).Infof("Error getting %s with %s", addr, err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
var data []interface{}
|
var data []interface{}
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, nodeInfo := range data {
|
for _, nodeInfo := range data {
|
||||||
n := nodeInfo.(map[string]interface{})
|
n := nodeInfo.(map[string]interface{})
|
||||||
if n["Node"] == c.node && n["Status"].(string) == "passing" {
|
if n["Node"].(string) == c.node {
|
||||||
|
if n["Status"].(string) == "passing" {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
glog.V(2).Infof("Consul healthcheck returned %s status", n["Status"].(string))
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
return false, fmt.Errorf("No healcheck info found for node %s in consul", c.node)
|
}
|
||||||
|
return false, fmt.Errorf("No healthcheck info found for node %s in consul", c.node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// healthCheck determines if we should use the local agent
|
||||||
|
// If the address contains "localhost", then it presumes that the local agent is to be used.
|
||||||
|
func (c *ConsulMon) healthCheck(service string) (bool, error) {
|
||||||
|
usingLocalAgent := strings.Contains(c.addr, "localhost") || strings.Contains(c.addr, "127.0.0.1")
|
||||||
|
if usingLocalAgent {
|
||||||
|
return c.healthCheckLocal(service)
|
||||||
|
}
|
||||||
|
return c.healthCheckRemote(service)
|
||||||
}
|
}
|
||||||
|
|||||||
201
controller/consul_test.go
Normal file
201
controller/consul_test.go
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mayuresh82/gocast/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mockConsulData = map[string]string{
|
||||||
|
"single-app": `{"Services": {
|
||||||
|
"test-app-1": {
|
||||||
|
"ID": "test-app-1",
|
||||||
|
"Service": "test-service",
|
||||||
|
"Tags": [
|
||||||
|
"enable_gocast", "gocast_vip=1.1.1.1/32", "gocast_monitor=consul", "gocast_vip_communities=111:222,333:444"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}}`,
|
||||||
|
"single-app-no-match": `{"Services": {
|
||||||
|
"test-app-1": {
|
||||||
|
"ID": "test-app-1",
|
||||||
|
"Service": "test-service",
|
||||||
|
"Tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}}`,
|
||||||
|
"single-app-no-vip": `{"Services": {
|
||||||
|
"test-app-1": {
|
||||||
|
"ID": "test-app-1",
|
||||||
|
"Service": "test-service",
|
||||||
|
"Tags": [
|
||||||
|
"enable_gocast", "gocast_monitor=consul"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
var mockConsulCheckData = map[string]string{
|
||||||
|
"remote-pass": `[
|
||||||
|
{
|
||||||
|
"Node": "test-node1",
|
||||||
|
"Status": "passing",
|
||||||
|
"ServiceName": "test-service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Node": "test-node2",
|
||||||
|
"Status": "passing",
|
||||||
|
"ServiceName": "test-service"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
"remote-fail": `[
|
||||||
|
{
|
||||||
|
"Node": "test-node1",
|
||||||
|
"Status": "failed",
|
||||||
|
"ServiceName": "test-service"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
"local-pass": `{
|
||||||
|
"service:test-service": {
|
||||||
|
"Node": "test-node1",
|
||||||
|
"Status": "passing",
|
||||||
|
"ServiceName": "test-service"
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
"local-fail": `{
|
||||||
|
"service:test-service": {
|
||||||
|
"Node": "test-node1",
|
||||||
|
"Status": "failed",
|
||||||
|
"ServiceName": "test-service"
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockClient struct {
|
||||||
|
do func(*http.Request) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockClient) Do(*http.Request) (*http.Response, error) {
|
||||||
|
if c.do != nil {
|
||||||
|
return c.do(&http.Request{})
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetNewHTTPReq(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
// test with consul token from config file
|
||||||
|
req, err := getHTTPReq("GET", "1.1.1.1", "3333-3333")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
a.Equal(req.Header.Get("X-Consul-Token"), "3333-3333")
|
||||||
|
|
||||||
|
// test with consul token from env variable
|
||||||
|
os.Setenv("CONSUL_TOKEN", "4444-4444")
|
||||||
|
req, err = getHTTPReq("GET", "1.1.1.1", "3333-3333")
|
||||||
|
os.Unsetenv("CONSUL_TOKEN")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
a.Equal(req.Header.Get("X-Consul-Token"), "4444-4444")
|
||||||
|
|
||||||
|
// test without consul token
|
||||||
|
req, err = getHTTPReq("GET", "1.1.1.1", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
a.Equal(req.Header.Get("X-Consul-Token"), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryServices(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
client := &MockClient{}
|
||||||
|
cm := &ConsulMon{
|
||||||
|
addr: "foo", node: "test", client: client,
|
||||||
|
}
|
||||||
|
|
||||||
|
// test valid app
|
||||||
|
client.do = func(*http.Request) (*http.Response, error) {
|
||||||
|
b := bytes.NewBuffer([]byte(mockConsulData["single-app"]))
|
||||||
|
return &http.Response{Body: ioutil.NopCloser(b), StatusCode: http.StatusOK}, nil
|
||||||
|
}
|
||||||
|
apps, err := cm.queryServices()
|
||||||
|
if err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
a.Equal(1, len(apps))
|
||||||
|
a.Equal([]string{"111:222", "333:444"}, apps[0].Vip.Communities)
|
||||||
|
|
||||||
|
app, _ := NewApp("test-service", "1.1.1.1/32", config.VipConfig{}, []string{"consul"}, []string{}, "consul")
|
||||||
|
a.True(app.Equal(apps[0]))
|
||||||
|
|
||||||
|
// test no match
|
||||||
|
client.do = func(*http.Request) (*http.Response, error) {
|
||||||
|
b := bytes.NewBuffer([]byte(mockConsulData["single-app-no-match"]))
|
||||||
|
return &http.Response{Body: ioutil.NopCloser(b), StatusCode: http.StatusOK}, nil
|
||||||
|
}
|
||||||
|
apps, err = cm.queryServices()
|
||||||
|
if err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
a.Equal(0, len(apps))
|
||||||
|
|
||||||
|
// test missing vip
|
||||||
|
client.do = func(*http.Request) (*http.Response, error) {
|
||||||
|
b := bytes.NewBuffer([]byte(mockConsulData["single-app-no-vip"]))
|
||||||
|
return &http.Response{Body: ioutil.NopCloser(b), StatusCode: http.StatusOK}, nil
|
||||||
|
}
|
||||||
|
apps, _ = cm.queryServices()
|
||||||
|
a.Equal(0, len(apps))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHealthCheck(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
client := &MockClient{}
|
||||||
|
cm := &ConsulMon{node: "test-node1", client: client}
|
||||||
|
|
||||||
|
// test remote checks
|
||||||
|
cm.addr = "http://remote/check"
|
||||||
|
client.do = func(*http.Request) (*http.Response, error) {
|
||||||
|
b := bytes.NewBuffer([]byte(mockConsulCheckData["remote-pass"]))
|
||||||
|
return &http.Response{Body: ioutil.NopCloser(b), StatusCode: http.StatusOK}, nil
|
||||||
|
}
|
||||||
|
check, err := cm.healthCheck("test-service")
|
||||||
|
if err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
a.True(check)
|
||||||
|
client.do = func(*http.Request) (*http.Response, error) {
|
||||||
|
b := bytes.NewBuffer([]byte(mockConsulCheckData["remote-fail"]))
|
||||||
|
return &http.Response{Body: ioutil.NopCloser(b), StatusCode: http.StatusOK}, nil
|
||||||
|
}
|
||||||
|
check, _ = cm.healthCheck("test-service")
|
||||||
|
a.False(check)
|
||||||
|
|
||||||
|
// test local checks
|
||||||
|
cm.addr = "http://localhost/check"
|
||||||
|
client.do = func(*http.Request) (*http.Response, error) {
|
||||||
|
b := bytes.NewBuffer([]byte(mockConsulCheckData["local-pass"]))
|
||||||
|
return &http.Response{Body: ioutil.NopCloser(b), StatusCode: http.StatusOK}, nil
|
||||||
|
}
|
||||||
|
check, _ = cm.healthCheck("test-service")
|
||||||
|
if err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
a.True(check)
|
||||||
|
cm.addr = "http://127.0.0.1/check"
|
||||||
|
client.do = func(*http.Request) (*http.Response, error) {
|
||||||
|
b := bytes.NewBuffer([]byte(mockConsulCheckData["local-fail"]))
|
||||||
|
return &http.Response{Body: ioutil.NopCloser(b), StatusCode: http.StatusOK}, nil
|
||||||
|
}
|
||||||
|
check, _ = cm.healthCheck("test-service")
|
||||||
|
a.False(check)
|
||||||
|
}
|
||||||
@@ -2,14 +2,15 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/golang/glog"
|
|
||||||
c "github.com/mayuresh82/gocast/config"
|
|
||||||
api "github.com/osrg/gobgp/api"
|
|
||||||
"net"
|
"net"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
c "github.com/mayuresh82/gocast/config"
|
||||||
|
api "github.com/osrg/gobgp/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -52,13 +53,15 @@ func execMonitor(cmd string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// appMon maintains the state of a registered app
|
||||||
type appMon struct {
|
type appMon struct {
|
||||||
app *App
|
app *App
|
||||||
done chan bool
|
done chan bool
|
||||||
announced bool
|
announced bool
|
||||||
checkOn bool
|
runLoopOn bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MonitorMgr manages the lifecycle of registered apps
|
||||||
type MonitorMgr struct {
|
type MonitorMgr struct {
|
||||||
monitors map[string]*appMon
|
monitors map[string]*appMon
|
||||||
cleanups map[string]chan bool
|
cleanups map[string]chan bool
|
||||||
@@ -66,11 +69,12 @@ type MonitorMgr struct {
|
|||||||
ctrl *Controller
|
ctrl *Controller
|
||||||
consul *ConsulMon
|
consul *ConsulMon
|
||||||
|
|
||||||
sync.Mutex
|
monMu sync.Mutex
|
||||||
|
clMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMonitor(config *c.Config) *MonitorMgr {
|
func NewMonitor(config *c.Config) *MonitorMgr {
|
||||||
ctrl, err := NewController(config)
|
ctrl, err := NewController(config.Bgp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Exitf("Failed to start BGP controller: %v", err)
|
glog.Exitf("Failed to start BGP controller: %v", err)
|
||||||
}
|
}
|
||||||
@@ -80,7 +84,7 @@ func NewMonitor(config *c.Config) *MonitorMgr {
|
|||||||
cleanups: make(map[string]chan bool),
|
cleanups: make(map[string]chan bool),
|
||||||
}
|
}
|
||||||
if config.Agent.ConsulAddr != "" {
|
if config.Agent.ConsulAddr != "" {
|
||||||
cmon, err := NewConsulMon(config.Agent.ConsulAddr)
|
cmon, err := NewConsulMon(config.Agent.ConsulAddr, config.Agent.ConsulToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Failed to start consul monitor: %v", err)
|
glog.Errorf("Failed to start consul monitor: %v", err)
|
||||||
} else {
|
} else {
|
||||||
@@ -97,7 +101,7 @@ func NewMonitor(config *c.Config) *MonitorMgr {
|
|||||||
mon.config = config
|
mon.config = config
|
||||||
// add apps defined in config
|
// add apps defined in config
|
||||||
for _, a := range config.Apps {
|
for _, a := range config.Apps {
|
||||||
app, err := NewApp(a.Name, a.Vip, a.Monitors, a.Nats)
|
app, err := NewApp(a.Name, a.Vip, a.VipConfig, a.Monitors, a.Nats, "config")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Failed to add configured app %s: %v", a.Name, err)
|
glog.Errorf("Failed to add configured app %s: %v", a.Name, err)
|
||||||
continue
|
continue
|
||||||
@@ -107,6 +111,8 @@ func NewMonitor(config *c.Config) *MonitorMgr {
|
|||||||
return mon
|
return mon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// consulMon periodically queries consul for apps that need to be
|
||||||
|
// registered and adds them to the monitor manager
|
||||||
func (m *MonitorMgr) consulMon() {
|
func (m *MonitorMgr) consulMon() {
|
||||||
for {
|
for {
|
||||||
apps, err := m.consul.queryServices()
|
apps, err := m.consul.queryServices()
|
||||||
@@ -118,8 +124,11 @@ func (m *MonitorMgr) consulMon() {
|
|||||||
}
|
}
|
||||||
// remove currently running apps that are not discovered in this pass
|
// remove currently running apps that are not discovered in this pass
|
||||||
var toRemove []string
|
var toRemove []string
|
||||||
m.Lock()
|
m.monMu.Lock()
|
||||||
for name := range m.monitors {
|
for name, mon := range m.monitors {
|
||||||
|
if mon.app.Source != "consul" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
var found bool
|
var found bool
|
||||||
for _, app := range apps {
|
for _, app := range apps {
|
||||||
if name == app.Name {
|
if name == app.Name {
|
||||||
@@ -132,61 +141,84 @@ func (m *MonitorMgr) consulMon() {
|
|||||||
toRemove = append(toRemove, name)
|
toRemove = append(toRemove, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
m.monMu.Unlock()
|
||||||
for _, tr := range toRemove {
|
for _, tr := range toRemove {
|
||||||
m.Remove(tr)
|
m.Remove(tr)
|
||||||
}
|
}
|
||||||
m.Unlock()
|
|
||||||
}
|
}
|
||||||
<-time.After(m.config.Agent.ConsulQueryInterval)
|
<-time.After(m.config.Agent.ConsulQueryInterval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add adds a new app into monitor manager
|
||||||
func (m *MonitorMgr) Add(app *App) {
|
func (m *MonitorMgr) Add(app *App) {
|
||||||
// check if already running
|
// check if already running
|
||||||
m.Lock()
|
m.monMu.Lock()
|
||||||
defer m.Unlock()
|
var existing *appMon
|
||||||
for _, appMon := range m.monitors {
|
for _, appMon := range m.monitors {
|
||||||
if appMon.app.Equal(app) && appMon.checkOn {
|
if appMon.app.Equal(app) {
|
||||||
glog.V(2).Infof("App %s already exists", app.Name)
|
glog.Infof("App %s already exists", app.Name)
|
||||||
return
|
existing = appMon
|
||||||
|
break
|
||||||
}
|
}
|
||||||
if appMon.app.Vip.String() == app.Vip.String() && appMon.app.Name != app.Name {
|
if appMon.app.Vip.Net.String() == app.Vip.Net.String() && appMon.app.Name != app.Name {
|
||||||
glog.Errorf("Error: Vip %s is already being announced by app: %s", app.Vip.String(), appMon.app.Name)
|
glog.Errorf("Error: Vip %s is already being announced by app: %s", app.Vip.Net.String(), appMon.app.Name)
|
||||||
|
m.monMu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.Remove(app.Name)
|
m.monMu.Unlock()
|
||||||
|
// if the same app already exists but its run loop is not running,
|
||||||
|
// then just restart the run loop
|
||||||
|
if existing != nil {
|
||||||
|
if !existing.runLoopOn {
|
||||||
|
go m.runLoop(existing)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// else add a new app and start its run loop
|
||||||
appMon := &appMon{app: app, done: make(chan bool)}
|
appMon := &appMon{app: app, done: make(chan bool)}
|
||||||
m.monitors[app.Name] = appMon
|
m.monitors[app.Name] = appMon
|
||||||
go m.runLoop(appMon)
|
go m.runLoop(appMon)
|
||||||
glog.Infof("Registered a new app: %v", app)
|
glog.Infof("Registered a new app: %v", app.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove removes an app from monitor manager, stops BGP
|
||||||
|
/// announcement and cleans up state
|
||||||
func (m *MonitorMgr) Remove(appName string) {
|
func (m *MonitorMgr) Remove(appName string) {
|
||||||
|
m.monMu.Lock()
|
||||||
|
defer m.monMu.Unlock()
|
||||||
if a, ok := m.monitors[appName]; ok {
|
if a, ok := m.monitors[appName]; ok {
|
||||||
if a.checkOn {
|
if a.runLoopOn {
|
||||||
a.done <- true
|
close(a.done)
|
||||||
}
|
}
|
||||||
if a.announced {
|
if a.announced {
|
||||||
if err := m.ctrl.Withdraw(a.app.Vip); err != nil {
|
if err := m.ctrl.Withdraw(a.app.Vip); err != nil {
|
||||||
glog.Errorf("Failed to withdraw route: %v", err)
|
glog.Errorf("Failed to withdraw route: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := deleteLoopback(a.app.Vip); err != nil {
|
if err := deleteLoopback(a.app.Vip.Net); err != nil {
|
||||||
glog.Errorf("Failed to remove app: %s: %v", a.app.Name, err)
|
glog.Errorf("Failed to remove app: %s: %v", a.app.Name, err)
|
||||||
}
|
}
|
||||||
for _, nat := range a.app.Nats {
|
for _, nat := range a.app.Nats {
|
||||||
parts := strings.Split(nat, ":")
|
parts := strings.Split(nat, ":")
|
||||||
if len(parts) != 2 {
|
switch len(parts) {
|
||||||
continue
|
case 3:
|
||||||
}
|
if err := natRule("D", a.app.Vip.Net.IP, m.ctrl.localIP, parts[0], parts[1], parts[2]); err != nil {
|
||||||
if err := natRule("D", a.app.Vip.IP, m.ctrl.localIP, parts[0], parts[1]); err != nil {
|
|
||||||
glog.Errorf("Failed to remove app: %s: %v", a.app.Name, err)
|
glog.Errorf("Failed to remove app: %s: %v", a.app.Name, err)
|
||||||
}
|
}
|
||||||
|
case 2:
|
||||||
|
if err := natRule("D", a.app.Vip.Net.IP, m.ctrl.localIP, parts[0], parts[1], parts[1]); err != nil {
|
||||||
|
glog.Errorf("Failed to remove app: %s: %v", a.app.Name, err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete(m.monitors, appName)
|
delete(m.monitors, appName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MonitorMgr) runMonitors(app *App) bool {
|
func (m *MonitorMgr) runMonitors(app *App) bool {
|
||||||
for _, mon := range app.Monitors {
|
for _, mon := range app.Monitors {
|
||||||
var check bool
|
var check bool
|
||||||
@@ -212,29 +244,36 @@ func (m *MonitorMgr) runMonitors(app *App) bool {
|
|||||||
|
|
||||||
func (m *MonitorMgr) checkCond(am *appMon) error {
|
func (m *MonitorMgr) checkCond(am *appMon) error {
|
||||||
app := am.app
|
app := am.app
|
||||||
m.Lock()
|
m.clMu.Lock()
|
||||||
defer m.Unlock()
|
defer m.clMu.Unlock()
|
||||||
if m.runMonitors(app) {
|
if m.runMonitors(app) {
|
||||||
glog.V(2).Infof("All Monitors for app: %s succeeded", app.Name)
|
glog.V(2).Infof("All Monitors for app: %s succeeded", app.Name)
|
||||||
if !am.announced {
|
if !am.announced {
|
||||||
if err := addLoopback(app.Name, app.Vip); err != nil {
|
if err := addLoopback(app.Name, app.Vip.Net); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, nat := range app.Nats {
|
for _, nat := range app.Nats {
|
||||||
parts := strings.Split(nat, ":")
|
parts := strings.Split(nat, ":")
|
||||||
if len(parts) != 2 {
|
switch len(parts) {
|
||||||
continue
|
case 3:
|
||||||
}
|
if err := natRule("A", app.Vip.Net.IP, m.ctrl.localIP, parts[0], parts[1], parts[2]); err != nil {
|
||||||
if err := natRule("A", app.Vip.IP, m.ctrl.localIP, parts[0], parts[1]); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case 2:
|
||||||
|
if err := natRule("A", app.Vip.Net.IP, m.ctrl.localIP, parts[0], parts[1], parts[1]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := m.ctrl.Announce(app.Vip); err != nil {
|
if err := m.ctrl.Announce(app.Vip); err != nil {
|
||||||
return fmt.Errorf("Failed to announce route: %v", err)
|
return fmt.Errorf("Failed to announce route: %v", err)
|
||||||
}
|
}
|
||||||
am.announced = true
|
am.announced = true
|
||||||
if exit, ok := m.cleanups[app.Name]; ok {
|
if exit, ok := m.cleanups[app.Name]; ok {
|
||||||
exit <- true
|
close(exit)
|
||||||
|
delete(m.cleanups, app.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -251,8 +290,11 @@ func (m *MonitorMgr) checkCond(am *appMon) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runLoop periodically checks if an app passes healthchecks
|
||||||
|
// and needs VIP announcement
|
||||||
func (m *MonitorMgr) runLoop(am *appMon) {
|
func (m *MonitorMgr) runLoop(am *appMon) {
|
||||||
am.checkOn = true
|
glog.Infof("Starting run-loop for app %s", am.app.Name)
|
||||||
|
am.runLoopOn = true
|
||||||
if err := m.checkCond(am); err != nil {
|
if err := m.checkCond(am); err != nil {
|
||||||
glog.Errorln(err)
|
glog.Errorln(err)
|
||||||
}
|
}
|
||||||
@@ -265,32 +307,264 @@ func (m *MonitorMgr) runLoop(am *appMon) {
|
|||||||
glog.Errorln(err)
|
glog.Errorln(err)
|
||||||
}
|
}
|
||||||
case <-am.done:
|
case <-am.done:
|
||||||
glog.V(2).Infof("Exit run-loop for app: %s", am.app.Name)
|
glog.Infof("Exit run-loop for app: %s", am.app.Name)
|
||||||
|
am.runLoopOn = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CloseAll shuts down all BGP sessions removes state
|
||||||
func (m *MonitorMgr) CloseAll() {
|
func (m *MonitorMgr) CloseAll() {
|
||||||
glog.Infof("Shutting down all open bgp sessions")
|
glog.Infof("Shutting down all open bgp sessions")
|
||||||
if err := m.ctrl.Shutdown(); err != nil {
|
if err := m.ctrl.Shutdown(); err != nil {
|
||||||
glog.Errorf("Failed to shut-down BGP: %v", err)
|
glog.Errorf("Failed to shut-down BGP: %v", err)
|
||||||
}
|
}
|
||||||
for _, am := range m.monitors {
|
for _, am := range m.monitors {
|
||||||
if am.checkOn {
|
if am.runLoopOn {
|
||||||
am.done <- true
|
close(am.done)
|
||||||
}
|
}
|
||||||
deleteLoopback(am.app.Vip)
|
deleteLoopback(am.app.Vip.Net)
|
||||||
for _, nat := range am.app.Nats {
|
for _, nat := range am.app.Nats {
|
||||||
parts := strings.Split(nat, ":")
|
parts := strings.Split(nat, ":")
|
||||||
if len(parts) != 2 {
|
switch len(parts) {
|
||||||
|
case 3:
|
||||||
|
natRule("D", am.app.Vip.Net.IP, m.ctrl.localIP, parts[0], parts[1], parts[2])
|
||||||
|
case 2:
|
||||||
|
natRule("D", am.app.Vip.Net.IP, m.ctrl.localIP, parts[0], parts[1], parts[1])
|
||||||
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
natRule("D", am.app.Vip.IP, m.ctrl.localIP, parts[0], parts[1])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reload re-reads the configuration file and applies changes
|
||||||
|
func (m *MonitorMgr) Reload(configPath string) error {
|
||||||
|
glog.Infof("Reloading configuration from %s", configPath)
|
||||||
|
|
||||||
|
// Read new configuration
|
||||||
|
newConfig := c.GetConfig(configPath)
|
||||||
|
if newConfig == nil {
|
||||||
|
return fmt.Errorf("Failed to load configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set defaults if not specified
|
||||||
|
if newConfig.Agent.MonitorInterval == 0 {
|
||||||
|
newConfig.Agent.MonitorInterval = defaultMonitorInterval
|
||||||
|
}
|
||||||
|
if newConfig.Agent.CleanupTimer == 0 {
|
||||||
|
newConfig.Agent.CleanupTimer = defaultCleanupTimer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if BGP configuration has changed
|
||||||
|
bgpChanged := m.bgpConfigChanged(m.config.Bgp, newConfig.Bgp)
|
||||||
|
|
||||||
|
if bgpChanged {
|
||||||
|
glog.Infof("BGP configuration changed, restarting BGP controller")
|
||||||
|
|
||||||
|
// Withdraw all current routes before shutting down
|
||||||
|
m.monMu.Lock()
|
||||||
|
for _, am := range m.monitors {
|
||||||
|
if am.announced {
|
||||||
|
if err := m.ctrl.Withdraw(am.app.Vip); err != nil {
|
||||||
|
glog.Errorf("Failed to withdraw route during reload: %v", err)
|
||||||
|
}
|
||||||
|
am.announced = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.monMu.Unlock()
|
||||||
|
|
||||||
|
// Shutdown old BGP controller
|
||||||
|
if err := m.ctrl.Shutdown(); err != nil {
|
||||||
|
glog.Errorf("Failed to shutdown BGP controller: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start new BGP controller
|
||||||
|
ctrl, err := NewController(newConfig.Bgp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to start new BGP controller: %v", err)
|
||||||
|
}
|
||||||
|
m.ctrl = ctrl
|
||||||
|
|
||||||
|
// Re-announce all routes with new BGP config
|
||||||
|
m.monMu.Lock()
|
||||||
|
for _, am := range m.monitors {
|
||||||
|
// Only re-announce if the app was previously announced and is still healthy
|
||||||
|
if m.runMonitors(am.app) {
|
||||||
|
if err := m.ctrl.Announce(am.app.Vip); err != nil {
|
||||||
|
glog.Errorf("Failed to re-announce route %s: %v", am.app.Vip.Net.String(), err)
|
||||||
|
} else {
|
||||||
|
am.announced = true
|
||||||
|
glog.Infof("Re-announced route %s", am.app.Vip.Net.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.monMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle consul configuration changes
|
||||||
|
if m.config.Agent.ConsulAddr != newConfig.Agent.ConsulAddr ||
|
||||||
|
m.config.Agent.ConsulToken != newConfig.Agent.ConsulToken {
|
||||||
|
glog.Infof("Consul configuration changed")
|
||||||
|
if newConfig.Agent.ConsulAddr != "" {
|
||||||
|
cmon, err := NewConsulMon(newConfig.Agent.ConsulAddr, newConfig.Agent.ConsulToken)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to start consul monitor: %v", err)
|
||||||
|
} else {
|
||||||
|
m.consul = cmon
|
||||||
|
// Start consul monitoring in background if not already running
|
||||||
|
if m.config.Agent.ConsulAddr == "" {
|
||||||
|
go m.consulMon()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update agent configuration
|
||||||
|
oldConfig := m.config
|
||||||
|
m.config = newConfig
|
||||||
|
|
||||||
|
// Handle app configuration changes
|
||||||
|
m.reloadApps(oldConfig.Apps, newConfig.Apps)
|
||||||
|
|
||||||
|
glog.Infof("Configuration reloaded successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bgpConfigChanged checks if BGP configuration has changed
|
||||||
|
func (m *MonitorMgr) bgpConfigChanged(old, new c.BgpConfig) bool {
|
||||||
|
// Check basic parameters
|
||||||
|
if old.LocalAS != new.LocalAS || old.LocalIP != new.LocalIP || old.Origin != new.Origin {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check legacy peer config
|
||||||
|
if old.PeerAS != new.PeerAS || old.PeerIP != new.PeerIP {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check peers
|
||||||
|
if len(old.Peers) != len(new.Peers) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare each peer
|
||||||
|
for i := range old.Peers {
|
||||||
|
if old.Peers[i].PeerIP != new.Peers[i].PeerIP ||
|
||||||
|
old.Peers[i].PeerAS != new.Peers[i].PeerAS ||
|
||||||
|
old.Peers[i].MD5Password != new.Peers[i].MD5Password ||
|
||||||
|
old.Peers[i].MD5EnvVar != new.Peers[i].MD5EnvVar {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check multi-hop
|
||||||
|
if (old.Peers[i].MultiHop == nil) != (new.Peers[i].MultiHop == nil) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if old.Peers[i].MultiHop != nil && new.Peers[i].MultiHop != nil {
|
||||||
|
if *old.Peers[i].MultiHop != *new.Peers[i].MultiHop {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check communities
|
||||||
|
if len(old.Peers[i].Communities) != len(new.Peers[i].Communities) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for j := range old.Peers[i].Communities {
|
||||||
|
if old.Peers[i].Communities[j] != new.Peers[i].Communities[j] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check global communities
|
||||||
|
if len(old.Communities) != len(new.Communities) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for i := range old.Communities {
|
||||||
|
if old.Communities[i] != new.Communities[i] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// reloadApps compares old and new app configurations and applies changes
|
||||||
|
func (m *MonitorMgr) reloadApps(oldApps, newApps []c.AppConfig) {
|
||||||
|
// Build maps for easy comparison
|
||||||
|
oldAppMap := make(map[string]c.AppConfig)
|
||||||
|
for _, app := range oldApps {
|
||||||
|
oldAppMap[app.Name] = app
|
||||||
|
}
|
||||||
|
|
||||||
|
newAppMap := make(map[string]c.AppConfig)
|
||||||
|
for _, app := range newApps {
|
||||||
|
newAppMap[app.Name] = app
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove apps that are no longer in config
|
||||||
|
for name := range oldAppMap {
|
||||||
|
if _, exists := newAppMap[name]; !exists {
|
||||||
|
m.monMu.Lock()
|
||||||
|
if am, ok := m.monitors[name]; ok {
|
||||||
|
if am.app.Source == "config" {
|
||||||
|
glog.Infof("Removing app %s (no longer in config)", name)
|
||||||
|
m.monMu.Unlock()
|
||||||
|
m.Remove(name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.monMu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new apps or update existing ones
|
||||||
|
for name, newAppConfig := range newAppMap {
|
||||||
|
oldAppConfig, existed := oldAppMap[name]
|
||||||
|
|
||||||
|
// Check if app configuration changed
|
||||||
|
configChanged := !existed ||
|
||||||
|
oldAppConfig.Vip != newAppConfig.Vip ||
|
||||||
|
!equalStringSlices(oldAppConfig.Monitors, newAppConfig.Monitors) ||
|
||||||
|
!equalStringSlices(oldAppConfig.Nats, newAppConfig.Nats) ||
|
||||||
|
!equalStringSlices(oldAppConfig.VipConfig.BgpCommunities, newAppConfig.VipConfig.BgpCommunities)
|
||||||
|
|
||||||
|
if configChanged {
|
||||||
|
if existed {
|
||||||
|
glog.Infof("App %s configuration changed, reloading", name)
|
||||||
|
m.Remove(name)
|
||||||
|
} else {
|
||||||
|
glog.Infof("Adding new app %s from config", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
app, err := NewApp(newAppConfig.Name, newAppConfig.Vip, newAppConfig.VipConfig,
|
||||||
|
newAppConfig.Monitors, newAppConfig.Nats, "config")
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to add app %s: %v", name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m.Add(app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// equalStringSlices compares two string slices
|
||||||
|
func equalStringSlices(a, b []string) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range a {
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanUp periodically monitors for stale apps and cleans them up
|
||||||
func (m *MonitorMgr) Cleanup(app string, exit chan bool) {
|
func (m *MonitorMgr) Cleanup(app string, exit chan bool) {
|
||||||
t := time.NewTimer(m.config.Agent.CleanupTimer)
|
t := time.NewTimer(m.config.Agent.CleanupTimer)
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
@@ -298,15 +572,15 @@ func (m *MonitorMgr) Cleanup(app string, exit chan bool) {
|
|||||||
select {
|
select {
|
||||||
case <-t.C:
|
case <-t.C:
|
||||||
glog.Infof("Cleaning up app %s", app)
|
glog.Infof("Cleaning up app %s", app)
|
||||||
m.Lock()
|
|
||||||
m.Remove(app)
|
m.Remove(app)
|
||||||
m.Unlock()
|
return
|
||||||
case <-exit:
|
case <-exit:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MonitorMgr) GetInfo() (*api.Peer, error) {
|
// GetInfo returns basic BGP info for established peers
|
||||||
|
func (m *MonitorMgr) GetInfo() ([]*api.Peer, error) {
|
||||||
return m.ctrl.PeerInfo()
|
return m.ctrl.PeerInfo()
|
||||||
}
|
}
|
||||||
|
|||||||
35
controller/monitor_test.go
Normal file
35
controller/monitor_test.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPortMonitor(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
addr, _ := net.ResolveTCPAddr("tcp", ":33333")
|
||||||
|
conn, err := net.ListenTCP("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
a.True(portMonitor("tcp", "33333"))
|
||||||
|
a.False(portMonitor("tcp", "44444"))
|
||||||
|
conn.Close()
|
||||||
|
|
||||||
|
uaddr, _ := net.ResolveUDPAddr("udp", ":33333")
|
||||||
|
udpconn, err := net.ListenUDP("udp", uaddr)
|
||||||
|
if err != nil {
|
||||||
|
a.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
a.True(portMonitor("udp", "33333"))
|
||||||
|
a.False(portMonitor("udp", "44444"))
|
||||||
|
udpconn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecMonitor(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
a.True(execMonitor("echo foo"))
|
||||||
|
a.False(execMonitor("echo foo && false"))
|
||||||
|
}
|
||||||
293
controller/reload_test.go
Normal file
293
controller/reload_test.go
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
config "github.com/mayuresh82/gocast/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBgpConfigChanged(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
mon := &MonitorMgr{}
|
||||||
|
|
||||||
|
// Test 1: No changes
|
||||||
|
cfg1 := config.BgpConfig{
|
||||||
|
LocalAS: 12345,
|
||||||
|
LocalIP: "192.168.1.100",
|
||||||
|
Origin: "igp",
|
||||||
|
Peers: []config.PeerConfig{
|
||||||
|
{PeerIP: "10.10.10.1", PeerAS: 6789},
|
||||||
|
},
|
||||||
|
Communities: []string{"100:100"},
|
||||||
|
}
|
||||||
|
cfg2 := cfg1
|
||||||
|
a.False(mon.bgpConfigChanged(cfg1, cfg2), "Identical configs should not be considered changed")
|
||||||
|
|
||||||
|
// Test 2: LocalAS changed
|
||||||
|
cfg3 := cfg1
|
||||||
|
cfg3.LocalAS = 54321
|
||||||
|
a.True(mon.bgpConfigChanged(cfg1, cfg3), "LocalAS change should be detected")
|
||||||
|
|
||||||
|
// Test 3: Peer IP changed
|
||||||
|
cfg4 := cfg1
|
||||||
|
cfg4.Peers = []config.PeerConfig{
|
||||||
|
{PeerIP: "10.10.10.2", PeerAS: 6789},
|
||||||
|
}
|
||||||
|
a.True(mon.bgpConfigChanged(cfg1, cfg4), "Peer IP change should be detected")
|
||||||
|
|
||||||
|
// Test 4: MD5 password changed
|
||||||
|
cfg5 := cfg1
|
||||||
|
cfg5.Peers = []config.PeerConfig{
|
||||||
|
{PeerIP: "10.10.10.1", PeerAS: 6789, MD5Password: "secret"},
|
||||||
|
}
|
||||||
|
a.True(mon.bgpConfigChanged(cfg1, cfg5), "MD5 password change should be detected")
|
||||||
|
|
||||||
|
// Test 5: Community added
|
||||||
|
cfg6 := cfg1
|
||||||
|
cfg6.Communities = []string{"100:100", "200:200"}
|
||||||
|
a.True(mon.bgpConfigChanged(cfg1, cfg6), "Community addition should be detected")
|
||||||
|
|
||||||
|
// Test 6: Peer added
|
||||||
|
cfg7 := cfg1
|
||||||
|
cfg7.Peers = []config.PeerConfig{
|
||||||
|
{PeerIP: "10.10.10.1", PeerAS: 6789},
|
||||||
|
{PeerIP: "10.10.10.2", PeerAS: 6789},
|
||||||
|
}
|
||||||
|
a.True(mon.bgpConfigChanged(cfg1, cfg7), "Peer addition should be detected")
|
||||||
|
|
||||||
|
// Test 7: MultiHop changed
|
||||||
|
multiHopTrue := true
|
||||||
|
cfg8 := cfg1
|
||||||
|
cfg8.Peers = []config.PeerConfig{
|
||||||
|
{PeerIP: "10.10.10.1", PeerAS: 6789, MultiHop: &multiHopTrue},
|
||||||
|
}
|
||||||
|
a.True(mon.bgpConfigChanged(cfg1, cfg8), "MultiHop change should be detected")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEqualStringSlices(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
a.True(equalStringSlices([]string{}, []string{}), "Empty slices should be equal")
|
||||||
|
a.True(equalStringSlices([]string{"a", "b"}, []string{"a", "b"}), "Identical slices should be equal")
|
||||||
|
a.False(equalStringSlices([]string{"a"}, []string{"a", "b"}), "Different length slices should not be equal")
|
||||||
|
a.False(equalStringSlices([]string{"a", "b"}, []string{"a", "c"}), "Different content should not be equal")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReload(t *testing.T) {
|
||||||
|
if os.Getenv("CI") != "" {
|
||||||
|
t.Skip("Skipping reload test in CI environment")
|
||||||
|
}
|
||||||
|
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
// Create initial config file
|
||||||
|
initialConfig := `
|
||||||
|
agent:
|
||||||
|
listen_addr: :8080
|
||||||
|
monitor_interval: 10s
|
||||||
|
cleanup_timer: 15m
|
||||||
|
|
||||||
|
bgp:
|
||||||
|
local_as: 12345
|
||||||
|
peer_as: 6789
|
||||||
|
local_ip: 192.168.1.100
|
||||||
|
origin: igp
|
||||||
|
communities:
|
||||||
|
- 100:100
|
||||||
|
|
||||||
|
apps:
|
||||||
|
- name: test-app
|
||||||
|
vip: 1.1.1.1/32
|
||||||
|
monitors:
|
||||||
|
- exec:echo
|
||||||
|
`
|
||||||
|
|
||||||
|
// Create temporary config file
|
||||||
|
tmpfile, err := ioutil.TempFile("", "gocast-test-*.yaml")
|
||||||
|
a.NoError(err)
|
||||||
|
defer os.Remove(tmpfile.Name())
|
||||||
|
|
||||||
|
_, err = tmpfile.Write([]byte(initialConfig))
|
||||||
|
a.NoError(err)
|
||||||
|
tmpfile.Close()
|
||||||
|
|
||||||
|
// Initialize monitor with initial config
|
||||||
|
conf := config.GetConfig(tmpfile.Name())
|
||||||
|
mon := NewMonitor(conf)
|
||||||
|
defer mon.CloseAll()
|
||||||
|
|
||||||
|
// Wait a bit for initialization
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// Verify initial state
|
||||||
|
a.Equal(12345, mon.ctrl.localAS)
|
||||||
|
m := mon.monitors["test-app"]
|
||||||
|
a.NotNil(m, "Initial app should be loaded")
|
||||||
|
|
||||||
|
// Update config file with new BGP AS and remove app
|
||||||
|
updatedConfig := `
|
||||||
|
agent:
|
||||||
|
listen_addr: :8080
|
||||||
|
monitor_interval: 10s
|
||||||
|
cleanup_timer: 15m
|
||||||
|
|
||||||
|
bgp:
|
||||||
|
local_as: 54321
|
||||||
|
peer_as: 6789
|
||||||
|
local_ip: 192.168.1.100
|
||||||
|
origin: igp
|
||||||
|
communities:
|
||||||
|
- 200:200
|
||||||
|
`
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(tmpfile.Name(), []byte(updatedConfig), 0644)
|
||||||
|
a.NoError(err)
|
||||||
|
|
||||||
|
// Reload configuration
|
||||||
|
err = mon.Reload(tmpfile.Name())
|
||||||
|
a.NoError(err)
|
||||||
|
|
||||||
|
// Wait for reload to complete
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
// Verify new state
|
||||||
|
a.Equal(54321, mon.ctrl.localAS)
|
||||||
|
a.Equal([]string{"200:200"}, mon.ctrl.communities)
|
||||||
|
|
||||||
|
// Verify app was removed
|
||||||
|
mon.monMu.Lock()
|
||||||
|
_, exists := mon.monitors["test-app"]
|
||||||
|
mon.monMu.Unlock()
|
||||||
|
a.False(exists, "App should be removed after reload")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReloadAddApp(t *testing.T) {
|
||||||
|
if os.Getenv("CI") != "" {
|
||||||
|
t.Skip("Skipping reload test in CI environment")
|
||||||
|
}
|
||||||
|
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
// Create initial config without apps
|
||||||
|
initialConfig := `
|
||||||
|
agent:
|
||||||
|
listen_addr: :8080
|
||||||
|
monitor_interval: 10s
|
||||||
|
|
||||||
|
bgp:
|
||||||
|
local_as: 12345
|
||||||
|
peer_as: 6789
|
||||||
|
local_ip: 192.168.1.100
|
||||||
|
origin: igp
|
||||||
|
`
|
||||||
|
|
||||||
|
tmpfile, err := ioutil.TempFile("", "gocast-test-*.yaml")
|
||||||
|
a.NoError(err)
|
||||||
|
defer os.Remove(tmpfile.Name())
|
||||||
|
|
||||||
|
_, err = tmpfile.Write([]byte(initialConfig))
|
||||||
|
a.NoError(err)
|
||||||
|
tmpfile.Close()
|
||||||
|
|
||||||
|
conf := config.GetConfig(tmpfile.Name())
|
||||||
|
mon := NewMonitor(conf)
|
||||||
|
defer mon.CloseAll()
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// Verify no apps initially
|
||||||
|
mon.monMu.Lock()
|
||||||
|
initialCount := len(mon.monitors)
|
||||||
|
mon.monMu.Unlock()
|
||||||
|
a.Equal(0, initialCount)
|
||||||
|
|
||||||
|
// Add app to config
|
||||||
|
updatedConfig := `
|
||||||
|
agent:
|
||||||
|
listen_addr: :8080
|
||||||
|
monitor_interval: 10s
|
||||||
|
|
||||||
|
bgp:
|
||||||
|
local_as: 12345
|
||||||
|
peer_as: 6789
|
||||||
|
local_ip: 192.168.1.100
|
||||||
|
origin: igp
|
||||||
|
|
||||||
|
apps:
|
||||||
|
- name: new-app
|
||||||
|
vip: 2.2.2.2/32
|
||||||
|
monitors:
|
||||||
|
- exec:echo
|
||||||
|
`
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(tmpfile.Name(), []byte(updatedConfig), 0644)
|
||||||
|
a.NoError(err)
|
||||||
|
|
||||||
|
// Reload
|
||||||
|
err = mon.Reload(tmpfile.Name())
|
||||||
|
a.NoError(err)
|
||||||
|
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
// Verify app was added
|
||||||
|
mon.monMu.Lock()
|
||||||
|
_, exists := mon.monitors["new-app"]
|
||||||
|
mon.monMu.Unlock()
|
||||||
|
a.True(exists, "New app should be added after reload")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReloadMD5Change(t *testing.T) {
|
||||||
|
if os.Getenv("CI") != "" {
|
||||||
|
t.Skip("Skipping reload test in CI environment")
|
||||||
|
}
|
||||||
|
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
// Set environment variable for MD5 password
|
||||||
|
os.Setenv("BGP_TEST_PASSWORD", "initial_secret")
|
||||||
|
defer os.Unsetenv("BGP_TEST_PASSWORD")
|
||||||
|
|
||||||
|
initialConfig := `
|
||||||
|
agent:
|
||||||
|
listen_addr: :8080
|
||||||
|
|
||||||
|
bgp:
|
||||||
|
local_as: 12345
|
||||||
|
local_ip: 192.168.1.100
|
||||||
|
peers:
|
||||||
|
- peer_ip: 10.10.10.1
|
||||||
|
peer_as: 6789
|
||||||
|
md5_env_var: BGP_TEST_PASSWORD
|
||||||
|
origin: igp
|
||||||
|
`
|
||||||
|
|
||||||
|
tmpfile, err := ioutil.TempFile("", "gocast-test-*.yaml")
|
||||||
|
a.NoError(err)
|
||||||
|
defer os.Remove(tmpfile.Name())
|
||||||
|
|
||||||
|
_, err = tmpfile.Write([]byte(initialConfig))
|
||||||
|
a.NoError(err)
|
||||||
|
tmpfile.Close()
|
||||||
|
|
||||||
|
conf := config.GetConfig(tmpfile.Name())
|
||||||
|
mon := NewMonitor(conf)
|
||||||
|
defer mon.CloseAll()
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// Update environment variable
|
||||||
|
os.Setenv("BGP_TEST_PASSWORD", "updated_secret")
|
||||||
|
|
||||||
|
// Reload (MD5 env var change should trigger BGP reload)
|
||||||
|
err = mon.Reload(tmpfile.Name())
|
||||||
|
a.NoError(err)
|
||||||
|
|
||||||
|
// Note: We can't easily verify the MD5 password changed without
|
||||||
|
// actually establishing BGP sessions, but we can verify reload succeeded
|
||||||
|
a.NotNil(mon.ctrl)
|
||||||
|
}
|
||||||
@@ -7,11 +7,37 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var execCmd = "bash"
|
||||||
|
|
||||||
|
func getCmdList(mainCmd string) []string {
|
||||||
|
cmdList := []string{}
|
||||||
|
if execCmd == "bash" {
|
||||||
|
cmdList = append(cmdList, "-c")
|
||||||
|
}
|
||||||
|
cmdList = append(cmdList, mainCmd)
|
||||||
|
return cmdList
|
||||||
|
}
|
||||||
|
|
||||||
func gateway() (net.IP, error) {
|
func gateway() (net.IP, error) {
|
||||||
cmd := `ip route | grep "^default" | cut -d" " -f3`
|
cmd := `ip route | grep "^default" | cut -d" " -f3`
|
||||||
out, err := exec.Command("bash", "-c", cmd).Output()
|
cmdList := getCmdList(cmd)
|
||||||
|
out, err := exec.Command(execCmd, cmdList...).Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to execute command: %s", cmd)
|
return nil, fmt.Errorf("Failed to execute command: %s: %v", cmd, err)
|
||||||
|
}
|
||||||
|
return net.ParseIP(strings.TrimSpace(string(out))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func via(dest net.IP) (net.IP, error) {
|
||||||
|
cmd := fmt.Sprintf(`ip route get %s | grep via | cut -d" " -f3`, dest.String())
|
||||||
|
cmdList := getCmdList(cmd)
|
||||||
|
out, err := exec.Command(execCmd, cmdList...).Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to execute command: %s: %v", cmd, err)
|
||||||
|
}
|
||||||
|
if string(out) == "" {
|
||||||
|
// assume the provided dest is the next hop
|
||||||
|
return dest, nil
|
||||||
}
|
}
|
||||||
return net.ParseIP(strings.TrimSpace(string(out))), nil
|
return net.ParseIP(strings.TrimSpace(string(out))), nil
|
||||||
}
|
}
|
||||||
@@ -41,7 +67,8 @@ func addLoopback(name string, addr *net.IPNet) error {
|
|||||||
label = label[:15]
|
label = label[:15]
|
||||||
}
|
}
|
||||||
cmd := fmt.Sprintf("ip address add %s/%d dev lo label %s", addr.IP.String(), prefixLen, label)
|
cmd := fmt.Sprintf("ip address add %s/%d dev lo label %s", addr.IP.String(), prefixLen, label)
|
||||||
_, err := exec.Command("bash", "-c", cmd).Output()
|
cmdList := getCmdList(cmd)
|
||||||
|
_, err := exec.Command(execCmd, cmdList...).Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to Add loopback command: %s: %v", cmd, err)
|
return fmt.Errorf("Failed to Add loopback command: %s: %v", cmd, err)
|
||||||
}
|
}
|
||||||
@@ -51,19 +78,21 @@ func addLoopback(name string, addr *net.IPNet) error {
|
|||||||
func deleteLoopback(addr *net.IPNet) error {
|
func deleteLoopback(addr *net.IPNet) error {
|
||||||
prefixLen, _ := addr.Mask.Size()
|
prefixLen, _ := addr.Mask.Size()
|
||||||
cmd := fmt.Sprintf("ip address delete %s/%d dev lo", addr.IP.String(), prefixLen)
|
cmd := fmt.Sprintf("ip address delete %s/%d dev lo", addr.IP.String(), prefixLen)
|
||||||
_, err := exec.Command("bash", "-c", cmd).Output()
|
cmdList := getCmdList(cmd)
|
||||||
|
_, err := exec.Command(execCmd, cmdList...).Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to delete loopback command: %s: %v", cmd, err)
|
return fmt.Errorf("Failed to delete loopback command: %s: %v", cmd, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func natRule(op string, vip, localAddr net.IP, protocol, port string) error {
|
func natRule(op string, vip, localAddr net.IP, protocol, lport, dport string) error {
|
||||||
cmd := fmt.Sprintf(
|
cmd := fmt.Sprintf(
|
||||||
"iptables -t nat -%s PREROUTING -p %s -d %s --dport %s -j DNAT --to-destination %s:%s",
|
"iptables -t nat -%s PREROUTING -p %s -d %s --dport %s -j DNAT --to-destination %s:%s",
|
||||||
op, protocol, vip.String(), port, localAddr.String(), port,
|
op, protocol, vip.String(), lport, localAddr.String(), dport,
|
||||||
)
|
)
|
||||||
_, err := exec.Command("bash", "-c", cmd).Output()
|
cmdList := getCmdList(cmd)
|
||||||
|
_, err := exec.Command(execCmd, cmdList...).Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to %s nat rule: %s: %v", op, cmd, err)
|
return fmt.Errorf("Failed to %s nat rule: %s: %v", op, cmd, err)
|
||||||
}
|
}
|
||||||
|
|||||||
63
controller/system_test.go
Normal file
63
controller/system_test.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGateway(t *testing.T) {
|
||||||
|
execCmd = os.Args[0]
|
||||||
|
os.Setenv("test_name", "test_gateway")
|
||||||
|
gw, err := gateway()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "10.1.1.1", gw.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVia(t *testing.T) {
|
||||||
|
execCmd = os.Args[0]
|
||||||
|
os.Setenv("test_name", "test_via")
|
||||||
|
ip, err := via(net.ParseIP("10.1.2.100"))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "10.1.2.1", ip.String())
|
||||||
|
|
||||||
|
os.Setenv("test_name", "test_via_none")
|
||||||
|
ip, err = via(net.ParseIP("10.1.4.1"))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "10.1.4.1", ip.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddLoopback(t *testing.T) {
|
||||||
|
execCmd = os.Args[0]
|
||||||
|
os.Setenv("test_name", "test_add_pass")
|
||||||
|
_, ipnet, _ := net.ParseCIDR("1.1.1.1/32")
|
||||||
|
err := addLoopback("test_app", ipnet)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
os.Setenv("test_name", "test_add_fail")
|
||||||
|
_, ipnet, _ = net.ParseCIDR("1.1.1.1/32")
|
||||||
|
err = addLoopback("test_app", ipnet)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
switch os.Getenv("test_name") {
|
||||||
|
case "test_gateway":
|
||||||
|
fmt.Println("10.1.1.1")
|
||||||
|
case "test_via":
|
||||||
|
fmt.Println("10.1.2.1")
|
||||||
|
case "test_via_none":
|
||||||
|
break
|
||||||
|
case "test_add_fail":
|
||||||
|
os.Exit(1)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if os.Getenv("test_name") != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
13
go.mod
Normal file
13
go.mod
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module github.com/mayuresh82/gocast
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
||||||
|
github.com/golang/protobuf v1.4.1
|
||||||
|
github.com/osrg/gobgp v0.0.0-20211201041502-6248c576b118
|
||||||
|
github.com/sirupsen/logrus v1.8.1
|
||||||
|
github.com/stretchr/testify v1.7.1
|
||||||
|
google.golang.org/protobuf v1.25.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
)
|
||||||
387
go.sum
Normal file
387
go.sum
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
|
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||||
|
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
|
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
|
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||||
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
|
||||||
|
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
|
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
|
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
|
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
|
||||||
|
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||||
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
|
github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k=
|
||||||
|
github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0=
|
||||||
|
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||||
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
|
||||||
|
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
|
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||||
|
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
|
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||||
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
|
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||||
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||||
|
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||||
|
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||||
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/k-sone/critbitgo v1.4.0 h1:l71cTyBGeh6X5ATh6Fibgw3+rtNT80BA0uNNWgkPrbE=
|
||||||
|
github.com/k-sone/critbitgo v1.4.0/go.mod h1:7E6pyoyADnFxlUBEKcnfS49b7SUAQGMK+OAp/UQvo0s=
|
||||||
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||||
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
|
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||||
|
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||||
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
|
github.com/osrg/gobgp v0.0.0-20211201041502-6248c576b118 h1:RBgnasb5QRwCufRtRE2eGeSRHPrGgk0frtdmULtuIxo=
|
||||||
|
github.com/osrg/gobgp v0.0.0-20211201041502-6248c576b118/go.mod h1:QvEj9qq9o66TvTyFC0Yyn1zgSSFrno8MsptfrjyMR7A=
|
||||||
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
|
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||||
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
|
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||||
|
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
|
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||||
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
|
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||||
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
|
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
|
||||||
|
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||||
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||||
|
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
|
||||||
|
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||||
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
|
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5 h1:+UB2BJA852UkGH42H+Oee69djmxS3ANzl2b/JtT1YiA=
|
||||||
|
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||||
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
|
||||||
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||||
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c=
|
||||||
|
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||||
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||||
|
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
16
main.go
16
main.go
@@ -3,9 +3,11 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
|
"github.com/golang/glog"
|
||||||
c "github.com/mayuresh82/gocast/config"
|
c "github.com/mayuresh82/gocast/config"
|
||||||
"github.com/mayuresh82/gocast/controller"
|
"github.com/mayuresh82/gocast/controller"
|
||||||
"github.com/mayuresh82/gocast/server"
|
"github.com/mayuresh82/gocast/server"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -17,6 +19,9 @@ var (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
if glog.V(4) {
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
}
|
||||||
conf := c.GetConfig(*config)
|
conf := c.GetConfig(*config)
|
||||||
mon := controller.NewMonitor(conf)
|
mon := controller.NewMonitor(conf)
|
||||||
srv := server.NewServer(conf.Agent.ListenAddr, mon)
|
srv := server.NewServer(conf.Agent.ListenAddr, mon)
|
||||||
@@ -28,7 +33,16 @@ func main() {
|
|||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
sig := <-signalChan
|
sig := <-signalChan
|
||||||
if sig == os.Interrupt || sig == syscall.SIGTERM {
|
switch sig {
|
||||||
|
case syscall.SIGHUP:
|
||||||
|
log.Info("Received SIGHUP, reloading configuration")
|
||||||
|
if err := mon.Reload(*config); err != nil {
|
||||||
|
log.Errorf("Failed to reload configuration: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Info("Configuration reloaded successfully")
|
||||||
|
}
|
||||||
|
case os.Interrupt, syscall.SIGTERM:
|
||||||
|
log.Info("Received shutdown signal, cleaning up")
|
||||||
mon.CloseAll()
|
mon.CloseAll()
|
||||||
cancel()
|
cancel()
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/golang/glog"
|
|
||||||
"github.com/mayuresh82/gocast/controller"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/mayuresh82/gocast/config"
|
||||||
|
"github.com/mayuresh82/gocast/controller"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server is the main entrypoint into the app and serves app requests
|
// Server is the main entrypoint into the app and serves app requests
|
||||||
@@ -46,7 +49,11 @@ func (s *Server) Serve(ctx context.Context) {
|
|||||||
|
|
||||||
func (s *Server) registerHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) registerHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
queries := r.URL.Query()
|
queries := r.URL.Query()
|
||||||
app, err := controller.NewApp(queries["name"][0], queries["vip"][0], queries["monitor"], queries["nat"])
|
var vipConf config.VipConfig
|
||||||
|
if vipComm, ok := queries["vip_communities"]; ok {
|
||||||
|
vipConf.BgpCommunities = strings.Split(vipComm[0], ",")
|
||||||
|
}
|
||||||
|
app, err := controller.NewApp(queries["name"][0], queries["vip"][0], vipConf, queries["monitor"], queries["nat"], "http")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("Invalid request: %v", err), http.StatusBadRequest)
|
http.Error(w, fmt.Sprintf("Invalid request: %v", err), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
@@ -61,17 +68,15 @@ func (s *Server) unregisterHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "Invalid request, need app name specified", http.StatusBadRequest)
|
http.Error(w, "Invalid request, need app name specified", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.mon.Lock()
|
|
||||||
s.mon.Remove(appName[0])
|
s.mon.Remove(appName[0])
|
||||||
s.mon.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) infoHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) infoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
peer, err := s.mon.GetInfo()
|
peers, err := s.mon.GetInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("Internal error getting peers: %v", err), http.StatusInternalServerError)
|
http.Error(w, fmt.Sprintf("Internal error getting peers: %v", err), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(peer)
|
json.NewEncoder(w).Encode(peers)
|
||||||
}
|
}
|
||||||
|
|||||||
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
Normal file
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
ISC License
|
||||||
|
|
||||||
|
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
145
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
Normal file
145
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||||
|
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||||
|
// tag is deprecated and thus should not be used.
|
||||||
|
// Go versions prior to 1.4 are disabled because they use a different layout
|
||||||
|
// for interfaces which make the implementation of unsafeReflectValue more complex.
|
||||||
|
// +build !js,!appengine,!safe,!disableunsafe,go1.4
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||||
|
// not access to the unsafe package is available.
|
||||||
|
UnsafeDisabled = false
|
||||||
|
|
||||||
|
// ptrSize is the size of a pointer on the current arch.
|
||||||
|
ptrSize = unsafe.Sizeof((*byte)(nil))
|
||||||
|
)
|
||||||
|
|
||||||
|
type flag uintptr
|
||||||
|
|
||||||
|
var (
|
||||||
|
// flagRO indicates whether the value field of a reflect.Value
|
||||||
|
// is read-only.
|
||||||
|
flagRO flag
|
||||||
|
|
||||||
|
// flagAddr indicates whether the address of the reflect.Value's
|
||||||
|
// value may be taken.
|
||||||
|
flagAddr flag
|
||||||
|
)
|
||||||
|
|
||||||
|
// flagKindMask holds the bits that make up the kind
|
||||||
|
// part of the flags field. In all the supported versions,
|
||||||
|
// it is in the lower 5 bits.
|
||||||
|
const flagKindMask = flag(0x1f)
|
||||||
|
|
||||||
|
// Different versions of Go have used different
|
||||||
|
// bit layouts for the flags type. This table
|
||||||
|
// records the known combinations.
|
||||||
|
var okFlags = []struct {
|
||||||
|
ro, addr flag
|
||||||
|
}{{
|
||||||
|
// From Go 1.4 to 1.5
|
||||||
|
ro: 1 << 5,
|
||||||
|
addr: 1 << 7,
|
||||||
|
}, {
|
||||||
|
// Up to Go tip.
|
||||||
|
ro: 1<<5 | 1<<6,
|
||||||
|
addr: 1 << 8,
|
||||||
|
}}
|
||||||
|
|
||||||
|
var flagValOffset = func() uintptr {
|
||||||
|
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||||
|
if !ok {
|
||||||
|
panic("reflect.Value has no flag field")
|
||||||
|
}
|
||||||
|
return field.Offset
|
||||||
|
}()
|
||||||
|
|
||||||
|
// flagField returns a pointer to the flag field of a reflect.Value.
|
||||||
|
func flagField(v *reflect.Value) *flag {
|
||||||
|
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
||||||
|
// the typical safety restrictions preventing access to unaddressable and
|
||||||
|
// unexported data. It works by digging the raw pointer to the underlying
|
||||||
|
// value out of the protected value and generating a new unprotected (unsafe)
|
||||||
|
// reflect.Value to it.
|
||||||
|
//
|
||||||
|
// This allows us to check for implementations of the Stringer and error
|
||||||
|
// interfaces to be used for pretty printing ordinarily unaddressable and
|
||||||
|
// inaccessible values such as unexported struct fields.
|
||||||
|
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||||
|
if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
flagFieldPtr := flagField(&v)
|
||||||
|
*flagFieldPtr &^= flagRO
|
||||||
|
*flagFieldPtr |= flagAddr
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity checks against future reflect package changes
|
||||||
|
// to the type or semantics of the Value.flag field.
|
||||||
|
func init() {
|
||||||
|
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||||
|
if !ok {
|
||||||
|
panic("reflect.Value has no flag field")
|
||||||
|
}
|
||||||
|
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
|
||||||
|
panic("reflect.Value flag field has changed kind")
|
||||||
|
}
|
||||||
|
type t0 int
|
||||||
|
var t struct {
|
||||||
|
A t0
|
||||||
|
// t0 will have flagEmbedRO set.
|
||||||
|
t0
|
||||||
|
// a will have flagStickyRO set
|
||||||
|
a t0
|
||||||
|
}
|
||||||
|
vA := reflect.ValueOf(t).FieldByName("A")
|
||||||
|
va := reflect.ValueOf(t).FieldByName("a")
|
||||||
|
vt0 := reflect.ValueOf(t).FieldByName("t0")
|
||||||
|
|
||||||
|
// Infer flagRO from the difference between the flags
|
||||||
|
// for the (otherwise identical) fields in t.
|
||||||
|
flagPublic := *flagField(&vA)
|
||||||
|
flagWithRO := *flagField(&va) | *flagField(&vt0)
|
||||||
|
flagRO = flagPublic ^ flagWithRO
|
||||||
|
|
||||||
|
// Infer flagAddr from the difference between a value
|
||||||
|
// taken from a pointer and not.
|
||||||
|
vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
|
||||||
|
flagNoPtr := *flagField(&vA)
|
||||||
|
flagPtr := *flagField(&vPtrA)
|
||||||
|
flagAddr = flagNoPtr ^ flagPtr
|
||||||
|
|
||||||
|
// Check that the inferred flags tally with one of the known versions.
|
||||||
|
for _, f := range okFlags {
|
||||||
|
if flagRO == f.ro && flagAddr == f.addr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("reflect.Value read-only flag has changed semantics")
|
||||||
|
}
|
||||||
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
Normal file
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is running on Google App Engine, compiled by GopherJS, or
|
||||||
|
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
||||||
|
// tag is deprecated and thus should not be used.
|
||||||
|
// +build js appengine safe disableunsafe !go1.4
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||||
|
// not access to the unsafe package is available.
|
||||||
|
UnsafeDisabled = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
||||||
|
// that bypasses the typical safety restrictions preventing access to
|
||||||
|
// unaddressable and unexported data. However, doing this relies on access to
|
||||||
|
// the unsafe package. This is a stub version which simply returns the passed
|
||||||
|
// reflect.Value when the unsafe package is not available.
|
||||||
|
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||||
|
return v
|
||||||
|
}
|
||||||
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
Normal file
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
||||||
|
// the technique used in the fmt package.
|
||||||
|
var (
|
||||||
|
panicBytes = []byte("(PANIC=")
|
||||||
|
plusBytes = []byte("+")
|
||||||
|
iBytes = []byte("i")
|
||||||
|
trueBytes = []byte("true")
|
||||||
|
falseBytes = []byte("false")
|
||||||
|
interfaceBytes = []byte("(interface {})")
|
||||||
|
commaNewlineBytes = []byte(",\n")
|
||||||
|
newlineBytes = []byte("\n")
|
||||||
|
openBraceBytes = []byte("{")
|
||||||
|
openBraceNewlineBytes = []byte("{\n")
|
||||||
|
closeBraceBytes = []byte("}")
|
||||||
|
asteriskBytes = []byte("*")
|
||||||
|
colonBytes = []byte(":")
|
||||||
|
colonSpaceBytes = []byte(": ")
|
||||||
|
openParenBytes = []byte("(")
|
||||||
|
closeParenBytes = []byte(")")
|
||||||
|
spaceBytes = []byte(" ")
|
||||||
|
pointerChainBytes = []byte("->")
|
||||||
|
nilAngleBytes = []byte("<nil>")
|
||||||
|
maxNewlineBytes = []byte("<max depth reached>\n")
|
||||||
|
maxShortBytes = []byte("<max>")
|
||||||
|
circularBytes = []byte("<already shown>")
|
||||||
|
circularShortBytes = []byte("<shown>")
|
||||||
|
invalidAngleBytes = []byte("<invalid>")
|
||||||
|
openBracketBytes = []byte("[")
|
||||||
|
closeBracketBytes = []byte("]")
|
||||||
|
percentBytes = []byte("%")
|
||||||
|
precisionBytes = []byte(".")
|
||||||
|
openAngleBytes = []byte("<")
|
||||||
|
closeAngleBytes = []byte(">")
|
||||||
|
openMapBytes = []byte("map[")
|
||||||
|
closeMapBytes = []byte("]")
|
||||||
|
lenEqualsBytes = []byte("len=")
|
||||||
|
capEqualsBytes = []byte("cap=")
|
||||||
|
)
|
||||||
|
|
||||||
|
// hexDigits is used to map a decimal value to a hex digit.
|
||||||
|
var hexDigits = "0123456789abcdef"
|
||||||
|
|
||||||
|
// catchPanic handles any panics that might occur during the handleMethods
|
||||||
|
// calls.
|
||||||
|
func catchPanic(w io.Writer, v reflect.Value) {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
w.Write(panicBytes)
|
||||||
|
fmt.Fprintf(w, "%v", err)
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleMethods attempts to call the Error and String methods on the underlying
|
||||||
|
// type the passed reflect.Value represents and outputes the result to Writer w.
|
||||||
|
//
|
||||||
|
// It handles panics in any called methods by catching and displaying the error
|
||||||
|
// as the formatted value.
|
||||||
|
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
||||||
|
// We need an interface to check if the type implements the error or
|
||||||
|
// Stringer interface. However, the reflect package won't give us an
|
||||||
|
// interface on certain things like unexported struct fields in order
|
||||||
|
// to enforce visibility rules. We use unsafe, when it's available,
|
||||||
|
// to bypass these restrictions since this package does not mutate the
|
||||||
|
// values.
|
||||||
|
if !v.CanInterface() {
|
||||||
|
if UnsafeDisabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
v = unsafeReflectValue(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose whether or not to do error and Stringer interface lookups against
|
||||||
|
// the base type or a pointer to the base type depending on settings.
|
||||||
|
// Technically calling one of these methods with a pointer receiver can
|
||||||
|
// mutate the value, however, types which choose to satisify an error or
|
||||||
|
// Stringer interface with a pointer receiver should not be mutating their
|
||||||
|
// state inside these interface methods.
|
||||||
|
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
||||||
|
v = unsafeReflectValue(v)
|
||||||
|
}
|
||||||
|
if v.CanAddr() {
|
||||||
|
v = v.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it an error or Stringer?
|
||||||
|
switch iface := v.Interface().(type) {
|
||||||
|
case error:
|
||||||
|
defer catchPanic(w, v)
|
||||||
|
if cs.ContinueOnMethod {
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(iface.Error()))
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(iface.Error()))
|
||||||
|
return true
|
||||||
|
|
||||||
|
case fmt.Stringer:
|
||||||
|
defer catchPanic(w, v)
|
||||||
|
if cs.ContinueOnMethod {
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(iface.String()))
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
w.Write([]byte(iface.String()))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// printBool outputs a boolean value as true or false to Writer w.
|
||||||
|
func printBool(w io.Writer, val bool) {
|
||||||
|
if val {
|
||||||
|
w.Write(trueBytes)
|
||||||
|
} else {
|
||||||
|
w.Write(falseBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printInt outputs a signed integer value to Writer w.
|
||||||
|
func printInt(w io.Writer, val int64, base int) {
|
||||||
|
w.Write([]byte(strconv.FormatInt(val, base)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printUint outputs an unsigned integer value to Writer w.
|
||||||
|
func printUint(w io.Writer, val uint64, base int) {
|
||||||
|
w.Write([]byte(strconv.FormatUint(val, base)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printFloat outputs a floating point value using the specified precision,
|
||||||
|
// which is expected to be 32 or 64bit, to Writer w.
|
||||||
|
func printFloat(w io.Writer, val float64, precision int) {
|
||||||
|
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printComplex outputs a complex value using the specified float precision
|
||||||
|
// for the real and imaginary parts to Writer w.
|
||||||
|
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||||||
|
r := real(c)
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
||||||
|
i := imag(c)
|
||||||
|
if i >= 0 {
|
||||||
|
w.Write(plusBytes)
|
||||||
|
}
|
||||||
|
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
||||||
|
w.Write(iBytes)
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
|
||||||
|
// prefix to Writer w.
|
||||||
|
func printHexPtr(w io.Writer, p uintptr) {
|
||||||
|
// Null pointer.
|
||||||
|
num := uint64(p)
|
||||||
|
if num == 0 {
|
||||||
|
w.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
||||||
|
buf := make([]byte, 18)
|
||||||
|
|
||||||
|
// It's simpler to construct the hex string right to left.
|
||||||
|
base := uint64(16)
|
||||||
|
i := len(buf) - 1
|
||||||
|
for num >= base {
|
||||||
|
buf[i] = hexDigits[num%base]
|
||||||
|
num /= base
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
buf[i] = hexDigits[num]
|
||||||
|
|
||||||
|
// Add '0x' prefix.
|
||||||
|
i--
|
||||||
|
buf[i] = 'x'
|
||||||
|
i--
|
||||||
|
buf[i] = '0'
|
||||||
|
|
||||||
|
// Strip unused leading bytes.
|
||||||
|
buf = buf[i:]
|
||||||
|
w.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||||||
|
// elements to be sorted.
|
||||||
|
type valuesSorter struct {
|
||||||
|
values []reflect.Value
|
||||||
|
strings []string // either nil or same len and values
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
||||||
|
// surrogate keys on which the data should be sorted. It uses flags in
|
||||||
|
// ConfigState to decide if and how to populate those surrogate keys.
|
||||||
|
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
||||||
|
vs := &valuesSorter{values: values, cs: cs}
|
||||||
|
if canSortSimply(vs.values[0].Kind()) {
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
if !cs.DisableMethods {
|
||||||
|
vs.strings = make([]string, len(values))
|
||||||
|
for i := range vs.values {
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
if !handleMethods(cs, &b, vs.values[i]) {
|
||||||
|
vs.strings = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
vs.strings[i] = b.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vs.strings == nil && cs.SpewKeys {
|
||||||
|
vs.strings = make([]string, len(values))
|
||||||
|
for i := range vs.values {
|
||||||
|
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
|
||||||
|
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
||||||
|
// directly, or whether it should be considered for sorting by surrogate keys
|
||||||
|
// (if the ConfigState allows it).
|
||||||
|
func canSortSimply(kind reflect.Kind) bool {
|
||||||
|
// This switch parallels valueSortLess, except for the default case.
|
||||||
|
switch kind {
|
||||||
|
case reflect.Bool:
|
||||||
|
return true
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
return true
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
return true
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return true
|
||||||
|
case reflect.String:
|
||||||
|
return true
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return true
|
||||||
|
case reflect.Array:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of values in the slice. It is part of the
|
||||||
|
// sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Len() int {
|
||||||
|
return len(s.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap swaps the values at the passed indices. It is part of the
|
||||||
|
// sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Swap(i, j int) {
|
||||||
|
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||||
|
if s.strings != nil {
|
||||||
|
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// valueSortLess returns whether the first value should sort before the second
|
||||||
|
// value. It is used by valueSorter.Less as part of the sort.Interface
|
||||||
|
// implementation.
|
||||||
|
func valueSortLess(a, b reflect.Value) bool {
|
||||||
|
switch a.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return !a.Bool() && b.Bool()
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
return a.Int() < b.Int()
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
return a.Uint() < b.Uint()
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return a.Float() < b.Float()
|
||||||
|
case reflect.String:
|
||||||
|
return a.String() < b.String()
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return a.Uint() < b.Uint()
|
||||||
|
case reflect.Array:
|
||||||
|
// Compare the contents of both arrays.
|
||||||
|
l := a.Len()
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
av := a.Index(i)
|
||||||
|
bv := b.Index(i)
|
||||||
|
if av.Interface() == bv.Interface() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return valueSortLess(av, bv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.String() < b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less returns whether the value at index i should sort before the
|
||||||
|
// value at index j. It is part of the sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Less(i, j int) bool {
|
||||||
|
if s.strings == nil {
|
||||||
|
return valueSortLess(s.values[i], s.values[j])
|
||||||
|
}
|
||||||
|
return s.strings[i] < s.strings[j]
|
||||||
|
}
|
||||||
|
|
||||||
|
// sortValues is a sort function that handles both native types and any type that
|
||||||
|
// can be converted to error or Stringer. Other inputs are sorted according to
|
||||||
|
// their Value.String() value to ensure display stability.
|
||||||
|
func sortValues(values []reflect.Value, cs *ConfigState) {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sort.Sort(newValuesSorter(values, cs))
|
||||||
|
}
|
||||||
306
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
Normal file
306
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigState houses the configuration options used by spew to format and
|
||||||
|
// display values. There is a global instance, Config, that is used to control
|
||||||
|
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
||||||
|
// provides methods equivalent to the top-level functions.
|
||||||
|
//
|
||||||
|
// The zero value for ConfigState provides no indentation. You would typically
|
||||||
|
// want to set it to a space or a tab.
|
||||||
|
//
|
||||||
|
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
||||||
|
// with default settings. See the documentation of NewDefaultConfig for default
|
||||||
|
// values.
|
||||||
|
type ConfigState struct {
|
||||||
|
// Indent specifies the string to use for each indentation level. The
|
||||||
|
// global config instance that all top-level functions use set this to a
|
||||||
|
// single space by default. If you would like more indentation, you might
|
||||||
|
// set this to a tab with "\t" or perhaps two spaces with " ".
|
||||||
|
Indent string
|
||||||
|
|
||||||
|
// MaxDepth controls the maximum number of levels to descend into nested
|
||||||
|
// data structures. The default, 0, means there is no limit.
|
||||||
|
//
|
||||||
|
// NOTE: Circular data structures are properly detected, so it is not
|
||||||
|
// necessary to set this value unless you specifically want to limit deeply
|
||||||
|
// nested data structures.
|
||||||
|
MaxDepth int
|
||||||
|
|
||||||
|
// DisableMethods specifies whether or not error and Stringer interfaces are
|
||||||
|
// invoked for types that implement them.
|
||||||
|
DisableMethods bool
|
||||||
|
|
||||||
|
// DisablePointerMethods specifies whether or not to check for and invoke
|
||||||
|
// error and Stringer interfaces on types which only accept a pointer
|
||||||
|
// receiver when the current type is not a pointer.
|
||||||
|
//
|
||||||
|
// NOTE: This might be an unsafe action since calling one of these methods
|
||||||
|
// with a pointer receiver could technically mutate the value, however,
|
||||||
|
// in practice, types which choose to satisify an error or Stringer
|
||||||
|
// interface with a pointer receiver should not be mutating their state
|
||||||
|
// inside these interface methods. As a result, this option relies on
|
||||||
|
// access to the unsafe package, so it will not have any effect when
|
||||||
|
// running in environments without access to the unsafe package such as
|
||||||
|
// Google App Engine or with the "safe" build tag specified.
|
||||||
|
DisablePointerMethods bool
|
||||||
|
|
||||||
|
// DisablePointerAddresses specifies whether to disable the printing of
|
||||||
|
// pointer addresses. This is useful when diffing data structures in tests.
|
||||||
|
DisablePointerAddresses bool
|
||||||
|
|
||||||
|
// DisableCapacities specifies whether to disable the printing of capacities
|
||||||
|
// for arrays, slices, maps and channels. This is useful when diffing
|
||||||
|
// data structures in tests.
|
||||||
|
DisableCapacities bool
|
||||||
|
|
||||||
|
// ContinueOnMethod specifies whether or not recursion should continue once
|
||||||
|
// a custom error or Stringer interface is invoked. The default, false,
|
||||||
|
// means it will print the results of invoking the custom error or Stringer
|
||||||
|
// interface and return immediately instead of continuing to recurse into
|
||||||
|
// the internals of the data type.
|
||||||
|
//
|
||||||
|
// NOTE: This flag does not have any effect if method invocation is disabled
|
||||||
|
// via the DisableMethods or DisablePointerMethods options.
|
||||||
|
ContinueOnMethod bool
|
||||||
|
|
||||||
|
// SortKeys specifies map keys should be sorted before being printed. Use
|
||||||
|
// this to have a more deterministic, diffable output. Note that only
|
||||||
|
// native types (bool, int, uint, floats, uintptr and string) and types
|
||||||
|
// that support the error or Stringer interfaces (if methods are
|
||||||
|
// enabled) are supported, with other types sorted according to the
|
||||||
|
// reflect.Value.String() output which guarantees display stability.
|
||||||
|
SortKeys bool
|
||||||
|
|
||||||
|
// SpewKeys specifies that, as a last resort attempt, map keys should
|
||||||
|
// be spewed to strings and sorted by those strings. This is only
|
||||||
|
// considered if SortKeys is true.
|
||||||
|
SpewKeys bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the active configuration of the top-level functions.
|
||||||
|
// The configuration can be changed by modifying the contents of spew.Config.
|
||||||
|
var Config = ConfigState{Indent: " "}
|
||||||
|
|
||||||
|
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the formatted string as a value that satisfies error. See NewFormatter
|
||||||
|
// for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
||||||
|
return fmt.Errorf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprint(w, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintln(w, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Print(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Printf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Println(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprint(a ...interface{}) string {
|
||||||
|
return fmt.Sprint(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
||||||
|
return fmt.Sprintf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||||
|
// were passed with a Formatter interface returned by c.NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
||||||
|
return fmt.Sprintln(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||||
|
interface. As a result, it integrates cleanly with standard fmt package
|
||||||
|
printing functions. The formatter is useful for inline printing of smaller data
|
||||||
|
types similar to the standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Typically this function shouldn't be called directly. It is much easier to make
|
||||||
|
use of the custom formatter by calling one of the convenience functions such as
|
||||||
|
c.Printf, c.Println, or c.Printf.
|
||||||
|
*/
|
||||||
|
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
||||||
|
return newFormatter(c, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||||
|
// exactly the same as Dump.
|
||||||
|
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
||||||
|
fdump(c, w, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dump displays the passed parameters to standard out with newlines, customizable
|
||||||
|
indentation, and additional debug information such as complete types and all
|
||||||
|
pointer addresses used to indirect to the final value. It provides the
|
||||||
|
following features over the built-in printing facilities provided by the fmt
|
||||||
|
package:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output
|
||||||
|
|
||||||
|
The configuration options are controlled by modifying the public members
|
||||||
|
of c. See ConfigState for options documentation.
|
||||||
|
|
||||||
|
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||||
|
get the formatted result as a string.
|
||||||
|
*/
|
||||||
|
func (c *ConfigState) Dump(a ...interface{}) {
|
||||||
|
fdump(c, os.Stdout, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||||
|
// as Dump.
|
||||||
|
func (c *ConfigState) Sdump(a ...interface{}) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fdump(c, &buf, a...)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||||
|
// length with each argument converted to a spew Formatter interface using
|
||||||
|
// the ConfigState associated with s.
|
||||||
|
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
||||||
|
formatters = make([]interface{}, len(args))
|
||||||
|
for index, arg := range args {
|
||||||
|
formatters[index] = newFormatter(c, arg)
|
||||||
|
}
|
||||||
|
return formatters
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultConfig returns a ConfigState with the following default settings.
|
||||||
|
//
|
||||||
|
// Indent: " "
|
||||||
|
// MaxDepth: 0
|
||||||
|
// DisableMethods: false
|
||||||
|
// DisablePointerMethods: false
|
||||||
|
// ContinueOnMethod: false
|
||||||
|
// SortKeys: false
|
||||||
|
func NewDefaultConfig() *ConfigState {
|
||||||
|
return &ConfigState{Indent: " "}
|
||||||
|
}
|
||||||
211
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
Normal file
211
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package spew implements a deep pretty printer for Go data structures to aid in
|
||||||
|
debugging.
|
||||||
|
|
||||||
|
A quick overview of the additional features spew provides over the built-in
|
||||||
|
printing facilities for Go data types are as follows:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output (only when using
|
||||||
|
Dump style)
|
||||||
|
|
||||||
|
There are two different approaches spew allows for dumping Go data structures:
|
||||||
|
|
||||||
|
* Dump style which prints with newlines, customizable indentation,
|
||||||
|
and additional debug information such as types and all pointer addresses
|
||||||
|
used to indirect to the final value
|
||||||
|
* A custom Formatter interface that integrates cleanly with the standard fmt
|
||||||
|
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
||||||
|
similar to the default %v while providing the additional functionality
|
||||||
|
outlined above and passing unsupported format verbs such as %x and %q
|
||||||
|
along to fmt
|
||||||
|
|
||||||
|
Quick Start
|
||||||
|
|
||||||
|
This section demonstrates how to quickly get started with spew. See the
|
||||||
|
sections below for further details on formatting and configuration options.
|
||||||
|
|
||||||
|
To dump a variable with full newlines, indentation, type, and pointer
|
||||||
|
information use Dump, Fdump, or Sdump:
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||||
|
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
||||||
|
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
||||||
|
%#+v (adds types and pointer addresses):
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
|
||||||
|
Configuration Options
|
||||||
|
|
||||||
|
Configuration of spew is handled by fields in the ConfigState type. For
|
||||||
|
convenience, all of the top-level functions use a global state available
|
||||||
|
via the spew.Config global.
|
||||||
|
|
||||||
|
It is also possible to create a ConfigState instance that provides methods
|
||||||
|
equivalent to the top-level functions. This allows concurrent configuration
|
||||||
|
options. See the ConfigState documentation for more details.
|
||||||
|
|
||||||
|
The following configuration options are available:
|
||||||
|
* Indent
|
||||||
|
String to use for each indentation level for Dump functions.
|
||||||
|
It is a single space by default. A popular alternative is "\t".
|
||||||
|
|
||||||
|
* MaxDepth
|
||||||
|
Maximum number of levels to descend into nested data structures.
|
||||||
|
There is no limit by default.
|
||||||
|
|
||||||
|
* DisableMethods
|
||||||
|
Disables invocation of error and Stringer interface methods.
|
||||||
|
Method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerMethods
|
||||||
|
Disables invocation of error and Stringer interface methods on types
|
||||||
|
which only accept pointer receivers from non-pointer variables.
|
||||||
|
Pointer method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerAddresses
|
||||||
|
DisablePointerAddresses specifies whether to disable the printing of
|
||||||
|
pointer addresses. This is useful when diffing data structures in tests.
|
||||||
|
|
||||||
|
* DisableCapacities
|
||||||
|
DisableCapacities specifies whether to disable the printing of
|
||||||
|
capacities for arrays, slices, maps and channels. This is useful when
|
||||||
|
diffing data structures in tests.
|
||||||
|
|
||||||
|
* ContinueOnMethod
|
||||||
|
Enables recursion into types after invoking error and Stringer interface
|
||||||
|
methods. Recursion after method invocation is disabled by default.
|
||||||
|
|
||||||
|
* SortKeys
|
||||||
|
Specifies map keys should be sorted before being printed. Use
|
||||||
|
this to have a more deterministic, diffable output. Note that
|
||||||
|
only native types (bool, int, uint, floats, uintptr and string)
|
||||||
|
and types which implement error or Stringer interfaces are
|
||||||
|
supported with other types sorted according to the
|
||||||
|
reflect.Value.String() output which guarantees display
|
||||||
|
stability. Natural map order is used by default.
|
||||||
|
|
||||||
|
* SpewKeys
|
||||||
|
Specifies that, as a last resort attempt, map keys should be
|
||||||
|
spewed to strings and sorted by those strings. This is only
|
||||||
|
considered if SortKeys is true.
|
||||||
|
|
||||||
|
Dump Usage
|
||||||
|
|
||||||
|
Simply call spew.Dump with a list of variables you want to dump:
|
||||||
|
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
||||||
|
io.Writer. For example, to dump to standard error:
|
||||||
|
|
||||||
|
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
A third option is to call spew.Sdump to get the formatted output as a string:
|
||||||
|
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
Sample Dump Output
|
||||||
|
|
||||||
|
See the Dump example for details on the setup of the types and variables being
|
||||||
|
shown here.
|
||||||
|
|
||||||
|
(main.Foo) {
|
||||||
|
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||||
|
flag: (main.Flag) flagTwo,
|
||||||
|
data: (uintptr) <nil>
|
||||||
|
}),
|
||||||
|
ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
(string) (len=3) "one": (bool) true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
||||||
|
command as shown.
|
||||||
|
([]uint8) (len=32 cap=32) {
|
||||||
|
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||||
|
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||||
|
00000020 31 32 |12|
|
||||||
|
}
|
||||||
|
|
||||||
|
Custom Formatter
|
||||||
|
|
||||||
|
Spew provides a custom formatter that implements the fmt.Formatter interface
|
||||||
|
so that it integrates cleanly with standard fmt package printing functions. The
|
||||||
|
formatter is useful for inline printing of smaller data types similar to the
|
||||||
|
standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Custom Formatter Usage
|
||||||
|
|
||||||
|
The simplest way to make use of the spew custom formatter is to call one of the
|
||||||
|
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
||||||
|
functions have syntax you are most likely already familiar with:
|
||||||
|
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Println(myVar, myVar2)
|
||||||
|
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
|
||||||
|
See the Index for the full list convenience functions.
|
||||||
|
|
||||||
|
Sample Formatter Output
|
||||||
|
|
||||||
|
Double pointer to a uint8:
|
||||||
|
%v: <**>5
|
||||||
|
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||||
|
%#v: (**uint8)5
|
||||||
|
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||||
|
|
||||||
|
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||||
|
%v: <*>{1 <*><shown>}
|
||||||
|
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||||
|
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||||
|
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||||
|
|
||||||
|
See the Printf example for details on the setup of variables being shown
|
||||||
|
here.
|
||||||
|
|
||||||
|
Errors
|
||||||
|
|
||||||
|
Since it is possible for custom Stringer/error interfaces to panic, spew
|
||||||
|
detects them and handles them internally by printing the panic information
|
||||||
|
inline with the output. Since spew is intended to provide deep pretty printing
|
||||||
|
capabilities on structures, it intentionally does not return any errors.
|
||||||
|
*/
|
||||||
|
package spew
|
||||||
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
Normal file
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
Normal file
@@ -0,0 +1,509 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// uint8Type is a reflect.Type representing a uint8. It is used to
|
||||||
|
// convert cgo types to uint8 slices for hexdumping.
|
||||||
|
uint8Type = reflect.TypeOf(uint8(0))
|
||||||
|
|
||||||
|
// cCharRE is a regular expression that matches a cgo char.
|
||||||
|
// It is used to detect character arrays to hexdump them.
|
||||||
|
cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
|
||||||
|
|
||||||
|
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
||||||
|
// char. It is used to detect unsigned character arrays to hexdump
|
||||||
|
// them.
|
||||||
|
cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
|
||||||
|
|
||||||
|
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
||||||
|
// It is used to detect uint8_t arrays to hexdump them.
|
||||||
|
cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// dumpState contains information about the state of a dump operation.
|
||||||
|
type dumpState struct {
|
||||||
|
w io.Writer
|
||||||
|
depth int
|
||||||
|
pointers map[uintptr]int
|
||||||
|
ignoreNextType bool
|
||||||
|
ignoreNextIndent bool
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// indent performs indentation according to the depth level and cs.Indent
|
||||||
|
// option.
|
||||||
|
func (d *dumpState) indent() {
|
||||||
|
if d.ignoreNextIndent {
|
||||||
|
d.ignoreNextIndent = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackValue returns values inside of non-nil interfaces when possible.
|
||||||
|
// This is useful for data types like structs, arrays, slices, and maps which
|
||||||
|
// can contain varying types packed inside an interface.
|
||||||
|
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
||||||
|
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
||||||
|
func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||||
|
// Remove pointers at or below the current depth from map used to detect
|
||||||
|
// circular refs.
|
||||||
|
for k, depth := range d.pointers {
|
||||||
|
if depth >= d.depth {
|
||||||
|
delete(d.pointers, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep list of all dereferenced pointers to show later.
|
||||||
|
pointerChain := make([]uintptr, 0)
|
||||||
|
|
||||||
|
// Figure out how many levels of indirection there are by dereferencing
|
||||||
|
// pointers and unpacking interfaces down the chain while detecting circular
|
||||||
|
// references.
|
||||||
|
nilFound := false
|
||||||
|
cycleFound := false
|
||||||
|
indirects := 0
|
||||||
|
ve := v
|
||||||
|
for ve.Kind() == reflect.Ptr {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indirects++
|
||||||
|
addr := ve.Pointer()
|
||||||
|
pointerChain = append(pointerChain, addr)
|
||||||
|
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
||||||
|
cycleFound = true
|
||||||
|
indirects--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
d.pointers[addr] = d.depth
|
||||||
|
|
||||||
|
ve = ve.Elem()
|
||||||
|
if ve.Kind() == reflect.Interface {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ve = ve.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display type information.
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||||
|
d.w.Write([]byte(ve.Type().String()))
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
|
||||||
|
// Display pointer information.
|
||||||
|
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
for i, addr := range pointerChain {
|
||||||
|
if i > 0 {
|
||||||
|
d.w.Write(pointerChainBytes)
|
||||||
|
}
|
||||||
|
printHexPtr(d.w, addr)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display dereferenced value.
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
switch {
|
||||||
|
case nilFound:
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
|
||||||
|
case cycleFound:
|
||||||
|
d.w.Write(circularBytes)
|
||||||
|
|
||||||
|
default:
|
||||||
|
d.ignoreNextType = true
|
||||||
|
d.dump(ve)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
||||||
|
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
||||||
|
func (d *dumpState) dumpSlice(v reflect.Value) {
|
||||||
|
// Determine whether this type should be hex dumped or not. Also,
|
||||||
|
// for types which should be hexdumped, try to use the underlying data
|
||||||
|
// first, then fall back to trying to convert them to a uint8 slice.
|
||||||
|
var buf []uint8
|
||||||
|
doConvert := false
|
||||||
|
doHexDump := false
|
||||||
|
numEntries := v.Len()
|
||||||
|
if numEntries > 0 {
|
||||||
|
vt := v.Index(0).Type()
|
||||||
|
vts := vt.String()
|
||||||
|
switch {
|
||||||
|
// C types that need to be converted.
|
||||||
|
case cCharRE.MatchString(vts):
|
||||||
|
fallthrough
|
||||||
|
case cUnsignedCharRE.MatchString(vts):
|
||||||
|
fallthrough
|
||||||
|
case cUint8tCharRE.MatchString(vts):
|
||||||
|
doConvert = true
|
||||||
|
|
||||||
|
// Try to use existing uint8 slices and fall back to converting
|
||||||
|
// and copying if that fails.
|
||||||
|
case vt.Kind() == reflect.Uint8:
|
||||||
|
// We need an addressable interface to convert the type
|
||||||
|
// to a byte slice. However, the reflect package won't
|
||||||
|
// give us an interface on certain things like
|
||||||
|
// unexported struct fields in order to enforce
|
||||||
|
// visibility rules. We use unsafe, when available, to
|
||||||
|
// bypass these restrictions since this package does not
|
||||||
|
// mutate the values.
|
||||||
|
vs := v
|
||||||
|
if !vs.CanInterface() || !vs.CanAddr() {
|
||||||
|
vs = unsafeReflectValue(vs)
|
||||||
|
}
|
||||||
|
if !UnsafeDisabled {
|
||||||
|
vs = vs.Slice(0, numEntries)
|
||||||
|
|
||||||
|
// Use the existing uint8 slice if it can be
|
||||||
|
// type asserted.
|
||||||
|
iface := vs.Interface()
|
||||||
|
if slice, ok := iface.([]uint8); ok {
|
||||||
|
buf = slice
|
||||||
|
doHexDump = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The underlying data needs to be converted if it can't
|
||||||
|
// be type asserted to a uint8 slice.
|
||||||
|
doConvert = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy and convert the underlying type if needed.
|
||||||
|
if doConvert && vt.ConvertibleTo(uint8Type) {
|
||||||
|
// Convert and copy each element into a uint8 byte
|
||||||
|
// slice.
|
||||||
|
buf = make([]uint8, numEntries)
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
vv := v.Index(i)
|
||||||
|
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
||||||
|
}
|
||||||
|
doHexDump = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hexdump the entire slice as needed.
|
||||||
|
if doHexDump {
|
||||||
|
indent := strings.Repeat(d.cs.Indent, d.depth)
|
||||||
|
str := indent + hex.Dump(buf)
|
||||||
|
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
||||||
|
str = strings.TrimRight(str, d.cs.Indent)
|
||||||
|
d.w.Write([]byte(str))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively call dump for each item.
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
d.dump(d.unpackValue(v.Index(i)))
|
||||||
|
if i < (numEntries - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
||||||
|
// value to figure out what kind of object we are dealing with and formats it
|
||||||
|
// appropriately. It is a recursive function, however circular data structures
|
||||||
|
// are detected and handled properly.
|
||||||
|
func (d *dumpState) dump(v reflect.Value) {
|
||||||
|
// Handle invalid reflect values immediately.
|
||||||
|
kind := v.Kind()
|
||||||
|
if kind == reflect.Invalid {
|
||||||
|
d.w.Write(invalidAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle pointers specially.
|
||||||
|
if kind == reflect.Ptr {
|
||||||
|
d.indent()
|
||||||
|
d.dumpPtr(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print type information unless already handled elsewhere.
|
||||||
|
if !d.ignoreNextType {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
d.w.Write([]byte(v.Type().String()))
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
d.ignoreNextType = false
|
||||||
|
|
||||||
|
// Display length and capacity if the built-in len and cap functions
|
||||||
|
// work with the value's kind and the len/cap itself is non-zero.
|
||||||
|
valueLen, valueCap := 0, 0
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice, reflect.Chan:
|
||||||
|
valueLen, valueCap = v.Len(), v.Cap()
|
||||||
|
case reflect.Map, reflect.String:
|
||||||
|
valueLen = v.Len()
|
||||||
|
}
|
||||||
|
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
if valueLen != 0 {
|
||||||
|
d.w.Write(lenEqualsBytes)
|
||||||
|
printInt(d.w, int64(valueLen), 10)
|
||||||
|
}
|
||||||
|
if !d.cs.DisableCapacities && valueCap != 0 {
|
||||||
|
if valueLen != 0 {
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
d.w.Write(capEqualsBytes)
|
||||||
|
printInt(d.w, int64(valueCap), 10)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call Stringer/error interfaces if they exist and the handle methods flag
|
||||||
|
// is enabled
|
||||||
|
if !d.cs.DisableMethods {
|
||||||
|
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||||
|
if handled := handleMethods(d.cs, d.w, v); handled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Invalid:
|
||||||
|
// Do nothing. We should never get here since invalid has already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
printBool(d.w, v.Bool())
|
||||||
|
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
printInt(d.w, v.Int(), 10)
|
||||||
|
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
printUint(d.w, v.Uint(), 10)
|
||||||
|
|
||||||
|
case reflect.Float32:
|
||||||
|
printFloat(d.w, v.Float(), 32)
|
||||||
|
|
||||||
|
case reflect.Float64:
|
||||||
|
printFloat(d.w, v.Float(), 64)
|
||||||
|
|
||||||
|
case reflect.Complex64:
|
||||||
|
printComplex(d.w, v.Complex(), 32)
|
||||||
|
|
||||||
|
case reflect.Complex128:
|
||||||
|
printComplex(d.w, v.Complex(), 64)
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case reflect.Array:
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.dumpSlice(v)
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
d.w.Write([]byte(strconv.Quote(v.String())))
|
||||||
|
|
||||||
|
case reflect.Interface:
|
||||||
|
// The only time we should get here is for nil interfaces due to
|
||||||
|
// unpackValue calls.
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Do nothing. We should never get here since pointers have already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// nil maps should be indicated as different than empty maps
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
numEntries := v.Len()
|
||||||
|
keys := v.MapKeys()
|
||||||
|
if d.cs.SortKeys {
|
||||||
|
sortValues(keys, d.cs)
|
||||||
|
}
|
||||||
|
for i, key := range keys {
|
||||||
|
d.dump(d.unpackValue(key))
|
||||||
|
d.w.Write(colonSpaceBytes)
|
||||||
|
d.ignoreNextIndent = true
|
||||||
|
d.dump(d.unpackValue(v.MapIndex(key)))
|
||||||
|
if i < (numEntries - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
vt := v.Type()
|
||||||
|
numFields := v.NumField()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
d.indent()
|
||||||
|
vtf := vt.Field(i)
|
||||||
|
d.w.Write([]byte(vtf.Name))
|
||||||
|
d.w.Write(colonSpaceBytes)
|
||||||
|
d.ignoreNextIndent = true
|
||||||
|
d.dump(d.unpackValue(v.Field(i)))
|
||||||
|
if i < (numFields - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Uintptr:
|
||||||
|
printHexPtr(d.w, uintptr(v.Uint()))
|
||||||
|
|
||||||
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
printHexPtr(d.w, v.Pointer())
|
||||||
|
|
||||||
|
// There were not any other types at the time this code was written, but
|
||||||
|
// fall back to letting the default fmt package handle it in case any new
|
||||||
|
// types are added.
|
||||||
|
default:
|
||||||
|
if v.CanInterface() {
|
||||||
|
fmt.Fprintf(d.w, "%v", v.Interface())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(d.w, "%v", v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fdump is a helper function to consolidate the logic from the various public
|
||||||
|
// methods which take varying writers and config states.
|
||||||
|
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
||||||
|
for _, arg := range a {
|
||||||
|
if arg == nil {
|
||||||
|
w.Write(interfaceBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
w.Write(nilAngleBytes)
|
||||||
|
w.Write(newlineBytes)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d := dumpState{w: w, cs: cs}
|
||||||
|
d.pointers = make(map[uintptr]int)
|
||||||
|
d.dump(reflect.ValueOf(arg))
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||||
|
// exactly the same as Dump.
|
||||||
|
func Fdump(w io.Writer, a ...interface{}) {
|
||||||
|
fdump(&Config, w, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||||
|
// as Dump.
|
||||||
|
func Sdump(a ...interface{}) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fdump(&Config, &buf, a...)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dump displays the passed parameters to standard out with newlines, customizable
|
||||||
|
indentation, and additional debug information such as complete types and all
|
||||||
|
pointer addresses used to indirect to the final value. It provides the
|
||||||
|
following features over the built-in printing facilities provided by the fmt
|
||||||
|
package:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output
|
||||||
|
|
||||||
|
The configuration options are controlled by an exported package global,
|
||||||
|
spew.Config. See ConfigState for options documentation.
|
||||||
|
|
||||||
|
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||||
|
get the formatted result as a string.
|
||||||
|
*/
|
||||||
|
func Dump(a ...interface{}) {
|
||||||
|
fdump(&Config, os.Stdout, a...)
|
||||||
|
}
|
||||||
419
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
Normal file
419
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// supportedFlags is a list of all the character flags supported by fmt package.
|
||||||
|
const supportedFlags = "0-+# "
|
||||||
|
|
||||||
|
// formatState implements the fmt.Formatter interface and contains information
|
||||||
|
// about the state of a formatting operation. The NewFormatter function can
|
||||||
|
// be used to get a new Formatter which can be used directly as arguments
|
||||||
|
// in standard fmt package printing calls.
|
||||||
|
type formatState struct {
|
||||||
|
value interface{}
|
||||||
|
fs fmt.State
|
||||||
|
depth int
|
||||||
|
pointers map[uintptr]int
|
||||||
|
ignoreNextType bool
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildDefaultFormat recreates the original format string without precision
|
||||||
|
// and width information to pass in to fmt.Sprintf in the case of an
|
||||||
|
// unrecognized type. Unless new types are added to the language, this
|
||||||
|
// function won't ever be called.
|
||||||
|
func (f *formatState) buildDefaultFormat() (format string) {
|
||||||
|
buf := bytes.NewBuffer(percentBytes)
|
||||||
|
|
||||||
|
for _, flag := range supportedFlags {
|
||||||
|
if f.fs.Flag(int(flag)) {
|
||||||
|
buf.WriteRune(flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteRune('v')
|
||||||
|
|
||||||
|
format = buf.String()
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// constructOrigFormat recreates the original format string including precision
|
||||||
|
// and width information to pass along to the standard fmt package. This allows
|
||||||
|
// automatic deferral of all format strings this package doesn't support.
|
||||||
|
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
||||||
|
buf := bytes.NewBuffer(percentBytes)
|
||||||
|
|
||||||
|
for _, flag := range supportedFlags {
|
||||||
|
if f.fs.Flag(int(flag)) {
|
||||||
|
buf.WriteRune(flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if width, ok := f.fs.Width(); ok {
|
||||||
|
buf.WriteString(strconv.Itoa(width))
|
||||||
|
}
|
||||||
|
|
||||||
|
if precision, ok := f.fs.Precision(); ok {
|
||||||
|
buf.Write(precisionBytes)
|
||||||
|
buf.WriteString(strconv.Itoa(precision))
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteRune(verb)
|
||||||
|
|
||||||
|
format = buf.String()
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackValue returns values inside of non-nil interfaces when possible and
|
||||||
|
// ensures that types for values which have been unpacked from an interface
|
||||||
|
// are displayed when the show types flag is also set.
|
||||||
|
// This is useful for data types like structs, arrays, slices, and maps which
|
||||||
|
// can contain varying types packed inside an interface.
|
||||||
|
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
||||||
|
if v.Kind() == reflect.Interface {
|
||||||
|
f.ignoreNextType = false
|
||||||
|
if !v.IsNil() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
||||||
|
func (f *formatState) formatPtr(v reflect.Value) {
|
||||||
|
// Display nil if top level pointer is nil.
|
||||||
|
showTypes := f.fs.Flag('#')
|
||||||
|
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove pointers at or below the current depth from map used to detect
|
||||||
|
// circular refs.
|
||||||
|
for k, depth := range f.pointers {
|
||||||
|
if depth >= f.depth {
|
||||||
|
delete(f.pointers, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep list of all dereferenced pointers to possibly show later.
|
||||||
|
pointerChain := make([]uintptr, 0)
|
||||||
|
|
||||||
|
// Figure out how many levels of indirection there are by derferencing
|
||||||
|
// pointers and unpacking interfaces down the chain while detecting circular
|
||||||
|
// references.
|
||||||
|
nilFound := false
|
||||||
|
cycleFound := false
|
||||||
|
indirects := 0
|
||||||
|
ve := v
|
||||||
|
for ve.Kind() == reflect.Ptr {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indirects++
|
||||||
|
addr := ve.Pointer()
|
||||||
|
pointerChain = append(pointerChain, addr)
|
||||||
|
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
||||||
|
cycleFound = true
|
||||||
|
indirects--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
f.pointers[addr] = f.depth
|
||||||
|
|
||||||
|
ve = ve.Elem()
|
||||||
|
if ve.Kind() == reflect.Interface {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ve = ve.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display type or indirection level depending on flags.
|
||||||
|
if showTypes && !f.ignoreNextType {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||||
|
f.fs.Write([]byte(ve.Type().String()))
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
} else {
|
||||||
|
if nilFound || cycleFound {
|
||||||
|
indirects += strings.Count(ve.Type().String(), "*")
|
||||||
|
}
|
||||||
|
f.fs.Write(openAngleBytes)
|
||||||
|
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
||||||
|
f.fs.Write(closeAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display pointer information depending on flags.
|
||||||
|
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
for i, addr := range pointerChain {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(pointerChainBytes)
|
||||||
|
}
|
||||||
|
printHexPtr(f.fs, addr)
|
||||||
|
}
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display dereferenced value.
|
||||||
|
switch {
|
||||||
|
case nilFound:
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
|
||||||
|
case cycleFound:
|
||||||
|
f.fs.Write(circularShortBytes)
|
||||||
|
|
||||||
|
default:
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(ve)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// format is the main workhorse for providing the Formatter interface. It
|
||||||
|
// uses the passed reflect value to figure out what kind of object we are
|
||||||
|
// dealing with and formats it appropriately. It is a recursive function,
|
||||||
|
// however circular data structures are detected and handled properly.
|
||||||
|
func (f *formatState) format(v reflect.Value) {
|
||||||
|
// Handle invalid reflect values immediately.
|
||||||
|
kind := v.Kind()
|
||||||
|
if kind == reflect.Invalid {
|
||||||
|
f.fs.Write(invalidAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle pointers specially.
|
||||||
|
if kind == reflect.Ptr {
|
||||||
|
f.formatPtr(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print type information unless already handled elsewhere.
|
||||||
|
if !f.ignoreNextType && f.fs.Flag('#') {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
f.fs.Write([]byte(v.Type().String()))
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = false
|
||||||
|
|
||||||
|
// Call Stringer/error interfaces if they exist and the handle methods
|
||||||
|
// flag is enabled.
|
||||||
|
if !f.cs.DisableMethods {
|
||||||
|
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||||
|
if handled := handleMethods(f.cs, f.fs, v); handled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Invalid:
|
||||||
|
// Do nothing. We should never get here since invalid has already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
printBool(f.fs, v.Bool())
|
||||||
|
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
printInt(f.fs, v.Int(), 10)
|
||||||
|
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
printUint(f.fs, v.Uint(), 10)
|
||||||
|
|
||||||
|
case reflect.Float32:
|
||||||
|
printFloat(f.fs, v.Float(), 32)
|
||||||
|
|
||||||
|
case reflect.Float64:
|
||||||
|
printFloat(f.fs, v.Float(), 64)
|
||||||
|
|
||||||
|
case reflect.Complex64:
|
||||||
|
printComplex(f.fs, v.Complex(), 32)
|
||||||
|
|
||||||
|
case reflect.Complex128:
|
||||||
|
printComplex(f.fs, v.Complex(), 64)
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case reflect.Array:
|
||||||
|
f.fs.Write(openBracketBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
numEntries := v.Len()
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(v.Index(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeBracketBytes)
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
f.fs.Write([]byte(v.String()))
|
||||||
|
|
||||||
|
case reflect.Interface:
|
||||||
|
// The only time we should get here is for nil interfaces due to
|
||||||
|
// unpackValue calls.
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Do nothing. We should never get here since pointers have already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// nil maps should be indicated as different than empty maps
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
f.fs.Write(openMapBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
keys := v.MapKeys()
|
||||||
|
if f.cs.SortKeys {
|
||||||
|
sortValues(keys, f.cs)
|
||||||
|
}
|
||||||
|
for i, key := range keys {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(key))
|
||||||
|
f.fs.Write(colonBytes)
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(v.MapIndex(key)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeMapBytes)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
numFields := v.NumField()
|
||||||
|
f.fs.Write(openBraceBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
vt := v.Type()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
vtf := vt.Field(i)
|
||||||
|
if f.fs.Flag('+') || f.fs.Flag('#') {
|
||||||
|
f.fs.Write([]byte(vtf.Name))
|
||||||
|
f.fs.Write(colonBytes)
|
||||||
|
}
|
||||||
|
f.format(f.unpackValue(v.Field(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Uintptr:
|
||||||
|
printHexPtr(f.fs, uintptr(v.Uint()))
|
||||||
|
|
||||||
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
printHexPtr(f.fs, v.Pointer())
|
||||||
|
|
||||||
|
// There were not any other types at the time this code was written, but
|
||||||
|
// fall back to letting the default fmt package handle it if any get added.
|
||||||
|
default:
|
||||||
|
format := f.buildDefaultFormat()
|
||||||
|
if v.CanInterface() {
|
||||||
|
fmt.Fprintf(f.fs, format, v.Interface())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(f.fs, format, v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
||||||
|
// details.
|
||||||
|
func (f *formatState) Format(fs fmt.State, verb rune) {
|
||||||
|
f.fs = fs
|
||||||
|
|
||||||
|
// Use standard formatting for verbs that are not v.
|
||||||
|
if verb != 'v' {
|
||||||
|
format := f.constructOrigFormat(verb)
|
||||||
|
fmt.Fprintf(fs, format, f.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.value == nil {
|
||||||
|
if fs.Flag('#') {
|
||||||
|
fs.Write(interfaceBytes)
|
||||||
|
}
|
||||||
|
fs.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.format(reflect.ValueOf(f.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFormatter is a helper function to consolidate the logic from the various
|
||||||
|
// public methods which take varying config states.
|
||||||
|
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
||||||
|
fs := &formatState{value: v, cs: cs}
|
||||||
|
fs.pointers = make(map[uintptr]int)
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||||
|
interface. As a result, it integrates cleanly with standard fmt package
|
||||||
|
printing functions. The formatter is useful for inline printing of smaller data
|
||||||
|
types similar to the standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Typically this function shouldn't be called directly. It is much easier to make
|
||||||
|
use of the custom formatter by calling one of the convenience functions such as
|
||||||
|
Printf, Println, or Fprintf.
|
||||||
|
*/
|
||||||
|
func NewFormatter(v interface{}) fmt.Formatter {
|
||||||
|
return newFormatter(&Config, v)
|
||||||
|
}
|
||||||
148
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
Normal file
148
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the formatted string as a value that satisfies error. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Errorf(format string, a ...interface{}) (err error) {
|
||||||
|
return fmt.Errorf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprint(w, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintf(w, format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintln(w, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Print(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Print(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Printf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Println(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Println(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprint(a ...interface{}) string {
|
||||||
|
return fmt.Sprint(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprintf(format string, a ...interface{}) string {
|
||||||
|
return fmt.Sprintf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||||
|
// were passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprintln(a ...interface{}) string {
|
||||||
|
return fmt.Sprintln(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||||
|
// length with each argument converted to a default spew Formatter interface.
|
||||||
|
func convertArgs(args []interface{}) (formatters []interface{}) {
|
||||||
|
formatters = make([]interface{}, len(args))
|
||||||
|
for index, arg := range args {
|
||||||
|
formatters[index] = NewFormatter(arg)
|
||||||
|
}
|
||||||
|
return formatters
|
||||||
|
}
|
||||||
24
vendor/github.com/dgryski/go-farm/.gitignore
generated
vendored
Normal file
24
vendor/github.com/dgryski/go-farm/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
|
||||||
|
target
|
||||||
39
vendor/github.com/dgryski/go-farm/.travis.yml
generated
vendored
Normal file
39
vendor/github.com/dgryski/go-farm/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
branches:
|
||||||
|
except:
|
||||||
|
- release
|
||||||
|
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
- travis
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.12.x
|
||||||
|
- 1.13.x
|
||||||
|
- tip
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- if [ -n "$GH_USER" ]; then git config --global github.user ${GH_USER}; fi;
|
||||||
|
- if [ -n "$GH_TOKEN" ]; then git config --global github.token ${GH_TOKEN}; fi;
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- make deps
|
||||||
|
|
||||||
|
script:
|
||||||
|
- make qa
|
||||||
|
|
||||||
|
after_failure:
|
||||||
|
- cat ./target/test/report.xml
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- if [ "$TRAVIS_GO_VERSION" = "1.9" ]; then $HOME/gopath/bin/goveralls -covermode=count -coverprofile=target/report/coverage.out -service=travis-ci; fi;
|
||||||
21
vendor/github.com/dgryski/go-farm/LICENSE
generated
vendored
Normal file
21
vendor/github.com/dgryski/go-farm/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
Copyright (c) 2014-2017 Damian Gryski
|
||||||
|
Copyright (c) 2016-2017 Nicola Asuni - Tecnick.com
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
187
vendor/github.com/dgryski/go-farm/Makefile
generated
vendored
Normal file
187
vendor/github.com/dgryski/go-farm/Makefile
generated
vendored
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
# MAKEFILE
|
||||||
|
#
|
||||||
|
# @author Nicola Asuni <info@tecnick.com>
|
||||||
|
# @link https://github.com/dgryski/go-farm
|
||||||
|
#
|
||||||
|
# This file is intended to be executed in a Linux-compatible system.
|
||||||
|
# It also assumes that the project has been cloned in the right path under GOPATH:
|
||||||
|
# $GOPATH/src/github.com/dgryski/go-farm
|
||||||
|
#
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# List special make targets that are not associated with files
|
||||||
|
.PHONY: help all test format fmtcheck vet lint coverage cyclo misspell errcheck staticcheck astscan qa deps clean nuke
|
||||||
|
|
||||||
|
# Use bash as shell (Note: Ubuntu now uses dash which doesn't support PIPESTATUS).
|
||||||
|
SHELL=/bin/bash
|
||||||
|
|
||||||
|
# CVS path (path to the parent dir containing the project)
|
||||||
|
CVSPATH=github.com/dgryski
|
||||||
|
|
||||||
|
# Project owner
|
||||||
|
OWNER=dgryski
|
||||||
|
|
||||||
|
# Project vendor
|
||||||
|
VENDOR=dgryski
|
||||||
|
|
||||||
|
# Project name
|
||||||
|
PROJECT=go-farm
|
||||||
|
|
||||||
|
# Project version
|
||||||
|
VERSION=$(shell cat VERSION)
|
||||||
|
|
||||||
|
# Name of RPM or DEB package
|
||||||
|
PKGNAME=${VENDOR}-${PROJECT}
|
||||||
|
|
||||||
|
# Current directory
|
||||||
|
CURRENTDIR=$(shell pwd)
|
||||||
|
|
||||||
|
# GO lang path
|
||||||
|
ifneq ($(GOPATH),)
|
||||||
|
ifeq ($(findstring $(GOPATH),$(CURRENTDIR)),)
|
||||||
|
# the defined GOPATH is not valid
|
||||||
|
GOPATH=
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
ifeq ($(GOPATH),)
|
||||||
|
# extract the GOPATH
|
||||||
|
GOPATH=$(firstword $(subst /src/, ,$(CURRENTDIR)))
|
||||||
|
endif
|
||||||
|
|
||||||
|
# --- MAKE TARGETS ---
|
||||||
|
|
||||||
|
# Display general help about this command
|
||||||
|
help:
|
||||||
|
@echo ""
|
||||||
|
@echo "$(PROJECT) Makefile."
|
||||||
|
@echo "GOPATH=$(GOPATH)"
|
||||||
|
@echo "The following commands are available:"
|
||||||
|
@echo ""
|
||||||
|
@echo " make qa : Run all the tests"
|
||||||
|
@echo " make test : Run the unit tests"
|
||||||
|
@echo ""
|
||||||
|
@echo " make format : Format the source code"
|
||||||
|
@echo " make fmtcheck : Check if the source code has been formatted"
|
||||||
|
@echo " make vet : Check for suspicious constructs"
|
||||||
|
@echo " make lint : Check for style errors"
|
||||||
|
@echo " make coverage : Generate the coverage report"
|
||||||
|
@echo " make cyclo : Generate the cyclomatic complexity report"
|
||||||
|
@echo " make misspell : Detect commonly misspelled words in source files"
|
||||||
|
@echo " make staticcheck : Run staticcheck
|
||||||
|
@echo " make errcheck : Check that error return values are used"
|
||||||
|
@echo " make astscan : GO AST scanner"
|
||||||
|
@echo ""
|
||||||
|
@echo " make docs : Generate source code documentation"
|
||||||
|
@echo ""
|
||||||
|
@echo " make deps : Get the dependencies"
|
||||||
|
@echo " make clean : Remove any build artifact"
|
||||||
|
@echo " make nuke : Deletes any intermediate file"
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
|
||||||
|
# Alias for help target
|
||||||
|
all: help
|
||||||
|
|
||||||
|
# Run the unit tests
|
||||||
|
test:
|
||||||
|
@mkdir -p target/test
|
||||||
|
@mkdir -p target/report
|
||||||
|
GOPATH=$(GOPATH) \
|
||||||
|
go test \
|
||||||
|
-covermode=atomic \
|
||||||
|
-bench=. \
|
||||||
|
-race \
|
||||||
|
-cpuprofile=target/report/cpu.out \
|
||||||
|
-memprofile=target/report/mem.out \
|
||||||
|
-mutexprofile=target/report/mutex.out \
|
||||||
|
-coverprofile=target/report/coverage.out \
|
||||||
|
-v ./... | \
|
||||||
|
tee >(PATH=$(GOPATH)/bin:$(PATH) go-junit-report > target/test/report.xml); \
|
||||||
|
test $${PIPESTATUS[0]} -eq 0
|
||||||
|
|
||||||
|
# Format the source code
|
||||||
|
format:
|
||||||
|
@find . -type f -name "*.go" -exec gofmt -s -w {} \;
|
||||||
|
|
||||||
|
# Check if the source code has been formatted
|
||||||
|
fmtcheck:
|
||||||
|
@mkdir -p target
|
||||||
|
@find . -type f -name "*.go" -exec gofmt -s -d {} \; | tee target/format.diff
|
||||||
|
@test ! -s target/format.diff || { echo "ERROR: the source code has not been formatted - please use 'make format' or 'gofmt'"; exit 1; }
|
||||||
|
|
||||||
|
# Check for syntax errors
|
||||||
|
vet:
|
||||||
|
GOPATH=$(GOPATH) go vet .
|
||||||
|
|
||||||
|
# Check for style errors
|
||||||
|
lint:
|
||||||
|
GOPATH=$(GOPATH) PATH=$(GOPATH)/bin:$(PATH) golint .
|
||||||
|
|
||||||
|
# Generate the coverage report
|
||||||
|
coverage:
|
||||||
|
@mkdir -p target/report
|
||||||
|
GOPATH=$(GOPATH) \
|
||||||
|
go tool cover -html=target/report/coverage.out -o target/report/coverage.html
|
||||||
|
|
||||||
|
# Report cyclomatic complexity
|
||||||
|
cyclo:
|
||||||
|
@mkdir -p target/report
|
||||||
|
GOPATH=$(GOPATH) gocyclo -avg ./ | tee target/report/cyclo.txt ; test $${PIPESTATUS[0]} -eq 0
|
||||||
|
|
||||||
|
# Detect commonly misspelled words in source files
|
||||||
|
misspell:
|
||||||
|
@mkdir -p target/report
|
||||||
|
GOPATH=$(GOPATH) misspell -error ./ | tee target/report/misspell.txt ; test $${PIPESTATUS[0]} -eq 0
|
||||||
|
|
||||||
|
# Check that error return values are used
|
||||||
|
errcheck:
|
||||||
|
@mkdir -p target/report
|
||||||
|
GOPATH=$(GOPATH) errcheck ./ | tee target/report/errcheck.txt
|
||||||
|
|
||||||
|
|
||||||
|
# staticcheck
|
||||||
|
staticcheck:
|
||||||
|
@mkdir -p target/report
|
||||||
|
GOPATH=$(GOPATH) staticcheck ./... | tee target/report/staticcheck.txt
|
||||||
|
|
||||||
|
|
||||||
|
# AST scanner
|
||||||
|
astscan:
|
||||||
|
@mkdir -p target/report
|
||||||
|
GOPATH=$(GOPATH) gas .//*.go | tee target/report/astscan.txt
|
||||||
|
|
||||||
|
# Generate source docs
|
||||||
|
docs:
|
||||||
|
@mkdir -p target/docs
|
||||||
|
nohup sh -c 'GOPATH=$(GOPATH) godoc -http=127.0.0.1:6060' > target/godoc_server.log 2>&1 &
|
||||||
|
wget --directory-prefix=target/docs/ --execute robots=off --retry-connrefused --recursive --no-parent --adjust-extension --page-requisites --convert-links http://127.0.0.1:6060/pkg/github.com/${VENDOR}/${PROJECT}/ ; kill -9 `lsof -ti :6060`
|
||||||
|
@echo '<html><head><meta http-equiv="refresh" content="0;./127.0.0.1:6060/pkg/'${CVSPATH}'/'${PROJECT}'/index.html"/></head><a href="./127.0.0.1:6060/pkg/'${CVSPATH}'/'${PROJECT}'/index.html">'${PKGNAME}' Documentation ...</a></html>' > target/docs/index.html
|
||||||
|
|
||||||
|
# Alias to run all quality-assurance checks
|
||||||
|
qa: fmtcheck test vet lint coverage cyclo misspell errcheck astscan
|
||||||
|
|
||||||
|
# --- INSTALL ---
|
||||||
|
|
||||||
|
# Get the dependencies
|
||||||
|
deps:
|
||||||
|
GOPATH=$(GOPATH) go get ./...
|
||||||
|
GOPATH=$(GOPATH) go get golang.org/x/lint/golint
|
||||||
|
GOPATH=$(GOPATH) go get github.com/jstemmer/go-junit-report
|
||||||
|
GOPATH=$(GOPATH) go get github.com/axw/gocov/gocov
|
||||||
|
GOPATH=$(GOPATH) go get github.com/fzipp/gocyclo
|
||||||
|
GOPATH=$(GOPATH) go get github.com/gordonklaus/ineffassign
|
||||||
|
GOPATH=$(GOPATH) go get github.com/client9/misspell/cmd/misspell
|
||||||
|
GOPATH=$(GOPATH) go get github.com/opennota/check/cmd/structcheck
|
||||||
|
GOPATH=$(GOPATH) go get github.com/opennota/check/cmd/varcheck
|
||||||
|
GOPATH=$(GOPATH) go get github.com/kisielk/errcheck
|
||||||
|
GOPATH=$(GOPATH) go get honnef.co/go/tools/cmd/staticcheck
|
||||||
|
GOPATH=$(GOPATH) go get github.com/GoASTScanner/gas
|
||||||
|
|
||||||
|
# Remove any build artifact
|
||||||
|
clean:
|
||||||
|
GOPATH=$(GOPATH) go clean ./...
|
||||||
|
|
||||||
|
# Deletes any intermediate file
|
||||||
|
nuke:
|
||||||
|
rm -rf ./target
|
||||||
|
GOPATH=$(GOPATH) go clean -i ./...
|
||||||
46
vendor/github.com/dgryski/go-farm/README.md
generated
vendored
Normal file
46
vendor/github.com/dgryski/go-farm/README.md
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# go-farm
|
||||||
|
|
||||||
|
*Google's FarmHash hash functions implemented in Go*
|
||||||
|
|
||||||
|
[](https://github.com/dgryski/go-farm/tree/master)
|
||||||
|
[](https://travis-ci.org/dgryski/go-farm?branch=master)
|
||||||
|
[](https://coveralls.io/github/dgryski/go-farm?branch=master)
|
||||||
|
[](https://goreportcard.com/report/github.com/dgryski/go-farm)
|
||||||
|
[](http://godoc.org/github.com/dgryski/go-farm)
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
FarmHash, a family of hash functions.
|
||||||
|
|
||||||
|
This is a (mechanical) translation of the non-SSE4/non-AESNI hash functions from Google's FarmHash (https://github.com/google/farmhash).
|
||||||
|
|
||||||
|
|
||||||
|
FarmHash provides hash functions for strings and other data.
|
||||||
|
The functions mix the input bits thoroughly but are not suitable for cryptography.
|
||||||
|
|
||||||
|
All members of the FarmHash family were designed with heavy reliance on previous work by Jyrki Alakuijala, Austin Appleby, Bob Jenkins, and others.
|
||||||
|
|
||||||
|
For more information please consult https://github.com/google/farmhash
|
||||||
|
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
This application is written in Go language, please refer to the guides in https://golang.org for getting started.
|
||||||
|
|
||||||
|
This project include a Makefile that allows you to test and build the project with simple commands.
|
||||||
|
To see all available options:
|
||||||
|
```bash
|
||||||
|
make help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running all tests
|
||||||
|
|
||||||
|
Before committing the code, please check if it passes all tests using
|
||||||
|
```bash
|
||||||
|
make qa
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
As this is a highly derivative work, I have placed it under the same license as the original implementation. See the
|
||||||
|
LICENSE file for details.
|
||||||
1
vendor/github.com/dgryski/go-farm/VERSION
generated
vendored
Normal file
1
vendor/github.com/dgryski/go-farm/VERSION
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
2.0.1
|
||||||
32
vendor/github.com/dgryski/go-farm/basics.go
generated
vendored
Normal file
32
vendor/github.com/dgryski/go-farm/basics.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package farm
|
||||||
|
|
||||||
|
import "math/bits"
|
||||||
|
|
||||||
|
// Some primes between 2^63 and 2^64 for various uses.
|
||||||
|
const k0 uint64 = 0xc3a5c85c97cb3127
|
||||||
|
const k1 uint64 = 0xb492b66fbe98f273
|
||||||
|
const k2 uint64 = 0x9ae16a3b2f90404f
|
||||||
|
|
||||||
|
// Magic numbers for 32-bit hashing. Copied from Murmur3.
|
||||||
|
const c1 uint32 = 0xcc9e2d51
|
||||||
|
const c2 uint32 = 0x1b873593
|
||||||
|
|
||||||
|
// A 32-bit to 32-bit integer hash copied from Murmur3.
|
||||||
|
func fmix(h uint32) uint32 {
|
||||||
|
h ^= h >> 16
|
||||||
|
h *= 0x85ebca6b
|
||||||
|
h ^= h >> 13
|
||||||
|
h *= 0xc2b2ae35
|
||||||
|
h ^= h >> 16
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func mur(a, h uint32) uint32 {
|
||||||
|
// Helper from Murmur3 for combining two 32-bit values.
|
||||||
|
a *= c1
|
||||||
|
a = bits.RotateLeft32(a, -17)
|
||||||
|
a *= c2
|
||||||
|
h ^= a
|
||||||
|
h = bits.RotateLeft32(h, -19)
|
||||||
|
return h*5 + 0xe6546b64
|
||||||
|
}
|
||||||
194
vendor/github.com/dgryski/go-farm/farmhashcc.go
generated
vendored
Normal file
194
vendor/github.com/dgryski/go-farm/farmhashcc.go
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
package farm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This file provides a 32-bit hash equivalent to CityHash32 (v1.1.1)
|
||||||
|
// and a 128-bit hash equivalent to CityHash128 (v1.1.1). It also provides
|
||||||
|
// a seeded 32-bit hash function similar to CityHash32.
|
||||||
|
|
||||||
|
func hash32Len13to24Seed(s []byte, seed uint32) uint32 {
|
||||||
|
slen := len(s)
|
||||||
|
a := binary.LittleEndian.Uint32(s[-4+(slen>>1) : -4+(slen>>1)+4])
|
||||||
|
b := binary.LittleEndian.Uint32(s[4 : 4+4])
|
||||||
|
c := binary.LittleEndian.Uint32(s[slen-8 : slen-8+4])
|
||||||
|
d := binary.LittleEndian.Uint32(s[(slen >> 1) : (slen>>1)+4])
|
||||||
|
e := binary.LittleEndian.Uint32(s[0 : 0+4])
|
||||||
|
f := binary.LittleEndian.Uint32(s[slen-4 : slen-4+4])
|
||||||
|
h := d*c1 + uint32(slen) + seed
|
||||||
|
a = bits.RotateLeft32(a, -12) + f
|
||||||
|
h = mur(c, h) + a
|
||||||
|
a = bits.RotateLeft32(a, -3) + c
|
||||||
|
h = mur(e, h) + a
|
||||||
|
a = bits.RotateLeft32(a+f, -12) + d
|
||||||
|
h = mur(b^seed, h) + a
|
||||||
|
return fmix(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hash32Len0to4(s []byte, seed uint32) uint32 {
|
||||||
|
slen := len(s)
|
||||||
|
b := seed
|
||||||
|
c := uint32(9)
|
||||||
|
for i := 0; i < slen; i++ {
|
||||||
|
v := int8(s[i])
|
||||||
|
b = (b * c1) + uint32(v)
|
||||||
|
c ^= b
|
||||||
|
}
|
||||||
|
return fmix(mur(b, mur(uint32(slen), c)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func hash128to64(x uint128) uint64 {
|
||||||
|
// Murmur-inspired hashing.
|
||||||
|
const mul uint64 = 0x9ddfea08eb382d69
|
||||||
|
a := (x.lo ^ x.hi) * mul
|
||||||
|
a ^= (a >> 47)
|
||||||
|
b := (x.hi ^ a) * mul
|
||||||
|
b ^= (b >> 47)
|
||||||
|
b *= mul
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
type uint128 struct {
|
||||||
|
lo uint64
|
||||||
|
hi uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// A subroutine for CityHash128(). Returns a decent 128-bit hash for strings
|
||||||
|
// of any length representable in signed long. Based on City and Murmur.
|
||||||
|
func cityMurmur(s []byte, seed uint128) uint128 {
|
||||||
|
slen := len(s)
|
||||||
|
a := seed.lo
|
||||||
|
b := seed.hi
|
||||||
|
var c uint64
|
||||||
|
var d uint64
|
||||||
|
l := slen - 16
|
||||||
|
if l <= 0 { // len <= 16
|
||||||
|
a = shiftMix(a*k1) * k1
|
||||||
|
c = b*k1 + hashLen0to16(s)
|
||||||
|
if slen >= 8 {
|
||||||
|
d = shiftMix(a + binary.LittleEndian.Uint64(s[0:0+8]))
|
||||||
|
} else {
|
||||||
|
d = shiftMix(a + c)
|
||||||
|
}
|
||||||
|
} else { // len > 16
|
||||||
|
c = hashLen16(binary.LittleEndian.Uint64(s[slen-8:slen-8+8])+k1, a)
|
||||||
|
d = hashLen16(b+uint64(slen), c+binary.LittleEndian.Uint64(s[slen-16:slen-16+8]))
|
||||||
|
a += d
|
||||||
|
for {
|
||||||
|
a ^= shiftMix(binary.LittleEndian.Uint64(s[0:0+8])*k1) * k1
|
||||||
|
a *= k1
|
||||||
|
b ^= a
|
||||||
|
c ^= shiftMix(binary.LittleEndian.Uint64(s[8:8+8])*k1) * k1
|
||||||
|
c *= k1
|
||||||
|
d ^= c
|
||||||
|
s = s[16:]
|
||||||
|
l -= 16
|
||||||
|
if l <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a = hashLen16(a, c)
|
||||||
|
b = hashLen16(d, b)
|
||||||
|
return uint128{a ^ b, hashLen16(b, a)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cityHash128WithSeed(s []byte, seed uint128) uint128 {
|
||||||
|
slen := len(s)
|
||||||
|
if slen < 128 {
|
||||||
|
return cityMurmur(s, seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
endIdx := ((slen - 1) / 128) * 128
|
||||||
|
lastBlockIdx := endIdx + ((slen - 1) & 127) - 127
|
||||||
|
last := s[lastBlockIdx:]
|
||||||
|
|
||||||
|
// We expect len >= 128 to be the common case. Keep 56 bytes of state:
|
||||||
|
// v, w, x, y, and z.
|
||||||
|
var v1, v2 uint64
|
||||||
|
var w1, w2 uint64
|
||||||
|
x := seed.lo
|
||||||
|
y := seed.hi
|
||||||
|
z := uint64(slen) * k1
|
||||||
|
v1 = bits.RotateLeft64(y^k1, -49)*k1 + binary.LittleEndian.Uint64(s[0:0+8])
|
||||||
|
v2 = bits.RotateLeft64(v1, -42)*k1 + binary.LittleEndian.Uint64(s[8:8+8])
|
||||||
|
w1 = bits.RotateLeft64(y+z, -35)*k1 + x
|
||||||
|
w2 = bits.RotateLeft64(x+binary.LittleEndian.Uint64(s[88:88+8]), -53) * k1
|
||||||
|
|
||||||
|
// This is the same inner loop as CityHash64(), manually unrolled.
|
||||||
|
for {
|
||||||
|
x = bits.RotateLeft64(x+y+v1+binary.LittleEndian.Uint64(s[8:8+8]), -37) * k1
|
||||||
|
y = bits.RotateLeft64(y+v2+binary.LittleEndian.Uint64(s[48:48+8]), -42) * k1
|
||||||
|
x ^= w2
|
||||||
|
y += v1 + binary.LittleEndian.Uint64(s[40:40+8])
|
||||||
|
z = bits.RotateLeft64(z+w1, -33) * k1
|
||||||
|
v1, v2 = weakHashLen32WithSeeds(s, v2*k1, x+w1)
|
||||||
|
w1, w2 = weakHashLen32WithSeeds(s[32:], z+w2, y+binary.LittleEndian.Uint64(s[16:16+8]))
|
||||||
|
z, x = x, z
|
||||||
|
s = s[64:]
|
||||||
|
x = bits.RotateLeft64(x+y+v1+binary.LittleEndian.Uint64(s[8:8+8]), -37) * k1
|
||||||
|
y = bits.RotateLeft64(y+v2+binary.LittleEndian.Uint64(s[48:48+8]), -42) * k1
|
||||||
|
x ^= w2
|
||||||
|
y += v1 + binary.LittleEndian.Uint64(s[40:40+8])
|
||||||
|
z = bits.RotateLeft64(z+w1, -33) * k1
|
||||||
|
v1, v2 = weakHashLen32WithSeeds(s, v2*k1, x+w1)
|
||||||
|
w1, w2 = weakHashLen32WithSeeds(s[32:], z+w2, y+binary.LittleEndian.Uint64(s[16:16+8]))
|
||||||
|
z, x = x, z
|
||||||
|
s = s[64:]
|
||||||
|
slen -= 128
|
||||||
|
if slen < 128 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x += bits.RotateLeft64(v1+z, -49) * k0
|
||||||
|
y = y*k0 + bits.RotateLeft64(w2, -37)
|
||||||
|
z = z*k0 + bits.RotateLeft64(w1, -27)
|
||||||
|
w1 *= 9
|
||||||
|
v1 *= k0
|
||||||
|
// If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s.
|
||||||
|
for tailDone := 0; tailDone < slen; {
|
||||||
|
tailDone += 32
|
||||||
|
y = bits.RotateLeft64(x+y, -42)*k0 + v2
|
||||||
|
w1 += binary.LittleEndian.Uint64(last[128-tailDone+16 : 128-tailDone+16+8])
|
||||||
|
x = x*k0 + w1
|
||||||
|
z += w2 + binary.LittleEndian.Uint64(last[128-tailDone:128-tailDone+8])
|
||||||
|
w2 += v1
|
||||||
|
v1, v2 = weakHashLen32WithSeeds(last[128-tailDone:], v1+z, v2)
|
||||||
|
v1 *= k0
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point our 56 bytes of state should contain more than
|
||||||
|
// enough information for a strong 128-bit hash. We use two
|
||||||
|
// different 56-byte-to-8-byte hashes to get a 16-byte final result.
|
||||||
|
x = hashLen16(x, v1)
|
||||||
|
y = hashLen16(y+z, w1)
|
||||||
|
return uint128{hashLen16(x+v2, w2) + y,
|
||||||
|
hashLen16(x+w2, y+v2)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cityHash128(s []byte) uint128 {
|
||||||
|
slen := len(s)
|
||||||
|
if slen >= 16 {
|
||||||
|
return cityHash128WithSeed(s[16:], uint128{binary.LittleEndian.Uint64(s[0 : 0+8]), binary.LittleEndian.Uint64(s[8:8+8]) + k0})
|
||||||
|
}
|
||||||
|
return cityHash128WithSeed(s, uint128{k0, k1})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fingerprint128 is a 128-bit fingerprint function for byte-slices
|
||||||
|
func Fingerprint128(s []byte) (lo, hi uint64) {
|
||||||
|
h := cityHash128(s)
|
||||||
|
return h.lo, h.hi
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash128 is a 128-bit hash function for byte-slices
|
||||||
|
func Hash128(s []byte) (lo, hi uint64) {
|
||||||
|
return Fingerprint128(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash128WithSeed is a 128-bit hash function for byte-slices and a 128-bit seed
|
||||||
|
func Hash128WithSeed(s []byte, seed0, seed1 uint64) (lo, hi uint64) {
|
||||||
|
h := cityHash128WithSeed(s, uint128{seed0, seed1})
|
||||||
|
return h.lo, h.hi
|
||||||
|
}
|
||||||
102
vendor/github.com/dgryski/go-farm/farmhashmk.go
generated
vendored
Normal file
102
vendor/github.com/dgryski/go-farm/farmhashmk.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package farm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
func hash32Len5to12(s []byte, seed uint32) uint32 {
|
||||||
|
slen := len(s)
|
||||||
|
a := uint32(len(s))
|
||||||
|
b := uint32(len(s) * 5)
|
||||||
|
c := uint32(9)
|
||||||
|
d := b + seed
|
||||||
|
a += binary.LittleEndian.Uint32(s[0 : 0+4])
|
||||||
|
b += binary.LittleEndian.Uint32(s[slen-4 : slen-4+4])
|
||||||
|
c += binary.LittleEndian.Uint32(s[((slen >> 1) & 4) : ((slen>>1)&4)+4])
|
||||||
|
return fmix(seed ^ mur(c, mur(b, mur(a, d))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash32 hashes a byte slice and returns a uint32 hash value
|
||||||
|
func Hash32(s []byte) uint32 {
|
||||||
|
|
||||||
|
slen := len(s)
|
||||||
|
|
||||||
|
if slen <= 24 {
|
||||||
|
if slen <= 12 {
|
||||||
|
if slen <= 4 {
|
||||||
|
return hash32Len0to4(s, 0)
|
||||||
|
}
|
||||||
|
return hash32Len5to12(s, 0)
|
||||||
|
}
|
||||||
|
return hash32Len13to24Seed(s, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// len > 24
|
||||||
|
h := uint32(slen)
|
||||||
|
g := c1 * uint32(slen)
|
||||||
|
f := g
|
||||||
|
a0 := bits.RotateLeft32(binary.LittleEndian.Uint32(s[slen-4:slen-4+4])*c1, -17) * c2
|
||||||
|
a1 := bits.RotateLeft32(binary.LittleEndian.Uint32(s[slen-8:slen-8+4])*c1, -17) * c2
|
||||||
|
a2 := bits.RotateLeft32(binary.LittleEndian.Uint32(s[slen-16:slen-16+4])*c1, -17) * c2
|
||||||
|
a3 := bits.RotateLeft32(binary.LittleEndian.Uint32(s[slen-12:slen-12+4])*c1, -17) * c2
|
||||||
|
a4 := bits.RotateLeft32(binary.LittleEndian.Uint32(s[slen-20:slen-20+4])*c1, -17) * c2
|
||||||
|
h ^= a0
|
||||||
|
h = bits.RotateLeft32(h, -19)
|
||||||
|
h = h*5 + 0xe6546b64
|
||||||
|
h ^= a2
|
||||||
|
h = bits.RotateLeft32(h, -19)
|
||||||
|
h = h*5 + 0xe6546b64
|
||||||
|
g ^= a1
|
||||||
|
g = bits.RotateLeft32(g, -19)
|
||||||
|
g = g*5 + 0xe6546b64
|
||||||
|
g ^= a3
|
||||||
|
g = bits.RotateLeft32(g, -19)
|
||||||
|
g = g*5 + 0xe6546b64
|
||||||
|
f += a4
|
||||||
|
f = bits.RotateLeft32(f, -19) + 113
|
||||||
|
for len(s) > 20 {
|
||||||
|
a := binary.LittleEndian.Uint32(s[0 : 0+4])
|
||||||
|
b := binary.LittleEndian.Uint32(s[4 : 4+4])
|
||||||
|
c := binary.LittleEndian.Uint32(s[8 : 8+4])
|
||||||
|
d := binary.LittleEndian.Uint32(s[12 : 12+4])
|
||||||
|
e := binary.LittleEndian.Uint32(s[16 : 16+4])
|
||||||
|
h += a
|
||||||
|
g += b
|
||||||
|
f += c
|
||||||
|
h = mur(d, h) + e
|
||||||
|
g = mur(c, g) + a
|
||||||
|
f = mur(b+e*c1, f) + d
|
||||||
|
f += g
|
||||||
|
g += f
|
||||||
|
s = s[20:]
|
||||||
|
}
|
||||||
|
g = bits.RotateLeft32(g, -11) * c1
|
||||||
|
g = bits.RotateLeft32(g, -17) * c1
|
||||||
|
f = bits.RotateLeft32(f, -11) * c1
|
||||||
|
f = bits.RotateLeft32(f, -17) * c1
|
||||||
|
h = bits.RotateLeft32(h+g, -19)
|
||||||
|
h = h*5 + 0xe6546b64
|
||||||
|
h = bits.RotateLeft32(h, -17) * c1
|
||||||
|
h = bits.RotateLeft32(h+f, -19)
|
||||||
|
h = h*5 + 0xe6546b64
|
||||||
|
h = bits.RotateLeft32(h, -17) * c1
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash32WithSeed hashes a byte slice and a uint32 seed and returns a uint32 hash value
|
||||||
|
func Hash32WithSeed(s []byte, seed uint32) uint32 {
|
||||||
|
slen := len(s)
|
||||||
|
|
||||||
|
if slen <= 24 {
|
||||||
|
if slen >= 13 {
|
||||||
|
return hash32Len13to24Seed(s, seed*c1)
|
||||||
|
}
|
||||||
|
if slen >= 5 {
|
||||||
|
return hash32Len5to12(s, seed)
|
||||||
|
}
|
||||||
|
return hash32Len0to4(s, seed)
|
||||||
|
}
|
||||||
|
h := hash32Len13to24Seed(s[:24], seed^uint32(slen))
|
||||||
|
return mur(Hash32(s[24:])+seed, h)
|
||||||
|
}
|
||||||
161
vendor/github.com/dgryski/go-farm/farmhashna.go
generated
vendored
Normal file
161
vendor/github.com/dgryski/go-farm/farmhashna.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
package farm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
func shiftMix(val uint64) uint64 {
|
||||||
|
return val ^ (val >> 47)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashLen16(u, v uint64) uint64 {
|
||||||
|
return hash128to64(uint128{u, v})
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashLen16Mul(u, v, mul uint64) uint64 {
|
||||||
|
// Murmur-inspired hashing.
|
||||||
|
a := (u ^ v) * mul
|
||||||
|
a ^= (a >> 47)
|
||||||
|
b := (v ^ a) * mul
|
||||||
|
b ^= (b >> 47)
|
||||||
|
b *= mul
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashLen0to16(s []byte) uint64 {
|
||||||
|
slen := uint64(len(s))
|
||||||
|
if slen >= 8 {
|
||||||
|
mul := k2 + slen*2
|
||||||
|
a := binary.LittleEndian.Uint64(s[0:0+8]) + k2
|
||||||
|
b := binary.LittleEndian.Uint64(s[int(slen-8) : int(slen-8)+8])
|
||||||
|
c := bits.RotateLeft64(b, -37)*mul + a
|
||||||
|
d := (bits.RotateLeft64(a, -25) + b) * mul
|
||||||
|
return hashLen16Mul(c, d, mul)
|
||||||
|
}
|
||||||
|
|
||||||
|
if slen >= 4 {
|
||||||
|
mul := k2 + slen*2
|
||||||
|
a := binary.LittleEndian.Uint32(s[0 : 0+4])
|
||||||
|
return hashLen16Mul(slen+(uint64(a)<<3), uint64(binary.LittleEndian.Uint32(s[int(slen-4):int(slen-4)+4])), mul)
|
||||||
|
}
|
||||||
|
if slen > 0 {
|
||||||
|
a := s[0]
|
||||||
|
b := s[slen>>1]
|
||||||
|
c := s[slen-1]
|
||||||
|
y := uint32(a) + (uint32(b) << 8)
|
||||||
|
z := uint32(slen) + (uint32(c) << 2)
|
||||||
|
return shiftMix(uint64(y)*k2^uint64(z)*k0) * k2
|
||||||
|
}
|
||||||
|
return k2
|
||||||
|
}
|
||||||
|
|
||||||
|
// This probably works well for 16-byte strings as well, but it may be overkill
|
||||||
|
// in that case.
|
||||||
|
func hashLen17to32(s []byte) uint64 {
|
||||||
|
slen := len(s)
|
||||||
|
mul := k2 + uint64(slen*2)
|
||||||
|
a := binary.LittleEndian.Uint64(s[0:0+8]) * k1
|
||||||
|
b := binary.LittleEndian.Uint64(s[8 : 8+8])
|
||||||
|
c := binary.LittleEndian.Uint64(s[slen-8:slen-8+8]) * mul
|
||||||
|
d := binary.LittleEndian.Uint64(s[slen-16:slen-16+8]) * k2
|
||||||
|
return hashLen16Mul(bits.RotateLeft64(a+b, -43)+bits.RotateLeft64(c, -30)+d, a+bits.RotateLeft64(b+k2, -18)+c, mul)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a 16-byte hash for 48 bytes. Quick and dirty.
|
||||||
|
// Callers do best to use "random-looking" values for a and b.
|
||||||
|
func weakHashLen32WithSeedsWords(w, x, y, z, a, b uint64) (uint64, uint64) {
|
||||||
|
a += w
|
||||||
|
b = bits.RotateLeft64(b+a+z, -21)
|
||||||
|
c := a
|
||||||
|
a += x
|
||||||
|
a += y
|
||||||
|
b += bits.RotateLeft64(a, -44)
|
||||||
|
return a + z, b + c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a 16-byte hash for s[0] ... s[31], a, and b. Quick and dirty.
|
||||||
|
func weakHashLen32WithSeeds(s []byte, a, b uint64) (uint64, uint64) {
|
||||||
|
return weakHashLen32WithSeedsWords(binary.LittleEndian.Uint64(s[0:0+8]),
|
||||||
|
binary.LittleEndian.Uint64(s[8:8+8]),
|
||||||
|
binary.LittleEndian.Uint64(s[16:16+8]),
|
||||||
|
binary.LittleEndian.Uint64(s[24:24+8]),
|
||||||
|
a,
|
||||||
|
b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an 8-byte hash for 33 to 64 bytes.
|
||||||
|
func hashLen33to64(s []byte) uint64 {
|
||||||
|
slen := len(s)
|
||||||
|
mul := k2 + uint64(slen)*2
|
||||||
|
a := binary.LittleEndian.Uint64(s[0:0+8]) * k2
|
||||||
|
b := binary.LittleEndian.Uint64(s[8 : 8+8])
|
||||||
|
c := binary.LittleEndian.Uint64(s[slen-8:slen-8+8]) * mul
|
||||||
|
d := binary.LittleEndian.Uint64(s[slen-16:slen-16+8]) * k2
|
||||||
|
y := bits.RotateLeft64(a+b, -43) + bits.RotateLeft64(c, -30) + d
|
||||||
|
z := hashLen16Mul(y, a+bits.RotateLeft64(b+k2, -18)+c, mul)
|
||||||
|
e := binary.LittleEndian.Uint64(s[16:16+8]) * mul
|
||||||
|
f := binary.LittleEndian.Uint64(s[24 : 24+8])
|
||||||
|
g := (y + binary.LittleEndian.Uint64(s[slen-32:slen-32+8])) * mul
|
||||||
|
h := (z + binary.LittleEndian.Uint64(s[slen-24:slen-24+8])) * mul
|
||||||
|
return hashLen16Mul(bits.RotateLeft64(e+f, -43)+bits.RotateLeft64(g, -30)+h, e+bits.RotateLeft64(f+a, -18)+g, mul)
|
||||||
|
}
|
||||||
|
|
||||||
|
func naHash64(s []byte) uint64 {
|
||||||
|
slen := len(s)
|
||||||
|
var seed uint64 = 81
|
||||||
|
if slen <= 32 {
|
||||||
|
if slen <= 16 {
|
||||||
|
return hashLen0to16(s)
|
||||||
|
}
|
||||||
|
return hashLen17to32(s)
|
||||||
|
}
|
||||||
|
if slen <= 64 {
|
||||||
|
return hashLen33to64(s)
|
||||||
|
}
|
||||||
|
// For strings over 64 bytes we loop.
|
||||||
|
// Internal state consists of 56 bytes: v, w, x, y, and z.
|
||||||
|
v := uint128{0, 0}
|
||||||
|
w := uint128{0, 0}
|
||||||
|
x := seed*k2 + binary.LittleEndian.Uint64(s[0:0+8])
|
||||||
|
y := seed*k1 + 113
|
||||||
|
z := shiftMix(y*k2+113) * k2
|
||||||
|
// Set end so that after the loop we have 1 to 64 bytes left to process.
|
||||||
|
endIdx := ((slen - 1) / 64) * 64
|
||||||
|
last64Idx := endIdx + ((slen - 1) & 63) - 63
|
||||||
|
last64 := s[last64Idx:]
|
||||||
|
for len(s) > 64 {
|
||||||
|
x = bits.RotateLeft64(x+y+v.lo+binary.LittleEndian.Uint64(s[8:8+8]), -37) * k1
|
||||||
|
y = bits.RotateLeft64(y+v.hi+binary.LittleEndian.Uint64(s[48:48+8]), -42) * k1
|
||||||
|
x ^= w.hi
|
||||||
|
y += v.lo + binary.LittleEndian.Uint64(s[40:40+8])
|
||||||
|
z = bits.RotateLeft64(z+w.lo, -33) * k1
|
||||||
|
v.lo, v.hi = weakHashLen32WithSeeds(s, v.hi*k1, x+w.lo)
|
||||||
|
w.lo, w.hi = weakHashLen32WithSeeds(s[32:], z+w.hi, y+binary.LittleEndian.Uint64(s[16:16+8]))
|
||||||
|
x, z = z, x
|
||||||
|
s = s[64:]
|
||||||
|
}
|
||||||
|
mul := k1 + ((z & 0xff) << 1)
|
||||||
|
// Make s point to the last 64 bytes of input.
|
||||||
|
s = last64
|
||||||
|
w.lo += (uint64(slen-1) & 63)
|
||||||
|
v.lo += w.lo
|
||||||
|
w.lo += v.lo
|
||||||
|
x = bits.RotateLeft64(x+y+v.lo+binary.LittleEndian.Uint64(s[8:8+8]), -37) * mul
|
||||||
|
y = bits.RotateLeft64(y+v.hi+binary.LittleEndian.Uint64(s[48:48+8]), -42) * mul
|
||||||
|
x ^= w.hi * 9
|
||||||
|
y += v.lo*9 + binary.LittleEndian.Uint64(s[40:40+8])
|
||||||
|
z = bits.RotateLeft64(z+w.lo, -33) * mul
|
||||||
|
v.lo, v.hi = weakHashLen32WithSeeds(s, v.hi*mul, x+w.lo)
|
||||||
|
w.lo, w.hi = weakHashLen32WithSeeds(s[32:], z+w.hi, y+binary.LittleEndian.Uint64(s[16:16+8]))
|
||||||
|
x, z = z, x
|
||||||
|
return hashLen16Mul(hashLen16Mul(v.lo, w.lo, mul)+shiftMix(y)*k0+z, hashLen16Mul(v.hi, w.hi, mul)+x, mul)
|
||||||
|
}
|
||||||
|
|
||||||
|
func naHash64WithSeed(s []byte, seed uint64) uint64 {
|
||||||
|
return naHash64WithSeeds(s, k2, seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func naHash64WithSeeds(s []byte, seed0, seed1 uint64) uint64 {
|
||||||
|
return hashLen16(naHash64(s)-seed0, seed1)
|
||||||
|
}
|
||||||
122
vendor/github.com/dgryski/go-farm/farmhashuo.go
generated
vendored
Normal file
122
vendor/github.com/dgryski/go-farm/farmhashuo.go
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
package farm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
func uoH(x, y, mul uint64, r uint) uint64 {
|
||||||
|
a := (x ^ y) * mul
|
||||||
|
a ^= (a >> 47)
|
||||||
|
b := (y ^ a) * mul
|
||||||
|
return bits.RotateLeft64(b, -int(r)) * mul
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash64WithSeeds hashes a byte slice and two uint64 seeds and returns a uint64 hash value
|
||||||
|
func Hash64WithSeeds(s []byte, seed0, seed1 uint64) uint64 {
|
||||||
|
slen := len(s)
|
||||||
|
if slen <= 64 {
|
||||||
|
return naHash64WithSeeds(s, seed0, seed1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For strings over 64 bytes we loop.
|
||||||
|
// Internal state consists of 64 bytes: u, v, w, x, y, and z.
|
||||||
|
x := seed0
|
||||||
|
y := seed1*k2 + 113
|
||||||
|
z := shiftMix(y*k2) * k2
|
||||||
|
v := uint128{seed0, seed1}
|
||||||
|
var w uint128
|
||||||
|
u := x - z
|
||||||
|
x *= k2
|
||||||
|
mul := k2 + (u & 0x82)
|
||||||
|
|
||||||
|
// Set end so that after the loop we have 1 to 64 bytes left to process.
|
||||||
|
endIdx := ((slen - 1) / 64) * 64
|
||||||
|
last64Idx := endIdx + ((slen - 1) & 63) - 63
|
||||||
|
last64 := s[last64Idx:]
|
||||||
|
|
||||||
|
for len(s) > 64 {
|
||||||
|
a0 := binary.LittleEndian.Uint64(s[0 : 0+8])
|
||||||
|
a1 := binary.LittleEndian.Uint64(s[8 : 8+8])
|
||||||
|
a2 := binary.LittleEndian.Uint64(s[16 : 16+8])
|
||||||
|
a3 := binary.LittleEndian.Uint64(s[24 : 24+8])
|
||||||
|
a4 := binary.LittleEndian.Uint64(s[32 : 32+8])
|
||||||
|
a5 := binary.LittleEndian.Uint64(s[40 : 40+8])
|
||||||
|
a6 := binary.LittleEndian.Uint64(s[48 : 48+8])
|
||||||
|
a7 := binary.LittleEndian.Uint64(s[56 : 56+8])
|
||||||
|
x += a0 + a1
|
||||||
|
y += a2
|
||||||
|
z += a3
|
||||||
|
v.lo += a4
|
||||||
|
v.hi += a5 + a1
|
||||||
|
w.lo += a6
|
||||||
|
w.hi += a7
|
||||||
|
|
||||||
|
x = bits.RotateLeft64(x, -26)
|
||||||
|
x *= 9
|
||||||
|
y = bits.RotateLeft64(y, -29)
|
||||||
|
z *= mul
|
||||||
|
v.lo = bits.RotateLeft64(v.lo, -33)
|
||||||
|
v.hi = bits.RotateLeft64(v.hi, -30)
|
||||||
|
w.lo ^= x
|
||||||
|
w.lo *= 9
|
||||||
|
z = bits.RotateLeft64(z, -32)
|
||||||
|
z += w.hi
|
||||||
|
w.hi += z
|
||||||
|
z *= 9
|
||||||
|
u, y = y, u
|
||||||
|
|
||||||
|
z += a0 + a6
|
||||||
|
v.lo += a2
|
||||||
|
v.hi += a3
|
||||||
|
w.lo += a4
|
||||||
|
w.hi += a5 + a6
|
||||||
|
x += a1
|
||||||
|
y += a7
|
||||||
|
|
||||||
|
y += v.lo
|
||||||
|
v.lo += x - y
|
||||||
|
v.hi += w.lo
|
||||||
|
w.lo += v.hi
|
||||||
|
w.hi += x - y
|
||||||
|
x += w.hi
|
||||||
|
w.hi = bits.RotateLeft64(w.hi, -34)
|
||||||
|
u, z = z, u
|
||||||
|
s = s[64:]
|
||||||
|
}
|
||||||
|
// Make s point to the last 64 bytes of input.
|
||||||
|
s = last64
|
||||||
|
u *= 9
|
||||||
|
v.hi = bits.RotateLeft64(v.hi, -28)
|
||||||
|
v.lo = bits.RotateLeft64(v.lo, -20)
|
||||||
|
w.lo += (uint64(slen-1) & 63)
|
||||||
|
u += y
|
||||||
|
y += u
|
||||||
|
x = bits.RotateLeft64(y-x+v.lo+binary.LittleEndian.Uint64(s[8:8+8]), -37) * mul
|
||||||
|
y = bits.RotateLeft64(y^v.hi^binary.LittleEndian.Uint64(s[48:48+8]), -42) * mul
|
||||||
|
x ^= w.hi * 9
|
||||||
|
y += v.lo + binary.LittleEndian.Uint64(s[40:40+8])
|
||||||
|
z = bits.RotateLeft64(z+w.lo, -33) * mul
|
||||||
|
v.lo, v.hi = weakHashLen32WithSeeds(s, v.hi*mul, x+w.lo)
|
||||||
|
w.lo, w.hi = weakHashLen32WithSeeds(s[32:], z+w.hi, y+binary.LittleEndian.Uint64(s[16:16+8]))
|
||||||
|
return uoH(hashLen16Mul(v.lo+x, w.lo^y, mul)+z-u,
|
||||||
|
uoH(v.hi+y, w.hi+z, k2, 30)^x,
|
||||||
|
k2,
|
||||||
|
31)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash64WithSeed hashes a byte slice and a uint64 seed and returns a uint64 hash value
|
||||||
|
func Hash64WithSeed(s []byte, seed uint64) uint64 {
|
||||||
|
if len(s) <= 64 {
|
||||||
|
return naHash64WithSeed(s, seed)
|
||||||
|
}
|
||||||
|
return Hash64WithSeeds(s, 0, seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash64 hashes a byte slice and returns a uint64 hash value
|
||||||
|
func uoHash64(s []byte) uint64 {
|
||||||
|
if len(s) <= 64 {
|
||||||
|
return naHash64(s)
|
||||||
|
}
|
||||||
|
return Hash64WithSeeds(s, 81, 0)
|
||||||
|
}
|
||||||
104
vendor/github.com/dgryski/go-farm/farmhashxo.go
generated
vendored
Normal file
104
vendor/github.com/dgryski/go-farm/farmhashxo.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package farm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
func h32(s []byte, mul uint64) uint64 {
|
||||||
|
slen := len(s)
|
||||||
|
a := binary.LittleEndian.Uint64(s[0:0+8]) * k1
|
||||||
|
b := binary.LittleEndian.Uint64(s[8 : 8+8])
|
||||||
|
c := binary.LittleEndian.Uint64(s[slen-8:slen-8+8]) * mul
|
||||||
|
d := binary.LittleEndian.Uint64(s[slen-16:slen-16+8]) * k2
|
||||||
|
u := bits.RotateLeft64(a+b, -43) + bits.RotateLeft64(c, -30) + d
|
||||||
|
v := a + bits.RotateLeft64(b+k2, -18) + c
|
||||||
|
a = shiftMix((u ^ v) * mul)
|
||||||
|
b = shiftMix((v ^ a) * mul)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func h32Seeds(s []byte, mul, seed0, seed1 uint64) uint64 {
|
||||||
|
slen := len(s)
|
||||||
|
a := binary.LittleEndian.Uint64(s[0:0+8]) * k1
|
||||||
|
b := binary.LittleEndian.Uint64(s[8 : 8+8])
|
||||||
|
c := binary.LittleEndian.Uint64(s[slen-8:slen-8+8]) * mul
|
||||||
|
d := binary.LittleEndian.Uint64(s[slen-16:slen-16+8]) * k2
|
||||||
|
u := bits.RotateLeft64(a+b, -43) + bits.RotateLeft64(c, -30) + d + seed0
|
||||||
|
v := a + bits.RotateLeft64(b+k2, -18) + c + seed1
|
||||||
|
a = shiftMix((u ^ v) * mul)
|
||||||
|
b = shiftMix((v ^ a) * mul)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func xohashLen33to64(s []byte) uint64 {
|
||||||
|
slen := len(s)
|
||||||
|
mul0 := k2 - 30
|
||||||
|
mul1 := k2 - 30 + 2*uint64(slen)
|
||||||
|
|
||||||
|
var h0 uint64
|
||||||
|
{
|
||||||
|
s := s[0:32]
|
||||||
|
mul := mul0
|
||||||
|
slen := len(s)
|
||||||
|
a := binary.LittleEndian.Uint64(s[0:0+8]) * k1
|
||||||
|
b := binary.LittleEndian.Uint64(s[8 : 8+8])
|
||||||
|
c := binary.LittleEndian.Uint64(s[slen-8:slen-8+8]) * mul
|
||||||
|
d := binary.LittleEndian.Uint64(s[slen-16:slen-16+8]) * k2
|
||||||
|
u := bits.RotateLeft64(a+b, -43) + bits.RotateLeft64(c, -30) + d
|
||||||
|
v := a + bits.RotateLeft64(b+k2, -18) + c
|
||||||
|
a = shiftMix((u ^ v) * mul)
|
||||||
|
b = shiftMix((v ^ a) * mul)
|
||||||
|
h0 = b
|
||||||
|
}
|
||||||
|
|
||||||
|
var h1 uint64
|
||||||
|
{
|
||||||
|
s := s[slen-32:]
|
||||||
|
mul := mul1
|
||||||
|
slen := len(s)
|
||||||
|
a := binary.LittleEndian.Uint64(s[0:0+8]) * k1
|
||||||
|
b := binary.LittleEndian.Uint64(s[8 : 8+8])
|
||||||
|
c := binary.LittleEndian.Uint64(s[slen-8:slen-8+8]) * mul
|
||||||
|
d := binary.LittleEndian.Uint64(s[slen-16:slen-16+8]) * k2
|
||||||
|
u := bits.RotateLeft64(a+b, -43) + bits.RotateLeft64(c, -30) + d
|
||||||
|
v := a + bits.RotateLeft64(b+k2, -18) + c
|
||||||
|
a = shiftMix((u ^ v) * mul)
|
||||||
|
b = shiftMix((v ^ a) * mul)
|
||||||
|
h1 = b
|
||||||
|
}
|
||||||
|
|
||||||
|
r := ((h1 * mul1) + h0) * mul1
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func xohashLen65to96(s []byte) uint64 {
|
||||||
|
slen := len(s)
|
||||||
|
|
||||||
|
mul0 := k2 - 114
|
||||||
|
mul1 := k2 - 114 + 2*uint64(slen)
|
||||||
|
h0 := h32(s[:32], mul0)
|
||||||
|
h1 := h32(s[32:64], mul1)
|
||||||
|
h2 := h32Seeds(s[slen-32:], mul1, h0, h1)
|
||||||
|
return (h2*9 + (h0 >> 17) + (h1 >> 21)) * mul1
|
||||||
|
}
|
||||||
|
|
||||||
|
func Hash64(s []byte) uint64 {
|
||||||
|
slen := len(s)
|
||||||
|
|
||||||
|
if slen <= 32 {
|
||||||
|
if slen <= 16 {
|
||||||
|
return hashLen0to16(s)
|
||||||
|
} else {
|
||||||
|
return hashLen17to32(s)
|
||||||
|
}
|
||||||
|
} else if slen <= 64 {
|
||||||
|
return xohashLen33to64(s)
|
||||||
|
} else if slen <= 96 {
|
||||||
|
return xohashLen65to96(s)
|
||||||
|
} else if slen <= 256 {
|
||||||
|
return naHash64(s)
|
||||||
|
} else {
|
||||||
|
return uoHash64(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
951
vendor/github.com/dgryski/go-farm/fp_amd64.s
generated
vendored
Normal file
951
vendor/github.com/dgryski/go-farm/fp_amd64.s
generated
vendored
Normal file
@@ -0,0 +1,951 @@
|
|||||||
|
// Code generated by command: go run asm.go -out=fp_amd64.s -go111=false. DO NOT EDIT.
|
||||||
|
|
||||||
|
// +build amd64,!purego
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// func Fingerprint64(s []byte) uint64
|
||||||
|
TEXT ·Fingerprint64(SB), NOSPLIT, $0-32
|
||||||
|
MOVQ s_base+0(FP), CX
|
||||||
|
MOVQ s_len+8(FP), AX
|
||||||
|
CMPQ AX, $0x10
|
||||||
|
JG check32
|
||||||
|
CMPQ AX, $0x08
|
||||||
|
JL check4
|
||||||
|
MOVQ (CX), DX
|
||||||
|
MOVQ AX, BX
|
||||||
|
SUBQ $0x08, BX
|
||||||
|
ADDQ CX, BX
|
||||||
|
MOVQ (BX), BX
|
||||||
|
MOVQ $0x9ae16a3b2f90404f, BP
|
||||||
|
ADDQ BP, DX
|
||||||
|
SHLQ $0x01, AX
|
||||||
|
ADDQ BP, AX
|
||||||
|
MOVQ BX, BP
|
||||||
|
RORQ $0x25, BP
|
||||||
|
IMULQ AX, BP
|
||||||
|
ADDQ DX, BP
|
||||||
|
RORQ $0x19, DX
|
||||||
|
ADDQ BX, DX
|
||||||
|
IMULQ AX, DX
|
||||||
|
XORQ DX, BP
|
||||||
|
IMULQ AX, BP
|
||||||
|
MOVQ BP, BX
|
||||||
|
SHRQ $0x2f, BX
|
||||||
|
XORQ BP, BX
|
||||||
|
XORQ BX, DX
|
||||||
|
IMULQ AX, DX
|
||||||
|
MOVQ DX, BX
|
||||||
|
SHRQ $0x2f, BX
|
||||||
|
XORQ DX, BX
|
||||||
|
IMULQ AX, BX
|
||||||
|
MOVQ BX, ret+24(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
check4:
|
||||||
|
CMPQ AX, $0x04
|
||||||
|
JL check0
|
||||||
|
MOVQ $0x9ae16a3b2f90404f, DX
|
||||||
|
MOVQ AX, BX
|
||||||
|
SHLQ $0x01, BX
|
||||||
|
ADDQ DX, BX
|
||||||
|
MOVL (CX), SI
|
||||||
|
SHLQ $0x03, SI
|
||||||
|
ADDQ AX, SI
|
||||||
|
SUBQ $0x04, AX
|
||||||
|
ADDQ AX, CX
|
||||||
|
MOVL (CX), DI
|
||||||
|
XORQ DI, SI
|
||||||
|
IMULQ BX, SI
|
||||||
|
MOVQ SI, DX
|
||||||
|
SHRQ $0x2f, DX
|
||||||
|
XORQ SI, DX
|
||||||
|
XORQ DX, DI
|
||||||
|
IMULQ BX, DI
|
||||||
|
MOVQ DI, DX
|
||||||
|
SHRQ $0x2f, DX
|
||||||
|
XORQ DI, DX
|
||||||
|
IMULQ BX, DX
|
||||||
|
MOVQ DX, ret+24(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
check0:
|
||||||
|
TESTQ AX, AX
|
||||||
|
JZ empty
|
||||||
|
MOVBQZX (CX), DX
|
||||||
|
MOVQ AX, BX
|
||||||
|
SHRQ $0x01, BX
|
||||||
|
ADDQ CX, BX
|
||||||
|
MOVBQZX (BX), BP
|
||||||
|
MOVQ AX, BX
|
||||||
|
SUBQ $0x01, BX
|
||||||
|
ADDQ CX, BX
|
||||||
|
MOVBQZX (BX), BX
|
||||||
|
SHLQ $0x08, BP
|
||||||
|
ADDQ BP, DX
|
||||||
|
SHLQ $0x02, BX
|
||||||
|
ADDQ BX, AX
|
||||||
|
MOVQ $0xc3a5c85c97cb3127, BX
|
||||||
|
IMULQ BX, AX
|
||||||
|
MOVQ $0x9ae16a3b2f90404f, BX
|
||||||
|
IMULQ BX, DX
|
||||||
|
XORQ DX, AX
|
||||||
|
MOVQ AX, DX
|
||||||
|
SHRQ $0x2f, DX
|
||||||
|
XORQ AX, DX
|
||||||
|
IMULQ BX, DX
|
||||||
|
MOVQ DX, ret+24(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
empty:
|
||||||
|
MOVQ $0x9ae16a3b2f90404f, DX
|
||||||
|
MOVQ DX, ret+24(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
check32:
|
||||||
|
CMPQ AX, $0x20
|
||||||
|
JG check64
|
||||||
|
MOVQ AX, DX
|
||||||
|
SHLQ $0x01, DX
|
||||||
|
MOVQ $0x9ae16a3b2f90404f, BX
|
||||||
|
ADDQ BX, DX
|
||||||
|
MOVQ (CX), BP
|
||||||
|
MOVQ $0xb492b66fbe98f273, SI
|
||||||
|
IMULQ SI, BP
|
||||||
|
MOVQ 8(CX), SI
|
||||||
|
MOVQ AX, DI
|
||||||
|
SUBQ $0x10, DI
|
||||||
|
ADDQ CX, DI
|
||||||
|
MOVQ 8(DI), R12
|
||||||
|
IMULQ DX, R12
|
||||||
|
MOVQ (DI), DI
|
||||||
|
IMULQ BX, DI
|
||||||
|
MOVQ BP, R13
|
||||||
|
ADDQ SI, R13
|
||||||
|
RORQ $0x2b, R13
|
||||||
|
ADDQ DI, R13
|
||||||
|
MOVQ R12, DI
|
||||||
|
RORQ $0x1e, DI
|
||||||
|
ADDQ DI, R13
|
||||||
|
ADDQ R12, BP
|
||||||
|
ADDQ BX, SI
|
||||||
|
RORQ $0x12, SI
|
||||||
|
ADDQ SI, BP
|
||||||
|
XORQ BP, R13
|
||||||
|
IMULQ DX, R13
|
||||||
|
MOVQ R13, BX
|
||||||
|
SHRQ $0x2f, BX
|
||||||
|
XORQ R13, BX
|
||||||
|
XORQ BX, BP
|
||||||
|
IMULQ DX, BP
|
||||||
|
MOVQ BP, BX
|
||||||
|
SHRQ $0x2f, BX
|
||||||
|
XORQ BP, BX
|
||||||
|
IMULQ DX, BX
|
||||||
|
MOVQ BX, ret+24(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
check64:
|
||||||
|
CMPQ AX, $0x40
|
||||||
|
JG long
|
||||||
|
MOVQ AX, DX
|
||||||
|
SHLQ $0x01, DX
|
||||||
|
MOVQ $0x9ae16a3b2f90404f, BX
|
||||||
|
ADDQ BX, DX
|
||||||
|
MOVQ (CX), BP
|
||||||
|
IMULQ BX, BP
|
||||||
|
MOVQ 8(CX), SI
|
||||||
|
MOVQ AX, DI
|
||||||
|
SUBQ $0x10, DI
|
||||||
|
ADDQ CX, DI
|
||||||
|
MOVQ 8(DI), R12
|
||||||
|
IMULQ DX, R12
|
||||||
|
MOVQ (DI), DI
|
||||||
|
IMULQ BX, DI
|
||||||
|
MOVQ BP, R13
|
||||||
|
ADDQ SI, R13
|
||||||
|
RORQ $0x2b, R13
|
||||||
|
ADDQ DI, R13
|
||||||
|
MOVQ R12, DI
|
||||||
|
RORQ $0x1e, DI
|
||||||
|
ADDQ DI, R13
|
||||||
|
ADDQ BP, R12
|
||||||
|
ADDQ BX, SI
|
||||||
|
RORQ $0x12, SI
|
||||||
|
ADDQ SI, R12
|
||||||
|
MOVQ R13, BX
|
||||||
|
XORQ R12, BX
|
||||||
|
IMULQ DX, BX
|
||||||
|
MOVQ BX, SI
|
||||||
|
SHRQ $0x2f, SI
|
||||||
|
XORQ BX, SI
|
||||||
|
XORQ SI, R12
|
||||||
|
IMULQ DX, R12
|
||||||
|
MOVQ R12, BX
|
||||||
|
SHRQ $0x2f, BX
|
||||||
|
XORQ R12, BX
|
||||||
|
IMULQ DX, BX
|
||||||
|
MOVQ 16(CX), SI
|
||||||
|
IMULQ DX, SI
|
||||||
|
MOVQ 24(CX), DI
|
||||||
|
MOVQ AX, R12
|
||||||
|
SUBQ $0x20, R12
|
||||||
|
ADDQ CX, R12
|
||||||
|
MOVQ (R12), R14
|
||||||
|
ADDQ R13, R14
|
||||||
|
IMULQ DX, R14
|
||||||
|
MOVQ 8(R12), R12
|
||||||
|
ADDQ BX, R12
|
||||||
|
IMULQ DX, R12
|
||||||
|
MOVQ SI, BX
|
||||||
|
ADDQ DI, BX
|
||||||
|
RORQ $0x2b, BX
|
||||||
|
ADDQ R12, BX
|
||||||
|
MOVQ R14, R12
|
||||||
|
RORQ $0x1e, R12
|
||||||
|
ADDQ R12, BX
|
||||||
|
ADDQ R14, SI
|
||||||
|
ADDQ BP, DI
|
||||||
|
RORQ $0x12, DI
|
||||||
|
ADDQ DI, SI
|
||||||
|
XORQ SI, BX
|
||||||
|
IMULQ DX, BX
|
||||||
|
MOVQ BX, BP
|
||||||
|
SHRQ $0x2f, BP
|
||||||
|
XORQ BX, BP
|
||||||
|
XORQ BP, SI
|
||||||
|
IMULQ DX, SI
|
||||||
|
MOVQ SI, BX
|
||||||
|
SHRQ $0x2f, BX
|
||||||
|
XORQ SI, BX
|
||||||
|
IMULQ DX, BX
|
||||||
|
MOVQ BX, ret+24(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
long:
|
||||||
|
XORQ R8, R8
|
||||||
|
XORQ R9, R9
|
||||||
|
XORQ R10, R10
|
||||||
|
XORQ R11, R11
|
||||||
|
MOVQ $0x01529cba0ca458ff, DX
|
||||||
|
ADDQ (CX), DX
|
||||||
|
MOVQ $0x226bb95b4e64b6d4, BX
|
||||||
|
MOVQ $0x134a747f856d0526, BP
|
||||||
|
MOVQ AX, SI
|
||||||
|
SUBQ $0x01, SI
|
||||||
|
MOVQ $0xffffffffffffffc0, DI
|
||||||
|
ANDQ DI, SI
|
||||||
|
MOVQ AX, DI
|
||||||
|
SUBQ $0x01, DI
|
||||||
|
ANDQ $0x3f, DI
|
||||||
|
SUBQ $0x3f, DI
|
||||||
|
ADDQ SI, DI
|
||||||
|
MOVQ DI, SI
|
||||||
|
ADDQ CX, SI
|
||||||
|
MOVQ AX, DI
|
||||||
|
|
||||||
|
loop:
|
||||||
|
MOVQ $0xb492b66fbe98f273, R12
|
||||||
|
ADDQ BX, DX
|
||||||
|
ADDQ R8, DX
|
||||||
|
ADDQ 8(CX), DX
|
||||||
|
RORQ $0x25, DX
|
||||||
|
IMULQ R12, DX
|
||||||
|
ADDQ R9, BX
|
||||||
|
ADDQ 48(CX), BX
|
||||||
|
RORQ $0x2a, BX
|
||||||
|
IMULQ R12, BX
|
||||||
|
XORQ R11, DX
|
||||||
|
ADDQ R8, BX
|
||||||
|
ADDQ 40(CX), BX
|
||||||
|
ADDQ R10, BP
|
||||||
|
RORQ $0x21, BP
|
||||||
|
IMULQ R12, BP
|
||||||
|
IMULQ R12, R9
|
||||||
|
MOVQ DX, R8
|
||||||
|
ADDQ R10, R8
|
||||||
|
ADDQ (CX), R9
|
||||||
|
ADDQ R9, R8
|
||||||
|
ADDQ 24(CX), R8
|
||||||
|
RORQ $0x15, R8
|
||||||
|
MOVQ R9, R10
|
||||||
|
ADDQ 8(CX), R9
|
||||||
|
ADDQ 16(CX), R9
|
||||||
|
MOVQ R9, R13
|
||||||
|
RORQ $0x2c, R13
|
||||||
|
ADDQ R13, R8
|
||||||
|
ADDQ 24(CX), R9
|
||||||
|
ADDQ R10, R8
|
||||||
|
XCHGQ R9, R8
|
||||||
|
ADDQ BP, R11
|
||||||
|
MOVQ BX, R10
|
||||||
|
ADDQ 16(CX), R10
|
||||||
|
ADDQ 32(CX), R11
|
||||||
|
ADDQ R11, R10
|
||||||
|
ADDQ 56(CX), R10
|
||||||
|
RORQ $0x15, R10
|
||||||
|
MOVQ R11, R13
|
||||||
|
ADDQ 40(CX), R11
|
||||||
|
ADDQ 48(CX), R11
|
||||||
|
MOVQ R11, R14
|
||||||
|
RORQ $0x2c, R14
|
||||||
|
ADDQ R14, R10
|
||||||
|
ADDQ 56(CX), R11
|
||||||
|
ADDQ R13, R10
|
||||||
|
XCHGQ R11, R10
|
||||||
|
XCHGQ BP, DX
|
||||||
|
ADDQ $0x40, CX
|
||||||
|
SUBQ $0x40, DI
|
||||||
|
CMPQ DI, $0x40
|
||||||
|
JG loop
|
||||||
|
MOVQ SI, CX
|
||||||
|
MOVQ BP, DI
|
||||||
|
ANDQ $0xff, DI
|
||||||
|
SHLQ $0x01, DI
|
||||||
|
ADDQ R12, DI
|
||||||
|
MOVQ SI, CX
|
||||||
|
SUBQ $0x01, AX
|
||||||
|
ANDQ $0x3f, AX
|
||||||
|
ADDQ AX, R10
|
||||||
|
ADDQ R10, R8
|
||||||
|
ADDQ R8, R10
|
||||||
|
ADDQ BX, DX
|
||||||
|
ADDQ R8, DX
|
||||||
|
ADDQ 8(CX), DX
|
||||||
|
RORQ $0x25, DX
|
||||||
|
IMULQ DI, DX
|
||||||
|
ADDQ R9, BX
|
||||||
|
ADDQ 48(CX), BX
|
||||||
|
RORQ $0x2a, BX
|
||||||
|
IMULQ DI, BX
|
||||||
|
MOVQ $0x00000009, AX
|
||||||
|
IMULQ R11, AX
|
||||||
|
XORQ AX, DX
|
||||||
|
MOVQ $0x00000009, AX
|
||||||
|
IMULQ R8, AX
|
||||||
|
ADDQ AX, BX
|
||||||
|
ADDQ 40(CX), BX
|
||||||
|
ADDQ R10, BP
|
||||||
|
RORQ $0x21, BP
|
||||||
|
IMULQ DI, BP
|
||||||
|
IMULQ DI, R9
|
||||||
|
MOVQ DX, R8
|
||||||
|
ADDQ R10, R8
|
||||||
|
ADDQ (CX), R9
|
||||||
|
ADDQ R9, R8
|
||||||
|
ADDQ 24(CX), R8
|
||||||
|
RORQ $0x15, R8
|
||||||
|
MOVQ R9, AX
|
||||||
|
ADDQ 8(CX), R9
|
||||||
|
ADDQ 16(CX), R9
|
||||||
|
MOVQ R9, SI
|
||||||
|
RORQ $0x2c, SI
|
||||||
|
ADDQ SI, R8
|
||||||
|
ADDQ 24(CX), R9
|
||||||
|
ADDQ AX, R8
|
||||||
|
XCHGQ R9, R8
|
||||||
|
ADDQ BP, R11
|
||||||
|
MOVQ BX, R10
|
||||||
|
ADDQ 16(CX), R10
|
||||||
|
ADDQ 32(CX), R11
|
||||||
|
ADDQ R11, R10
|
||||||
|
ADDQ 56(CX), R10
|
||||||
|
RORQ $0x15, R10
|
||||||
|
MOVQ R11, AX
|
||||||
|
ADDQ 40(CX), R11
|
||||||
|
ADDQ 48(CX), R11
|
||||||
|
MOVQ R11, SI
|
||||||
|
RORQ $0x2c, SI
|
||||||
|
ADDQ SI, R10
|
||||||
|
ADDQ 56(CX), R11
|
||||||
|
ADDQ AX, R10
|
||||||
|
XCHGQ R11, R10
|
||||||
|
XCHGQ BP, DX
|
||||||
|
XORQ R10, R8
|
||||||
|
IMULQ DI, R8
|
||||||
|
MOVQ R8, AX
|
||||||
|
SHRQ $0x2f, AX
|
||||||
|
XORQ R8, AX
|
||||||
|
XORQ AX, R10
|
||||||
|
IMULQ DI, R10
|
||||||
|
MOVQ R10, AX
|
||||||
|
SHRQ $0x2f, AX
|
||||||
|
XORQ R10, AX
|
||||||
|
IMULQ DI, AX
|
||||||
|
ADDQ BP, AX
|
||||||
|
MOVQ BX, CX
|
||||||
|
SHRQ $0x2f, CX
|
||||||
|
XORQ BX, CX
|
||||||
|
MOVQ $0xc3a5c85c97cb3127, BX
|
||||||
|
IMULQ BX, CX
|
||||||
|
ADDQ CX, AX
|
||||||
|
XORQ R11, R9
|
||||||
|
IMULQ DI, R9
|
||||||
|
MOVQ R9, CX
|
||||||
|
SHRQ $0x2f, CX
|
||||||
|
XORQ R9, CX
|
||||||
|
XORQ CX, R11
|
||||||
|
IMULQ DI, R11
|
||||||
|
MOVQ R11, CX
|
||||||
|
SHRQ $0x2f, CX
|
||||||
|
XORQ R11, CX
|
||||||
|
IMULQ DI, CX
|
||||||
|
ADDQ DX, CX
|
||||||
|
XORQ CX, AX
|
||||||
|
IMULQ DI, AX
|
||||||
|
MOVQ AX, DX
|
||||||
|
SHRQ $0x2f, DX
|
||||||
|
XORQ AX, DX
|
||||||
|
XORQ DX, CX
|
||||||
|
IMULQ DI, CX
|
||||||
|
MOVQ CX, AX
|
||||||
|
SHRQ $0x2f, AX
|
||||||
|
XORQ CX, AX
|
||||||
|
IMULQ DI, AX
|
||||||
|
MOVQ AX, ret+24(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// func Fingerprint32(s []byte) uint32
|
||||||
|
TEXT ·Fingerprint32(SB), NOSPLIT, $0-28
|
||||||
|
MOVQ s_base+0(FP), AX
|
||||||
|
MOVQ s_len+8(FP), CX
|
||||||
|
CMPQ CX, $0x18
|
||||||
|
JG long
|
||||||
|
CMPQ CX, $0x0c
|
||||||
|
JG hash_13_24
|
||||||
|
CMPQ CX, $0x04
|
||||||
|
JG hash_5_12
|
||||||
|
XORL DX, DX
|
||||||
|
MOVL $0x00000009, BX
|
||||||
|
TESTQ CX, CX
|
||||||
|
JZ done
|
||||||
|
MOVQ CX, BP
|
||||||
|
MOVL $0xcc9e2d51, DI
|
||||||
|
IMULL DI, DX
|
||||||
|
MOVBLSX (AX), SI
|
||||||
|
ADDL SI, DX
|
||||||
|
XORL DX, BX
|
||||||
|
SUBQ $0x01, BP
|
||||||
|
TESTQ BP, BP
|
||||||
|
JZ done
|
||||||
|
IMULL DI, DX
|
||||||
|
MOVBLSX 1(AX), SI
|
||||||
|
ADDL SI, DX
|
||||||
|
XORL DX, BX
|
||||||
|
SUBQ $0x01, BP
|
||||||
|
TESTQ BP, BP
|
||||||
|
JZ done
|
||||||
|
IMULL DI, DX
|
||||||
|
MOVBLSX 2(AX), SI
|
||||||
|
ADDL SI, DX
|
||||||
|
XORL DX, BX
|
||||||
|
SUBQ $0x01, BP
|
||||||
|
TESTQ BP, BP
|
||||||
|
JZ done
|
||||||
|
IMULL DI, DX
|
||||||
|
MOVBLSX 3(AX), SI
|
||||||
|
ADDL SI, DX
|
||||||
|
XORL DX, BX
|
||||||
|
SUBQ $0x01, BP
|
||||||
|
TESTQ BP, BP
|
||||||
|
JZ done
|
||||||
|
|
||||||
|
done:
|
||||||
|
MOVL CX, BP
|
||||||
|
MOVL $0xcc9e2d51, SI
|
||||||
|
IMULL SI, BP
|
||||||
|
RORL $0x11, BP
|
||||||
|
MOVL $0x1b873593, SI
|
||||||
|
IMULL SI, BP
|
||||||
|
XORL BP, BX
|
||||||
|
RORL $0x13, BX
|
||||||
|
LEAL (BX)(BX*4), BP
|
||||||
|
LEAL 3864292196(BP), BX
|
||||||
|
MOVL $0xcc9e2d51, BP
|
||||||
|
IMULL BP, DX
|
||||||
|
RORL $0x11, DX
|
||||||
|
MOVL $0x1b873593, BP
|
||||||
|
IMULL BP, DX
|
||||||
|
XORL DX, BX
|
||||||
|
RORL $0x13, BX
|
||||||
|
LEAL (BX)(BX*4), DX
|
||||||
|
LEAL 3864292196(DX), BX
|
||||||
|
MOVL BX, DX
|
||||||
|
SHRL $0x10, DX
|
||||||
|
XORL DX, BX
|
||||||
|
MOVL $0x85ebca6b, DX
|
||||||
|
IMULL DX, BX
|
||||||
|
MOVL BX, DX
|
||||||
|
SHRL $0x0d, DX
|
||||||
|
XORL DX, BX
|
||||||
|
MOVL $0xc2b2ae35, DX
|
||||||
|
IMULL DX, BX
|
||||||
|
MOVL BX, DX
|
||||||
|
SHRL $0x10, DX
|
||||||
|
XORL DX, BX
|
||||||
|
MOVL BX, ret+24(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
hash_5_12:
|
||||||
|
MOVL CX, DX
|
||||||
|
MOVL DX, BX
|
||||||
|
SHLL $0x02, BX
|
||||||
|
ADDL DX, BX
|
||||||
|
MOVL $0x00000009, BP
|
||||||
|
MOVL BX, SI
|
||||||
|
ADDL (AX), DX
|
||||||
|
MOVQ CX, DI
|
||||||
|
SUBQ $0x04, DI
|
||||||
|
ADDQ AX, DI
|
||||||
|
ADDL (DI), BX
|
||||||
|
MOVQ CX, DI
|
||||||
|
SHRQ $0x01, DI
|
||||||
|
ANDQ $0x04, DI
|
||||||
|
ADDQ AX, DI
|
||||||
|
ADDL (DI), BP
|
||||||
|
MOVL $0xcc9e2d51, DI
|
||||||
|
IMULL DI, DX
|
||||||
|
RORL $0x11, DX
|
||||||
|
MOVL $0x1b873593, DI
|
||||||
|
IMULL DI, DX
|
||||||
|
XORL DX, SI
|
||||||
|
RORL $0x13, SI
|
||||||
|
LEAL (SI)(SI*4), DX
|
||||||
|
LEAL 3864292196(DX), SI
|
||||||
|
MOVL $0xcc9e2d51, DX
|
||||||
|
IMULL DX, BX
|
||||||
|
RORL $0x11, BX
|
||||||
|
MOVL $0x1b873593, DX
|
||||||
|
IMULL DX, BX
|
||||||
|
XORL BX, SI
|
||||||
|
RORL $0x13, SI
|
||||||
|
LEAL (SI)(SI*4), BX
|
||||||
|
LEAL 3864292196(BX), SI
|
||||||
|
MOVL $0xcc9e2d51, DX
|
||||||
|
IMULL DX, BP
|
||||||
|
RORL $0x11, BP
|
||||||
|
MOVL $0x1b873593, DX
|
||||||
|
IMULL DX, BP
|
||||||
|
XORL BP, SI
|
||||||
|
RORL $0x13, SI
|
||||||
|
LEAL (SI)(SI*4), BP
|
||||||
|
LEAL 3864292196(BP), SI
|
||||||
|
MOVL SI, DX
|
||||||
|
SHRL $0x10, DX
|
||||||
|
XORL DX, SI
|
||||||
|
MOVL $0x85ebca6b, DX
|
||||||
|
IMULL DX, SI
|
||||||
|
MOVL SI, DX
|
||||||
|
SHRL $0x0d, DX
|
||||||
|
XORL DX, SI
|
||||||
|
MOVL $0xc2b2ae35, DX
|
||||||
|
IMULL DX, SI
|
||||||
|
MOVL SI, DX
|
||||||
|
SHRL $0x10, DX
|
||||||
|
XORL DX, SI
|
||||||
|
MOVL SI, ret+24(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
hash_13_24:
|
||||||
|
MOVQ CX, DX
|
||||||
|
SHRQ $0x01, DX
|
||||||
|
ADDQ AX, DX
|
||||||
|
MOVL -4(DX), BX
|
||||||
|
MOVL 4(AX), BP
|
||||||
|
MOVQ CX, SI
|
||||||
|
ADDQ AX, SI
|
||||||
|
MOVL -8(SI), DI
|
||||||
|
MOVL (DX), DX
|
||||||
|
MOVL (AX), R8
|
||||||
|
MOVL -4(SI), SI
|
||||||
|
MOVL $0xcc9e2d51, R9
|
||||||
|
IMULL DX, R9
|
||||||
|
ADDL CX, R9
|
||||||
|
RORL $0x0c, BX
|
||||||
|
ADDL SI, BX
|
||||||
|
MOVL DI, R10
|
||||||
|
MOVL $0xcc9e2d51, R11
|
||||||
|
IMULL R11, R10
|
||||||
|
RORL $0x11, R10
|
||||||
|
MOVL $0x1b873593, R11
|
||||||
|
IMULL R11, R10
|
||||||
|
XORL R10, R9
|
||||||
|
RORL $0x13, R9
|
||||||
|
LEAL (R9)(R9*4), R10
|
||||||
|
LEAL 3864292196(R10), R9
|
||||||
|
ADDL BX, R9
|
||||||
|
RORL $0x03, BX
|
||||||
|
ADDL DI, BX
|
||||||
|
MOVL $0xcc9e2d51, DI
|
||||||
|
IMULL DI, R8
|
||||||
|
RORL $0x11, R8
|
||||||
|
MOVL $0x1b873593, DI
|
||||||
|
IMULL DI, R8
|
||||||
|
XORL R8, R9
|
||||||
|
RORL $0x13, R9
|
||||||
|
LEAL (R9)(R9*4), R8
|
||||||
|
LEAL 3864292196(R8), R9
|
||||||
|
ADDL BX, R9
|
||||||
|
ADDL SI, BX
|
||||||
|
RORL $0x0c, BX
|
||||||
|
ADDL DX, BX
|
||||||
|
MOVL $0xcc9e2d51, DX
|
||||||
|
IMULL DX, BP
|
||||||
|
RORL $0x11, BP
|
||||||
|
MOVL $0x1b873593, DX
|
||||||
|
IMULL DX, BP
|
||||||
|
XORL BP, R9
|
||||||
|
RORL $0x13, R9
|
||||||
|
LEAL (R9)(R9*4), BP
|
||||||
|
LEAL 3864292196(BP), R9
|
||||||
|
ADDL BX, R9
|
||||||
|
MOVL R9, DX
|
||||||
|
SHRL $0x10, DX
|
||||||
|
XORL DX, R9
|
||||||
|
MOVL $0x85ebca6b, DX
|
||||||
|
IMULL DX, R9
|
||||||
|
MOVL R9, DX
|
||||||
|
SHRL $0x0d, DX
|
||||||
|
XORL DX, R9
|
||||||
|
MOVL $0xc2b2ae35, DX
|
||||||
|
IMULL DX, R9
|
||||||
|
MOVL R9, DX
|
||||||
|
SHRL $0x10, DX
|
||||||
|
XORL DX, R9
|
||||||
|
MOVL R9, ret+24(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
long:
|
||||||
|
MOVL CX, DX
|
||||||
|
MOVL $0xcc9e2d51, BX
|
||||||
|
IMULL DX, BX
|
||||||
|
MOVL BX, BP
|
||||||
|
MOVQ CX, SI
|
||||||
|
ADDQ AX, SI
|
||||||
|
MOVL $0xcc9e2d51, DI
|
||||||
|
MOVL $0x1b873593, R8
|
||||||
|
MOVL -4(SI), R9
|
||||||
|
IMULL DI, R9
|
||||||
|
RORL $0x11, R9
|
||||||
|
IMULL R8, R9
|
||||||
|
XORL R9, DX
|
||||||
|
RORL $0x13, DX
|
||||||
|
MOVL DX, R9
|
||||||
|
SHLL $0x02, R9
|
||||||
|
ADDL R9, DX
|
||||||
|
ADDL $0xe6546b64, DX
|
||||||
|
MOVL -8(SI), R9
|
||||||
|
IMULL DI, R9
|
||||||
|
RORL $0x11, R9
|
||||||
|
IMULL R8, R9
|
||||||
|
XORL R9, BX
|
||||||
|
RORL $0x13, BX
|
||||||
|
MOVL BX, R9
|
||||||
|
SHLL $0x02, R9
|
||||||
|
ADDL R9, BX
|
||||||
|
ADDL $0xe6546b64, BX
|
||||||
|
MOVL -16(SI), R9
|
||||||
|
IMULL DI, R9
|
||||||
|
RORL $0x11, R9
|
||||||
|
IMULL R8, R9
|
||||||
|
XORL R9, DX
|
||||||
|
RORL $0x13, DX
|
||||||
|
MOVL DX, R9
|
||||||
|
SHLL $0x02, R9
|
||||||
|
ADDL R9, DX
|
||||||
|
ADDL $0xe6546b64, DX
|
||||||
|
MOVL -12(SI), R9
|
||||||
|
IMULL DI, R9
|
||||||
|
RORL $0x11, R9
|
||||||
|
IMULL R8, R9
|
||||||
|
XORL R9, BX
|
||||||
|
RORL $0x13, BX
|
||||||
|
MOVL BX, R9
|
||||||
|
SHLL $0x02, R9
|
||||||
|
ADDL R9, BX
|
||||||
|
ADDL $0xe6546b64, BX
|
||||||
|
PREFETCHT0 (AX)
|
||||||
|
MOVL -20(SI), SI
|
||||||
|
IMULL DI, SI
|
||||||
|
RORL $0x11, SI
|
||||||
|
IMULL R8, SI
|
||||||
|
ADDL SI, BP
|
||||||
|
RORL $0x13, BP
|
||||||
|
ADDL $0x71, BP
|
||||||
|
|
||||||
|
loop80:
|
||||||
|
CMPQ CX, $0x64
|
||||||
|
JL loop20
|
||||||
|
PREFETCHT0 20(AX)
|
||||||
|
MOVL (AX), SI
|
||||||
|
ADDL SI, DX
|
||||||
|
MOVL 4(AX), DI
|
||||||
|
ADDL DI, BX
|
||||||
|
MOVL 8(AX), R8
|
||||||
|
ADDL R8, BP
|
||||||
|
MOVL 12(AX), R9
|
||||||
|
MOVL R9, R11
|
||||||
|
MOVL $0xcc9e2d51, R10
|
||||||
|
IMULL R10, R11
|
||||||
|
RORL $0x11, R11
|
||||||
|
MOVL $0x1b873593, R10
|
||||||
|
IMULL R10, R11
|
||||||
|
XORL R11, DX
|
||||||
|
RORL $0x13, DX
|
||||||
|
LEAL (DX)(DX*4), R11
|
||||||
|
LEAL 3864292196(R11), DX
|
||||||
|
MOVL 16(AX), R10
|
||||||
|
ADDL R10, DX
|
||||||
|
MOVL R8, R11
|
||||||
|
MOVL $0xcc9e2d51, R8
|
||||||
|
IMULL R8, R11
|
||||||
|
RORL $0x11, R11
|
||||||
|
MOVL $0x1b873593, R8
|
||||||
|
IMULL R8, R11
|
||||||
|
XORL R11, BX
|
||||||
|
RORL $0x13, BX
|
||||||
|
LEAL (BX)(BX*4), R11
|
||||||
|
LEAL 3864292196(R11), BX
|
||||||
|
ADDL SI, BX
|
||||||
|
MOVL $0xcc9e2d51, SI
|
||||||
|
IMULL SI, R10
|
||||||
|
MOVL R10, R11
|
||||||
|
ADDL DI, R11
|
||||||
|
MOVL $0xcc9e2d51, SI
|
||||||
|
IMULL SI, R11
|
||||||
|
RORL $0x11, R11
|
||||||
|
MOVL $0x1b873593, SI
|
||||||
|
IMULL SI, R11
|
||||||
|
XORL R11, BP
|
||||||
|
RORL $0x13, BP
|
||||||
|
LEAL (BP)(BP*4), R11
|
||||||
|
LEAL 3864292196(R11), BP
|
||||||
|
ADDL R9, BP
|
||||||
|
ADDL BX, BP
|
||||||
|
ADDL BP, BX
|
||||||
|
PREFETCHT0 40(AX)
|
||||||
|
MOVL 20(AX), SI
|
||||||
|
ADDL SI, DX
|
||||||
|
MOVL 24(AX), DI
|
||||||
|
ADDL DI, BX
|
||||||
|
MOVL 28(AX), R8
|
||||||
|
ADDL R8, BP
|
||||||
|
MOVL 32(AX), R9
|
||||||
|
MOVL R9, R11
|
||||||
|
MOVL $0xcc9e2d51, R10
|
||||||
|
IMULL R10, R11
|
||||||
|
RORL $0x11, R11
|
||||||
|
MOVL $0x1b873593, R10
|
||||||
|
IMULL R10, R11
|
||||||
|
XORL R11, DX
|
||||||
|
RORL $0x13, DX
|
||||||
|
LEAL (DX)(DX*4), R11
|
||||||
|
LEAL 3864292196(R11), DX
|
||||||
|
MOVL 36(AX), R10
|
||||||
|
ADDL R10, DX
|
||||||
|
MOVL R8, R11
|
||||||
|
MOVL $0xcc9e2d51, R8
|
||||||
|
IMULL R8, R11
|
||||||
|
RORL $0x11, R11
|
||||||
|
MOVL $0x1b873593, R8
|
||||||
|
IMULL R8, R11
|
||||||
|
XORL R11, BX
|
||||||
|
RORL $0x13, BX
|
||||||
|
LEAL (BX)(BX*4), R11
|
||||||
|
LEAL 3864292196(R11), BX
|
||||||
|
ADDL SI, BX
|
||||||
|
MOVL $0xcc9e2d51, SI
|
||||||
|
IMULL SI, R10
|
||||||
|
MOVL R10, R11
|
||||||
|
ADDL DI, R11
|
||||||
|
MOVL $0xcc9e2d51, SI
|
||||||
|
IMULL SI, R11
|
||||||
|
RORL $0x11, R11
|
||||||
|
MOVL $0x1b873593, SI
|
||||||
|
IMULL SI, R11
|
||||||
|
XORL R11, BP
|
||||||
|
RORL $0x13, BP
|
||||||
|
LEAL (BP)(BP*4), R11
|
||||||
|
LEAL 3864292196(R11), BP
|
||||||
|
ADDL R9, BP
|
||||||
|
ADDL BX, BP
|
||||||
|
ADDL BP, BX
|
||||||
|
PREFETCHT0 60(AX)
|
||||||
|
MOVL 40(AX), SI
|
||||||
|
ADDL SI, DX
|
||||||
|
MOVL 44(AX), DI
|
||||||
|
ADDL DI, BX
|
||||||
|
MOVL 48(AX), R8
|
||||||
|
ADDL R8, BP
|
||||||
|
MOVL 52(AX), R9
|
||||||
|
MOVL R9, R11
|
||||||
|
MOVL $0xcc9e2d51, R10
|
||||||
|
IMULL R10, R11
|
||||||
|
RORL $0x11, R11
|
||||||
|
MOVL $0x1b873593, R10
|
||||||
|
IMULL R10, R11
|
||||||
|
XORL R11, DX
|
||||||
|
RORL $0x13, DX
|
||||||
|
LEAL (DX)(DX*4), R11
|
||||||
|
LEAL 3864292196(R11), DX
|
||||||
|
MOVL 56(AX), R10
|
||||||
|
ADDL R10, DX
|
||||||
|
MOVL R8, R11
|
||||||
|
MOVL $0xcc9e2d51, R8
|
||||||
|
IMULL R8, R11
|
||||||
|
RORL $0x11, R11
|
||||||
|
MOVL $0x1b873593, R8
|
||||||
|
IMULL R8, R11
|
||||||
|
XORL R11, BX
|
||||||
|
RORL $0x13, BX
|
||||||
|
LEAL (BX)(BX*4), R11
|
||||||
|
LEAL 3864292196(R11), BX
|
||||||
|
ADDL SI, BX
|
||||||
|
MOVL $0xcc9e2d51, SI
|
||||||
|
IMULL SI, R10
|
||||||
|
MOVL R10, R11
|
||||||
|
ADDL DI, R11
|
||||||
|
MOVL $0xcc9e2d51, SI
|
||||||
|
IMULL SI, R11
|
||||||
|
RORL $0x11, R11
|
||||||
|
MOVL $0x1b873593, SI
|
||||||
|
IMULL SI, R11
|
||||||
|
XORL R11, BP
|
||||||
|
RORL $0x13, BP
|
||||||
|
LEAL (BP)(BP*4), R11
|
||||||
|
LEAL 3864292196(R11), BP
|
||||||
|
ADDL R9, BP
|
||||||
|
ADDL BX, BP
|
||||||
|
ADDL BP, BX
|
||||||
|
PREFETCHT0 80(AX)
|
||||||
|
MOVL 60(AX), SI
|
||||||
|
ADDL SI, DX
|
||||||
|
MOVL 64(AX), DI
|
||||||
|
ADDL DI, BX
|
||||||
|
MOVL 68(AX), R8
|
||||||
|
ADDL R8, BP
|
||||||
|
MOVL 72(AX), R9
|
||||||
|
MOVL R9, R11
|
||||||
|
MOVL $0xcc9e2d51, R10
|
||||||
|
IMULL R10, R11
|
||||||
|
RORL $0x11, R11
|
||||||
|
MOVL $0x1b873593, R10
|
||||||
|
IMULL R10, R11
|
||||||
|
XORL R11, DX
|
||||||
|
RORL $0x13, DX
|
||||||
|
LEAL (DX)(DX*4), R11
|
||||||
|
LEAL 3864292196(R11), DX
|
||||||
|
MOVL 76(AX), R10
|
||||||
|
ADDL R10, DX
|
||||||
|
MOVL R8, R11
|
||||||
|
MOVL $0xcc9e2d51, R8
|
||||||
|
IMULL R8, R11
|
||||||
|
RORL $0x11, R11
|
||||||
|
MOVL $0x1b873593, R8
|
||||||
|
IMULL R8, R11
|
||||||
|
XORL R11, BX
|
||||||
|
RORL $0x13, BX
|
||||||
|
LEAL (BX)(BX*4), R11
|
||||||
|
LEAL 3864292196(R11), BX
|
||||||
|
ADDL SI, BX
|
||||||
|
MOVL $0xcc9e2d51, SI
|
||||||
|
IMULL SI, R10
|
||||||
|
MOVL R10, R11
|
||||||
|
ADDL DI, R11
|
||||||
|
MOVL $0xcc9e2d51, SI
|
||||||
|
IMULL SI, R11
|
||||||
|
RORL $0x11, R11
|
||||||
|
MOVL $0x1b873593, SI
|
||||||
|
IMULL SI, R11
|
||||||
|
XORL R11, BP
|
||||||
|
RORL $0x13, BP
|
||||||
|
LEAL (BP)(BP*4), R11
|
||||||
|
LEAL 3864292196(R11), BP
|
||||||
|
ADDL R9, BP
|
||||||
|
ADDL BX, BP
|
||||||
|
ADDL BP, BX
|
||||||
|
ADDQ $0x50, AX
|
||||||
|
SUBQ $0x50, CX
|
||||||
|
JMP loop80
|
||||||
|
|
||||||
|
loop20:
|
||||||
|
CMPQ CX, $0x14
|
||||||
|
JLE after
|
||||||
|
MOVL (AX), SI
|
||||||
|
ADDL SI, DX
|
||||||
|
MOVL 4(AX), DI
|
||||||
|
ADDL DI, BX
|
||||||
|
MOVL 8(AX), R8
|
||||||
|
ADDL R8, BP
|
||||||
|
MOVL 12(AX), R9
|
||||||
|
MOVL R9, R11
|
||||||
|
MOVL $0xcc9e2d51, R10
|
||||||
|
IMULL R10, R11
|
||||||
|
RORL $0x11, R11
|
||||||
|
MOVL $0x1b873593, R10
|
||||||
|
IMULL R10, R11
|
||||||
|
XORL R11, DX
|
||||||
|
RORL $0x13, DX
|
||||||
|
LEAL (DX)(DX*4), R11
|
||||||
|
LEAL 3864292196(R11), DX
|
||||||
|
MOVL 16(AX), R10
|
||||||
|
ADDL R10, DX
|
||||||
|
MOVL R8, R11
|
||||||
|
MOVL $0xcc9e2d51, R8
|
||||||
|
IMULL R8, R11
|
||||||
|
RORL $0x11, R11
|
||||||
|
MOVL $0x1b873593, R8
|
||||||
|
IMULL R8, R11
|
||||||
|
XORL R11, BX
|
||||||
|
RORL $0x13, BX
|
||||||
|
LEAL (BX)(BX*4), R11
|
||||||
|
LEAL 3864292196(R11), BX
|
||||||
|
ADDL SI, BX
|
||||||
|
MOVL $0xcc9e2d51, SI
|
||||||
|
IMULL SI, R10
|
||||||
|
MOVL R10, R11
|
||||||
|
ADDL DI, R11
|
||||||
|
MOVL $0xcc9e2d51, SI
|
||||||
|
IMULL SI, R11
|
||||||
|
RORL $0x11, R11
|
||||||
|
MOVL $0x1b873593, SI
|
||||||
|
IMULL SI, R11
|
||||||
|
XORL R11, BP
|
||||||
|
RORL $0x13, BP
|
||||||
|
LEAL (BP)(BP*4), R11
|
||||||
|
LEAL 3864292196(R11), BP
|
||||||
|
ADDL R9, BP
|
||||||
|
ADDL BX, BP
|
||||||
|
ADDL BP, BX
|
||||||
|
ADDQ $0x14, AX
|
||||||
|
SUBQ $0x14, CX
|
||||||
|
JMP loop20
|
||||||
|
|
||||||
|
after:
|
||||||
|
MOVL $0xcc9e2d51, AX
|
||||||
|
RORL $0x0b, BX
|
||||||
|
IMULL AX, BX
|
||||||
|
RORL $0x11, BX
|
||||||
|
IMULL AX, BX
|
||||||
|
RORL $0x0b, BP
|
||||||
|
IMULL AX, BP
|
||||||
|
RORL $0x11, BP
|
||||||
|
IMULL AX, BP
|
||||||
|
ADDL BX, DX
|
||||||
|
RORL $0x13, DX
|
||||||
|
MOVL DX, CX
|
||||||
|
SHLL $0x02, CX
|
||||||
|
ADDL CX, DX
|
||||||
|
ADDL $0xe6546b64, DX
|
||||||
|
RORL $0x11, DX
|
||||||
|
IMULL AX, DX
|
||||||
|
ADDL BP, DX
|
||||||
|
RORL $0x13, DX
|
||||||
|
MOVL DX, CX
|
||||||
|
SHLL $0x02, CX
|
||||||
|
ADDL CX, DX
|
||||||
|
ADDL $0xe6546b64, DX
|
||||||
|
RORL $0x11, DX
|
||||||
|
IMULL AX, DX
|
||||||
|
MOVL DX, ret+24(FP)
|
||||||
|
RET
|
||||||
13
vendor/github.com/dgryski/go-farm/fp_generic.go
generated
vendored
Normal file
13
vendor/github.com/dgryski/go-farm/fp_generic.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// +build !amd64 purego
|
||||||
|
|
||||||
|
package farm
|
||||||
|
|
||||||
|
// Fingerprint64 is a 64-bit fingerprint function for byte-slices
|
||||||
|
func Fingerprint64(s []byte) uint64 {
|
||||||
|
return naHash64(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fingerprint32 is a 32-bit fingerprint function for byte-slices
|
||||||
|
func Fingerprint32(s []byte) uint32 {
|
||||||
|
return Hash32(s)
|
||||||
|
}
|
||||||
9
vendor/github.com/dgryski/go-farm/fp_stub.go
generated
vendored
Normal file
9
vendor/github.com/dgryski/go-farm/fp_stub.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// Code generated by command: go run asm.go -out=fp_amd64.s -stubs=fp_stub.go. DO NOT EDIT.
|
||||||
|
|
||||||
|
// +build amd64,!purego
|
||||||
|
|
||||||
|
package farm
|
||||||
|
|
||||||
|
func Fingerprint64(s []byte) uint64
|
||||||
|
|
||||||
|
func Fingerprint32(s []byte) uint32
|
||||||
22
vendor/github.com/eapache/channels/.gitignore
generated
vendored
Normal file
22
vendor/github.com/eapache/channels/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
11
vendor/github.com/eapache/channels/.travis.yml
generated
vendored
Normal file
11
vendor/github.com/eapache/channels/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
script: go test -v -race -timeout 10s ./...
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.1
|
||||||
|
- 1.2
|
||||||
|
- 1.3
|
||||||
|
- 1.4
|
||||||
|
- 1.5
|
||||||
17
vendor/github.com/eapache/channels/CHANGELOG.md
generated
vendored
Normal file
17
vendor/github.com/eapache/channels/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
#### Version 1.1.0 (2015-11-22)
|
||||||
|
|
||||||
|
Bug Fixes:
|
||||||
|
- The `Len()` and `Cap()` methods on several implementations were racy
|
||||||
|
([#18](https://github.com/eapache/channels/issues/18)).
|
||||||
|
|
||||||
|
Note: Fixing the above issue led to a fairly substantial performance hit
|
||||||
|
(anywhere from 10-25% in benchmarks depending on use case) and involved fairly
|
||||||
|
major refactoring, which is why this is being released as v1.1.0 instead
|
||||||
|
of v1.0.1.
|
||||||
|
|
||||||
|
#### Version 1.0.0 (2015-01-24)
|
||||||
|
|
||||||
|
Version 1.0.0 is the first tagged release. All core functionality was available
|
||||||
|
at this point.
|
||||||
20
vendor/github.com/eapache/channels/LICENSE
generated
vendored
Normal file
20
vendor/github.com/eapache/channels/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2013 Evan Huus
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
27
vendor/github.com/eapache/channels/README.md
generated
vendored
Normal file
27
vendor/github.com/eapache/channels/README.md
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
channels
|
||||||
|
========
|
||||||
|
|
||||||
|
[](https://travis-ci.org/eapache/channels)
|
||||||
|
[](https://godoc.org/github.com/eapache/channels)
|
||||||
|
[](https://eapache.github.io/conduct.html)
|
||||||
|
|
||||||
|
A collection of helper functions and special types for working with and
|
||||||
|
extending [Go](https://golang.org/)'s existing channels. Due to limitations
|
||||||
|
of Go's type system, importing this library directly is often not practical for
|
||||||
|
production code. It serves equally well, however, as a reference guide and
|
||||||
|
template for implementing many common idioms; if you use it in this way I would
|
||||||
|
appreciate the inclusion of some sort of credit in the resulting code.
|
||||||
|
|
||||||
|
See https://godoc.org/github.com/eapache/channels for full documentation or
|
||||||
|
https://gopkg.in/eapache/channels.v1 for a versioned import path.
|
||||||
|
|
||||||
|
Requires Go version 1.1 or later, as certain necessary elements of the `reflect`
|
||||||
|
package were not present in 1.0.
|
||||||
|
|
||||||
|
Most of the buffered channel types in this package are backed by a very fast
|
||||||
|
queue implementation that used to be built into this package but has now been
|
||||||
|
extracted into its own package at https://github.com/eapache/queue.
|
||||||
|
|
||||||
|
*Note:* Several types in this package provide so-called "infinite" buffers. Be
|
||||||
|
very careful using these, as no buffer is truly infinite. If such a buffer
|
||||||
|
grows too large your program will run out of memory and crash. Caveat emptor.
|
||||||
87
vendor/github.com/eapache/channels/batching_channel.go
generated
vendored
Normal file
87
vendor/github.com/eapache/channels/batching_channel.go
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package channels
|
||||||
|
|
||||||
|
// BatchingChannel implements the Channel interface, with the change that instead of producing individual elements
|
||||||
|
// on Out(), it batches together the entire internal buffer each time. Trying to construct an unbuffered batching channel
|
||||||
|
// will panic, that configuration is not supported (and provides no benefit over an unbuffered NativeChannel).
|
||||||
|
type BatchingChannel struct {
|
||||||
|
input, output chan interface{}
|
||||||
|
length chan int
|
||||||
|
buffer []interface{}
|
||||||
|
size BufferCap
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBatchingChannel(size BufferCap) *BatchingChannel {
|
||||||
|
if size == None {
|
||||||
|
panic("channels: BatchingChannel does not support unbuffered behaviour")
|
||||||
|
}
|
||||||
|
if size < 0 && size != Infinity {
|
||||||
|
panic("channels: invalid negative size in NewBatchingChannel")
|
||||||
|
}
|
||||||
|
ch := &BatchingChannel{
|
||||||
|
input: make(chan interface{}),
|
||||||
|
output: make(chan interface{}),
|
||||||
|
length: make(chan int),
|
||||||
|
size: size,
|
||||||
|
}
|
||||||
|
go ch.batchingBuffer()
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *BatchingChannel) In() chan<- interface{} {
|
||||||
|
return ch.input
|
||||||
|
}
|
||||||
|
|
||||||
|
// Out returns a <-chan interface{} in order that BatchingChannel conforms to the standard Channel interface provided
|
||||||
|
// by this package, however each output value is guaranteed to be of type []interface{} - a slice collecting the most
|
||||||
|
// recent batch of values sent on the In channel. The slice is guaranteed to not be empty or nil. In practice the net
|
||||||
|
// result is that you need an additional type assertion to access the underlying values.
|
||||||
|
func (ch *BatchingChannel) Out() <-chan interface{} {
|
||||||
|
return ch.output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *BatchingChannel) Len() int {
|
||||||
|
return <-ch.length
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *BatchingChannel) Cap() BufferCap {
|
||||||
|
return ch.size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *BatchingChannel) Close() {
|
||||||
|
close(ch.input)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *BatchingChannel) batchingBuffer() {
|
||||||
|
var input, output, nextInput chan interface{}
|
||||||
|
nextInput = ch.input
|
||||||
|
input = nextInput
|
||||||
|
|
||||||
|
for input != nil || output != nil {
|
||||||
|
select {
|
||||||
|
case elem, open := <-input:
|
||||||
|
if open {
|
||||||
|
ch.buffer = append(ch.buffer, elem)
|
||||||
|
} else {
|
||||||
|
input = nil
|
||||||
|
nextInput = nil
|
||||||
|
}
|
||||||
|
case output <- ch.buffer:
|
||||||
|
ch.buffer = nil
|
||||||
|
case ch.length <- len(ch.buffer):
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ch.buffer) == 0 {
|
||||||
|
input = nextInput
|
||||||
|
output = nil
|
||||||
|
} else if ch.size != Infinity && len(ch.buffer) >= int(ch.size) {
|
||||||
|
input = nil
|
||||||
|
output = ch.output
|
||||||
|
} else {
|
||||||
|
input = nextInput
|
||||||
|
output = ch.output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(ch.output)
|
||||||
|
close(ch.length)
|
||||||
|
}
|
||||||
54
vendor/github.com/eapache/channels/black_hole.go
generated
vendored
Normal file
54
vendor/github.com/eapache/channels/black_hole.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package channels
|
||||||
|
|
||||||
|
// BlackHole implements the InChannel interface and provides an analogue for the "Discard" variable in
|
||||||
|
// the ioutil package - it never blocks, and simply discards every value it reads. The number of items
|
||||||
|
// discarded in this way is counted and returned from Len.
|
||||||
|
type BlackHole struct {
|
||||||
|
input chan interface{}
|
||||||
|
length chan int
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBlackHole() *BlackHole {
|
||||||
|
ch := &BlackHole{
|
||||||
|
input: make(chan interface{}),
|
||||||
|
length: make(chan int),
|
||||||
|
}
|
||||||
|
go ch.discard()
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *BlackHole) In() chan<- interface{} {
|
||||||
|
return ch.input
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *BlackHole) Len() int {
|
||||||
|
val, open := <-ch.length
|
||||||
|
if open {
|
||||||
|
return val
|
||||||
|
} else {
|
||||||
|
return ch.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *BlackHole) Cap() BufferCap {
|
||||||
|
return Infinity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *BlackHole) Close() {
|
||||||
|
close(ch.input)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *BlackHole) discard() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case _, open := <-ch.input:
|
||||||
|
if !open {
|
||||||
|
close(ch.length)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch.count++
|
||||||
|
case ch.length <- ch.count:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
277
vendor/github.com/eapache/channels/channels.go
generated
vendored
Normal file
277
vendor/github.com/eapache/channels/channels.go
generated
vendored
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
/*
|
||||||
|
Package channels provides a collection of helper functions, interfaces and implementations for
|
||||||
|
working with and extending the capabilities of golang's existing channels. The main interface of
|
||||||
|
interest is Channel, though sub-interfaces are also provided for cases where the full Channel interface
|
||||||
|
cannot be met (for example, InChannel for write-only channels).
|
||||||
|
|
||||||
|
For integration with native typed golang channels, functions Wrap and Unwrap are provided which do the
|
||||||
|
appropriate type conversions. The NativeChannel, NativeInChannel and NativeOutChannel type definitions
|
||||||
|
are also provided for use with native channels which already carry values of type interface{}.
|
||||||
|
|
||||||
|
The heart of the package consists of several distinct implementations of the Channel interface, including
|
||||||
|
channels backed by special buffers (resizable, infinite, ring buffers, etc) and other useful types. A
|
||||||
|
"black hole" channel for discarding unwanted values (similar in purpose to ioutil.Discard or /dev/null)
|
||||||
|
rounds out the set.
|
||||||
|
|
||||||
|
Helper functions for operating on Channels include Pipe and Tee (which behave much like their Unix
|
||||||
|
namesakes), as well as Multiplex and Distribute. "Weak" versions of these functions also exist, which
|
||||||
|
do not close their output channel(s) on completion.
|
||||||
|
|
||||||
|
Due to limitations of Go's type system, importing this library directly is often not practical for
|
||||||
|
production code. It serves equally well, however, as a reference guide and template for implementing
|
||||||
|
many common idioms; if you use it in this way I would appreciate the inclusion of some sort of credit
|
||||||
|
in the resulting code.
|
||||||
|
|
||||||
|
Warning: several types in this package provide so-called "infinite" buffers. Be *very* careful using
|
||||||
|
these, as no buffer is truly infinite - if such a buffer grows too large your program will run out of
|
||||||
|
memory and crash. Caveat emptor.
|
||||||
|
*/
|
||||||
|
package channels
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
// BufferCap represents the capacity of the buffer backing a channel. Valid values consist of all
|
||||||
|
// positive integers, as well as the special values below.
|
||||||
|
type BufferCap int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// None is the capacity for channels that have no buffer at all.
|
||||||
|
None BufferCap = 0
|
||||||
|
// Infinity is the capacity for channels with no limit on their buffer size.
|
||||||
|
Infinity BufferCap = -1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Buffer is an interface for any channel that provides access to query the state of its buffer.
|
||||||
|
// Even unbuffered channels can implement this interface by simply returning 0 from Len() and None from Cap().
|
||||||
|
type Buffer interface {
|
||||||
|
Len() int // The number of elements currently buffered.
|
||||||
|
Cap() BufferCap // The maximum number of elements that can be buffered.
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleInChannel is an interface representing a writeable channel that does not necessarily
|
||||||
|
// implement the Buffer interface.
|
||||||
|
type SimpleInChannel interface {
|
||||||
|
In() chan<- interface{} // The writeable end of the channel.
|
||||||
|
Close() // Closes the channel. It is an error to write to In() after calling Close().
|
||||||
|
}
|
||||||
|
|
||||||
|
// InChannel is an interface representing a writeable channel with a buffer.
|
||||||
|
type InChannel interface {
|
||||||
|
SimpleInChannel
|
||||||
|
Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleOutChannel is an interface representing a readable channel that does not necessarily
|
||||||
|
// implement the Buffer interface.
|
||||||
|
type SimpleOutChannel interface {
|
||||||
|
Out() <-chan interface{} // The readable end of the channel.
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutChannel is an interface representing a readable channel implementing the Buffer interface.
|
||||||
|
type OutChannel interface {
|
||||||
|
SimpleOutChannel
|
||||||
|
Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleChannel is an interface representing a channel that is both readable and writeable,
|
||||||
|
// but does not necessarily implement the Buffer interface.
|
||||||
|
type SimpleChannel interface {
|
||||||
|
SimpleInChannel
|
||||||
|
SimpleOutChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channel is an interface representing a channel that is readable, writeable and implements
|
||||||
|
// the Buffer interface
|
||||||
|
type Channel interface {
|
||||||
|
SimpleChannel
|
||||||
|
Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func pipe(input SimpleOutChannel, output SimpleInChannel, closeWhenDone bool) {
|
||||||
|
for elem := range input.Out() {
|
||||||
|
output.In() <- elem
|
||||||
|
}
|
||||||
|
if closeWhenDone {
|
||||||
|
output.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func multiplex(output SimpleInChannel, inputs []SimpleOutChannel, closeWhenDone bool) {
|
||||||
|
inputCount := len(inputs)
|
||||||
|
cases := make([]reflect.SelectCase, inputCount)
|
||||||
|
for i := range cases {
|
||||||
|
cases[i].Dir = reflect.SelectRecv
|
||||||
|
cases[i].Chan = reflect.ValueOf(inputs[i].Out())
|
||||||
|
}
|
||||||
|
for inputCount > 0 {
|
||||||
|
chosen, recv, recvOK := reflect.Select(cases)
|
||||||
|
if recvOK {
|
||||||
|
output.In() <- recv.Interface()
|
||||||
|
} else {
|
||||||
|
cases[chosen].Chan = reflect.ValueOf(nil)
|
||||||
|
inputCount--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if closeWhenDone {
|
||||||
|
output.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tee(input SimpleOutChannel, outputs []SimpleInChannel, closeWhenDone bool) {
|
||||||
|
cases := make([]reflect.SelectCase, len(outputs))
|
||||||
|
for i := range cases {
|
||||||
|
cases[i].Dir = reflect.SelectSend
|
||||||
|
}
|
||||||
|
for elem := range input.Out() {
|
||||||
|
for i := range cases {
|
||||||
|
cases[i].Chan = reflect.ValueOf(outputs[i].In())
|
||||||
|
cases[i].Send = reflect.ValueOf(elem)
|
||||||
|
}
|
||||||
|
for _ = range cases {
|
||||||
|
chosen, _, _ := reflect.Select(cases)
|
||||||
|
cases[chosen].Chan = reflect.ValueOf(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if closeWhenDone {
|
||||||
|
for i := range outputs {
|
||||||
|
outputs[i].Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func distribute(input SimpleOutChannel, outputs []SimpleInChannel, closeWhenDone bool) {
|
||||||
|
cases := make([]reflect.SelectCase, len(outputs))
|
||||||
|
for i := range cases {
|
||||||
|
cases[i].Dir = reflect.SelectSend
|
||||||
|
cases[i].Chan = reflect.ValueOf(outputs[i].In())
|
||||||
|
}
|
||||||
|
for elem := range input.Out() {
|
||||||
|
for i := range cases {
|
||||||
|
cases[i].Send = reflect.ValueOf(elem)
|
||||||
|
}
|
||||||
|
reflect.Select(cases)
|
||||||
|
}
|
||||||
|
if closeWhenDone {
|
||||||
|
for i := range outputs {
|
||||||
|
outputs[i].Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pipe connects the input channel to the output channel so that
|
||||||
|
// they behave as if a single channel.
|
||||||
|
func Pipe(input SimpleOutChannel, output SimpleInChannel) {
|
||||||
|
go pipe(input, output, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiplex takes an arbitrary number of input channels and multiplexes their output into a single output
|
||||||
|
// channel. When all input channels have been closed, the output channel is closed. Multiplex with a single
|
||||||
|
// input channel is equivalent to Pipe (though slightly less efficient).
|
||||||
|
func Multiplex(output SimpleInChannel, inputs ...SimpleOutChannel) {
|
||||||
|
if len(inputs) == 0 {
|
||||||
|
panic("channels: Multiplex requires at least one input")
|
||||||
|
}
|
||||||
|
go multiplex(output, inputs, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tee (like its Unix namesake) takes a single input channel and an arbitrary number of output channels
|
||||||
|
// and duplicates each input into every output. When the input channel is closed, all outputs channels are closed.
|
||||||
|
// Tee with a single output channel is equivalent to Pipe (though slightly less efficient).
|
||||||
|
func Tee(input SimpleOutChannel, outputs ...SimpleInChannel) {
|
||||||
|
if len(outputs) == 0 {
|
||||||
|
panic("channels: Tee requires at least one output")
|
||||||
|
}
|
||||||
|
go tee(input, outputs, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distribute takes a single input channel and an arbitrary number of output channels and duplicates each input
|
||||||
|
// into *one* available output. If multiple outputs are waiting for a value, one is chosen at random. When the
|
||||||
|
// input channel is closed, all outputs channels are closed. Distribute with a single output channel is
|
||||||
|
// equivalent to Pipe (though slightly less efficient).
|
||||||
|
func Distribute(input SimpleOutChannel, outputs ...SimpleInChannel) {
|
||||||
|
if len(outputs) == 0 {
|
||||||
|
panic("channels: Distribute requires at least one output")
|
||||||
|
}
|
||||||
|
go distribute(input, outputs, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WeakPipe behaves like Pipe (connecting the two channels) except that it does not close
|
||||||
|
// the output channel when the input channel is closed.
|
||||||
|
func WeakPipe(input SimpleOutChannel, output SimpleInChannel) {
|
||||||
|
go pipe(input, output, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WeakMultiplex behaves like Multiplex (multiplexing multiple inputs into a single output) except that it does not close
|
||||||
|
// the output channel when the input channels are closed.
|
||||||
|
func WeakMultiplex(output SimpleInChannel, inputs ...SimpleOutChannel) {
|
||||||
|
if len(inputs) == 0 {
|
||||||
|
panic("channels: WeakMultiplex requires at least one input")
|
||||||
|
}
|
||||||
|
go multiplex(output, inputs, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WeakTee behaves like Tee (duplicating a single input into multiple outputs) except that it does not close
|
||||||
|
// the output channels when the input channel is closed.
|
||||||
|
func WeakTee(input SimpleOutChannel, outputs ...SimpleInChannel) {
|
||||||
|
if len(outputs) == 0 {
|
||||||
|
panic("channels: WeakTee requires at least one output")
|
||||||
|
}
|
||||||
|
go tee(input, outputs, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WeakDistribute behaves like Distribute (distributing a single input amongst multiple outputs) except that
|
||||||
|
// it does not close the output channels when the input channel is closed.
|
||||||
|
func WeakDistribute(input SimpleOutChannel, outputs ...SimpleInChannel) {
|
||||||
|
if len(outputs) == 0 {
|
||||||
|
panic("channels: WeakDistribute requires at least one output")
|
||||||
|
}
|
||||||
|
go distribute(input, outputs, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap takes any readable channel type (chan or <-chan but not chan<-) and
|
||||||
|
// exposes it as a SimpleOutChannel for easy integration with existing channel sources.
|
||||||
|
// It panics if the input is not a readable channel.
|
||||||
|
func Wrap(ch interface{}) SimpleOutChannel {
|
||||||
|
t := reflect.TypeOf(ch)
|
||||||
|
if t.Kind() != reflect.Chan || t.ChanDir()&reflect.RecvDir == 0 {
|
||||||
|
panic("channels: input to Wrap must be readable channel")
|
||||||
|
}
|
||||||
|
realChan := make(chan interface{})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
v := reflect.ValueOf(ch)
|
||||||
|
for {
|
||||||
|
x, ok := v.Recv()
|
||||||
|
if !ok {
|
||||||
|
close(realChan)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
realChan <- x.Interface()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return NativeOutChannel(realChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap takes a SimpleOutChannel and uses reflection to pipe it to a typed native channel for
|
||||||
|
// easy integration with existing channel sources. Output can be any writable channel type (chan or chan<-).
|
||||||
|
// It panics if the output is not a writable channel, or if a value is received that cannot be sent on the
|
||||||
|
// output channel.
|
||||||
|
func Unwrap(input SimpleOutChannel, output interface{}) {
|
||||||
|
t := reflect.TypeOf(output)
|
||||||
|
if t.Kind() != reflect.Chan || t.ChanDir()&reflect.SendDir == 0 {
|
||||||
|
panic("channels: input to Unwrap must be readable channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
v := reflect.ValueOf(output)
|
||||||
|
for {
|
||||||
|
x, ok := <-input.Out()
|
||||||
|
if !ok {
|
||||||
|
v.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v.Send(reflect.ValueOf(x))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
72
vendor/github.com/eapache/channels/infinite_channel.go
generated
vendored
Normal file
72
vendor/github.com/eapache/channels/infinite_channel.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package channels
|
||||||
|
|
||||||
|
import "github.com/eapache/queue"
|
||||||
|
|
||||||
|
// InfiniteChannel implements the Channel interface with an infinite buffer between the input and the output.
|
||||||
|
type InfiniteChannel struct {
|
||||||
|
input, output chan interface{}
|
||||||
|
length chan int
|
||||||
|
buffer *queue.Queue
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInfiniteChannel() *InfiniteChannel {
|
||||||
|
ch := &InfiniteChannel{
|
||||||
|
input: make(chan interface{}),
|
||||||
|
output: make(chan interface{}),
|
||||||
|
length: make(chan int),
|
||||||
|
buffer: queue.New(),
|
||||||
|
}
|
||||||
|
go ch.infiniteBuffer()
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *InfiniteChannel) In() chan<- interface{} {
|
||||||
|
return ch.input
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *InfiniteChannel) Out() <-chan interface{} {
|
||||||
|
return ch.output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *InfiniteChannel) Len() int {
|
||||||
|
return <-ch.length
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *InfiniteChannel) Cap() BufferCap {
|
||||||
|
return Infinity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *InfiniteChannel) Close() {
|
||||||
|
close(ch.input)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *InfiniteChannel) infiniteBuffer() {
|
||||||
|
var input, output chan interface{}
|
||||||
|
var next interface{}
|
||||||
|
input = ch.input
|
||||||
|
|
||||||
|
for input != nil || output != nil {
|
||||||
|
select {
|
||||||
|
case elem, open := <-input:
|
||||||
|
if open {
|
||||||
|
ch.buffer.Add(elem)
|
||||||
|
} else {
|
||||||
|
input = nil
|
||||||
|
}
|
||||||
|
case output <- next:
|
||||||
|
ch.buffer.Remove()
|
||||||
|
case ch.length <- ch.buffer.Length():
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch.buffer.Length() > 0 {
|
||||||
|
output = ch.output
|
||||||
|
next = ch.buffer.Peek()
|
||||||
|
} else {
|
||||||
|
output = nil
|
||||||
|
next = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(ch.output)
|
||||||
|
close(ch.length)
|
||||||
|
}
|
||||||
92
vendor/github.com/eapache/channels/native_channel.go
generated
vendored
Normal file
92
vendor/github.com/eapache/channels/native_channel.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package channels
|
||||||
|
|
||||||
|
// NativeInChannel implements the InChannel interface by wrapping a native go write-only channel.
|
||||||
|
type NativeInChannel chan<- interface{}
|
||||||
|
|
||||||
|
func (ch NativeInChannel) In() chan<- interface{} {
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch NativeInChannel) Len() int {
|
||||||
|
return len(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch NativeInChannel) Cap() BufferCap {
|
||||||
|
return BufferCap(cap(ch))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch NativeInChannel) Close() {
|
||||||
|
close(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NativeOutChannel implements the OutChannel interface by wrapping a native go read-only channel.
|
||||||
|
type NativeOutChannel <-chan interface{}
|
||||||
|
|
||||||
|
func (ch NativeOutChannel) Out() <-chan interface{} {
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch NativeOutChannel) Len() int {
|
||||||
|
return len(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch NativeOutChannel) Cap() BufferCap {
|
||||||
|
return BufferCap(cap(ch))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NativeChannel implements the Channel interface by wrapping a native go channel.
|
||||||
|
type NativeChannel chan interface{}
|
||||||
|
|
||||||
|
// NewNativeChannel makes a new NativeChannel with the given buffer size. Just a convenience wrapper
|
||||||
|
// to avoid having to cast the result of make().
|
||||||
|
func NewNativeChannel(size BufferCap) NativeChannel {
|
||||||
|
return make(chan interface{}, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch NativeChannel) In() chan<- interface{} {
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch NativeChannel) Out() <-chan interface{} {
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch NativeChannel) Len() int {
|
||||||
|
return len(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch NativeChannel) Cap() BufferCap {
|
||||||
|
return BufferCap(cap(ch))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch NativeChannel) Close() {
|
||||||
|
close(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeadChannel is a placeholder implementation of the Channel interface with no buffer
|
||||||
|
// that is never ready for reading or writing. Closing a dead channel is a no-op.
|
||||||
|
// Behaves almost like NativeChannel(nil) except that closing a nil NativeChannel will panic.
|
||||||
|
type DeadChannel struct{}
|
||||||
|
|
||||||
|
func NewDeadChannel() DeadChannel {
|
||||||
|
return DeadChannel{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch DeadChannel) In() chan<- interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch DeadChannel) Out() <-chan interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch DeadChannel) Len() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch DeadChannel) Cap() BufferCap {
|
||||||
|
return BufferCap(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch DeadChannel) Close() {
|
||||||
|
}
|
||||||
113
vendor/github.com/eapache/channels/overflowing_channel.go
generated
vendored
Normal file
113
vendor/github.com/eapache/channels/overflowing_channel.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package channels
|
||||||
|
|
||||||
|
import "github.com/eapache/queue"
|
||||||
|
|
||||||
|
// OverflowingChannel implements the Channel interface in a way that never blocks the writer.
|
||||||
|
// Specifically, if a value is written to an OverflowingChannel when its buffer is full
|
||||||
|
// (or, in an unbuffered case, when the recipient is not ready) then that value is simply discarded.
|
||||||
|
// Note that Go's scheduler can cause discarded values when they could be avoided, simply by scheduling
|
||||||
|
// the writer before the reader, so caveat emptor.
|
||||||
|
// For the opposite behaviour (discarding the oldest element, not the newest) see RingChannel.
|
||||||
|
type OverflowingChannel struct {
|
||||||
|
input, output chan interface{}
|
||||||
|
length chan int
|
||||||
|
buffer *queue.Queue
|
||||||
|
size BufferCap
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOverflowingChannel(size BufferCap) *OverflowingChannel {
|
||||||
|
if size < 0 && size != Infinity {
|
||||||
|
panic("channels: invalid negative size in NewOverflowingChannel")
|
||||||
|
}
|
||||||
|
ch := &OverflowingChannel{
|
||||||
|
input: make(chan interface{}),
|
||||||
|
output: make(chan interface{}),
|
||||||
|
length: make(chan int),
|
||||||
|
size: size,
|
||||||
|
}
|
||||||
|
if size == None {
|
||||||
|
go ch.overflowingDirect()
|
||||||
|
} else {
|
||||||
|
ch.buffer = queue.New()
|
||||||
|
go ch.overflowingBuffer()
|
||||||
|
}
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *OverflowingChannel) In() chan<- interface{} {
|
||||||
|
return ch.input
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *OverflowingChannel) Out() <-chan interface{} {
|
||||||
|
return ch.output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *OverflowingChannel) Len() int {
|
||||||
|
if ch.size == None {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
return <-ch.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *OverflowingChannel) Cap() BufferCap {
|
||||||
|
return ch.size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *OverflowingChannel) Close() {
|
||||||
|
close(ch.input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for entirely unbuffered cases
|
||||||
|
func (ch *OverflowingChannel) overflowingDirect() {
|
||||||
|
for elem := range ch.input {
|
||||||
|
// if we can't write it immediately, drop it and move on
|
||||||
|
select {
|
||||||
|
case ch.output <- elem:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(ch.output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for all buffered cases
|
||||||
|
func (ch *OverflowingChannel) overflowingBuffer() {
|
||||||
|
var input, output chan interface{}
|
||||||
|
var next interface{}
|
||||||
|
input = ch.input
|
||||||
|
|
||||||
|
for input != nil || output != nil {
|
||||||
|
select {
|
||||||
|
// Prefer to write if possible, which is surprisingly effective in reducing
|
||||||
|
// dropped elements due to overflow. The naive read/write select chooses randomly
|
||||||
|
// when both channels are ready, which produces unnecessary drops 50% of the time.
|
||||||
|
case output <- next:
|
||||||
|
ch.buffer.Remove()
|
||||||
|
default:
|
||||||
|
select {
|
||||||
|
case elem, open := <-input:
|
||||||
|
if open {
|
||||||
|
if ch.size == Infinity || ch.buffer.Length() < int(ch.size) {
|
||||||
|
ch.buffer.Add(elem)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
input = nil
|
||||||
|
}
|
||||||
|
case output <- next:
|
||||||
|
ch.buffer.Remove()
|
||||||
|
case ch.length <- ch.buffer.Length():
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch.buffer.Length() > 0 {
|
||||||
|
output = ch.output
|
||||||
|
next = ch.buffer.Peek()
|
||||||
|
} else {
|
||||||
|
output = nil
|
||||||
|
next = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(ch.output)
|
||||||
|
close(ch.length)
|
||||||
|
}
|
||||||
109
vendor/github.com/eapache/channels/resizable_channel.go
generated
vendored
Normal file
109
vendor/github.com/eapache/channels/resizable_channel.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package channels
|
||||||
|
|
||||||
|
import "github.com/eapache/queue"
|
||||||
|
|
||||||
|
// ResizableChannel implements the Channel interface with a resizable buffer between the input and the output.
|
||||||
|
// The channel initially has a buffer size of 1, but can be resized by calling Resize().
|
||||||
|
//
|
||||||
|
// Resizing to a buffer capacity of None is, unfortunately, not supported and will panic
|
||||||
|
// (see https://github.com/eapache/channels/issues/1).
|
||||||
|
// Resizing back and forth between a finite and infinite buffer is fully supported.
|
||||||
|
type ResizableChannel struct {
|
||||||
|
input, output chan interface{}
|
||||||
|
length chan int
|
||||||
|
capacity, resize chan BufferCap
|
||||||
|
size BufferCap
|
||||||
|
buffer *queue.Queue
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResizableChannel() *ResizableChannel {
|
||||||
|
ch := &ResizableChannel{
|
||||||
|
input: make(chan interface{}),
|
||||||
|
output: make(chan interface{}),
|
||||||
|
length: make(chan int),
|
||||||
|
capacity: make(chan BufferCap),
|
||||||
|
resize: make(chan BufferCap),
|
||||||
|
size: 1,
|
||||||
|
buffer: queue.New(),
|
||||||
|
}
|
||||||
|
go ch.magicBuffer()
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *ResizableChannel) In() chan<- interface{} {
|
||||||
|
return ch.input
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *ResizableChannel) Out() <-chan interface{} {
|
||||||
|
return ch.output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *ResizableChannel) Len() int {
|
||||||
|
return <-ch.length
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *ResizableChannel) Cap() BufferCap {
|
||||||
|
val, open := <-ch.capacity
|
||||||
|
if open {
|
||||||
|
return val
|
||||||
|
} else {
|
||||||
|
return ch.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *ResizableChannel) Close() {
|
||||||
|
close(ch.input)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *ResizableChannel) Resize(newSize BufferCap) {
|
||||||
|
if newSize == None {
|
||||||
|
panic("channels: ResizableChannel does not support unbuffered behaviour")
|
||||||
|
}
|
||||||
|
if newSize < 0 && newSize != Infinity {
|
||||||
|
panic("channels: invalid negative size trying to resize channel")
|
||||||
|
}
|
||||||
|
ch.resize <- newSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *ResizableChannel) magicBuffer() {
|
||||||
|
var input, output, nextInput chan interface{}
|
||||||
|
var next interface{}
|
||||||
|
nextInput = ch.input
|
||||||
|
input = nextInput
|
||||||
|
|
||||||
|
for input != nil || output != nil {
|
||||||
|
select {
|
||||||
|
case elem, open := <-input:
|
||||||
|
if open {
|
||||||
|
ch.buffer.Add(elem)
|
||||||
|
} else {
|
||||||
|
input = nil
|
||||||
|
nextInput = nil
|
||||||
|
}
|
||||||
|
case output <- next:
|
||||||
|
ch.buffer.Remove()
|
||||||
|
case ch.size = <-ch.resize:
|
||||||
|
case ch.length <- ch.buffer.Length():
|
||||||
|
case ch.capacity <- ch.size:
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch.buffer.Length() == 0 {
|
||||||
|
output = nil
|
||||||
|
next = nil
|
||||||
|
} else {
|
||||||
|
output = ch.output
|
||||||
|
next = ch.buffer.Peek()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch.size != Infinity && ch.buffer.Length() >= int(ch.size) {
|
||||||
|
input = nil
|
||||||
|
} else {
|
||||||
|
input = nextInput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(ch.output)
|
||||||
|
close(ch.resize)
|
||||||
|
close(ch.length)
|
||||||
|
close(ch.capacity)
|
||||||
|
}
|
||||||
114
vendor/github.com/eapache/channels/ring_channel.go
generated
vendored
Normal file
114
vendor/github.com/eapache/channels/ring_channel.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package channels
|
||||||
|
|
||||||
|
import "github.com/eapache/queue"
|
||||||
|
|
||||||
|
// RingChannel implements the Channel interface in a way that never blocks the writer.
|
||||||
|
// Specifically, if a value is written to a RingChannel when its buffer is full then the oldest
|
||||||
|
// value in the buffer is discarded to make room (just like a standard ring-buffer).
|
||||||
|
// Note that Go's scheduler can cause discarded values when they could be avoided, simply by scheduling
|
||||||
|
// the writer before the reader, so caveat emptor.
|
||||||
|
// For the opposite behaviour (discarding the newest element, not the oldest) see OverflowingChannel.
|
||||||
|
type RingChannel struct {
|
||||||
|
input, output chan interface{}
|
||||||
|
length chan int
|
||||||
|
buffer *queue.Queue
|
||||||
|
size BufferCap
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRingChannel(size BufferCap) *RingChannel {
|
||||||
|
if size < 0 && size != Infinity {
|
||||||
|
panic("channels: invalid negative size in NewRingChannel")
|
||||||
|
}
|
||||||
|
ch := &RingChannel{
|
||||||
|
input: make(chan interface{}),
|
||||||
|
output: make(chan interface{}),
|
||||||
|
buffer: queue.New(),
|
||||||
|
size: size,
|
||||||
|
}
|
||||||
|
if size == None {
|
||||||
|
go ch.overflowingDirect()
|
||||||
|
} else {
|
||||||
|
ch.length = make(chan int)
|
||||||
|
go ch.ringBuffer()
|
||||||
|
}
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *RingChannel) In() chan<- interface{} {
|
||||||
|
return ch.input
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *RingChannel) Out() <-chan interface{} {
|
||||||
|
return ch.output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *RingChannel) Len() int {
|
||||||
|
if ch.size == None {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
return <-ch.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *RingChannel) Cap() BufferCap {
|
||||||
|
return ch.size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *RingChannel) Close() {
|
||||||
|
close(ch.input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for entirely unbuffered cases
|
||||||
|
func (ch *RingChannel) overflowingDirect() {
|
||||||
|
for elem := range ch.input {
|
||||||
|
// if we can't write it immediately, drop it and move on
|
||||||
|
select {
|
||||||
|
case ch.output <- elem:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(ch.output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for all buffered cases
|
||||||
|
func (ch *RingChannel) ringBuffer() {
|
||||||
|
var input, output chan interface{}
|
||||||
|
var next interface{}
|
||||||
|
input = ch.input
|
||||||
|
|
||||||
|
for input != nil || output != nil {
|
||||||
|
select {
|
||||||
|
// Prefer to write if possible, which is surprisingly effective in reducing
|
||||||
|
// dropped elements due to overflow. The naive read/write select chooses randomly
|
||||||
|
// when both channels are ready, which produces unnecessary drops 50% of the time.
|
||||||
|
case output <- next:
|
||||||
|
ch.buffer.Remove()
|
||||||
|
default:
|
||||||
|
select {
|
||||||
|
case elem, open := <-input:
|
||||||
|
if open {
|
||||||
|
ch.buffer.Add(elem)
|
||||||
|
if ch.size != Infinity && ch.buffer.Length() > int(ch.size) {
|
||||||
|
ch.buffer.Remove()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
input = nil
|
||||||
|
}
|
||||||
|
case output <- next:
|
||||||
|
ch.buffer.Remove()
|
||||||
|
case ch.length <- ch.buffer.Length():
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch.buffer.Length() > 0 {
|
||||||
|
output = ch.output
|
||||||
|
next = ch.buffer.Peek()
|
||||||
|
} else {
|
||||||
|
output = nil
|
||||||
|
next = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(ch.output)
|
||||||
|
close(ch.length)
|
||||||
|
}
|
||||||
167
vendor/github.com/eapache/channels/shared_buffer.go
generated
vendored
Normal file
167
vendor/github.com/eapache/channels/shared_buffer.go
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
package channels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/eapache/queue"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sharedBufferChannel implements SimpleChannel and is created by the public
|
||||||
|
//SharedBuffer type below
|
||||||
|
type sharedBufferChannel struct {
|
||||||
|
in chan interface{}
|
||||||
|
out chan interface{}
|
||||||
|
buf *queue.Queue
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *sharedBufferChannel) In() chan<- interface{} {
|
||||||
|
return sch.in
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *sharedBufferChannel) Out() <-chan interface{} {
|
||||||
|
return sch.out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *sharedBufferChannel) Close() {
|
||||||
|
close(sch.in)
|
||||||
|
}
|
||||||
|
|
||||||
|
//SharedBuffer implements the Buffer interface, and permits multiple SimpleChannel instances to "share" a single buffer.
|
||||||
|
//Each channel spawned by NewChannel has its own internal queue (so values flowing through do not get mixed up with
|
||||||
|
//other channels) but the total number of elements buffered by all spawned channels is limited to a single capacity. This
|
||||||
|
//means *all* such channels block and unblock for writing together. The primary use case is for implementing pipeline-style
|
||||||
|
//parallelism with goroutines, limiting the total number of elements in the pipeline without limiting the number of elements
|
||||||
|
//at any particular step.
|
||||||
|
type SharedBuffer struct {
|
||||||
|
cases []reflect.SelectCase // 2n+1 of these; [0] is for control, [1,3,5...] for recv, [2,4,6...] for send
|
||||||
|
chans []*sharedBufferChannel // n of these
|
||||||
|
count int
|
||||||
|
size BufferCap
|
||||||
|
in chan *sharedBufferChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSharedBuffer(size BufferCap) *SharedBuffer {
|
||||||
|
if size < 0 && size != Infinity {
|
||||||
|
panic("channels: invalid negative size in NewSharedBuffer")
|
||||||
|
} else if size == None {
|
||||||
|
panic("channels: SharedBuffer does not support unbuffered behaviour")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &SharedBuffer{
|
||||||
|
size: size,
|
||||||
|
in: make(chan *sharedBufferChannel),
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.cases = append(buf.cases, reflect.SelectCase{
|
||||||
|
Dir: reflect.SelectRecv,
|
||||||
|
Chan: reflect.ValueOf(buf.in),
|
||||||
|
})
|
||||||
|
|
||||||
|
go buf.mainLoop()
|
||||||
|
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewChannel spawns and returns a new channel sharing the underlying buffer.
|
||||||
|
func (buf *SharedBuffer) NewChannel() SimpleChannel {
|
||||||
|
ch := &sharedBufferChannel{
|
||||||
|
in: make(chan interface{}),
|
||||||
|
out: make(chan interface{}),
|
||||||
|
buf: queue.New(),
|
||||||
|
}
|
||||||
|
buf.in <- ch
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
//Close shuts down the SharedBuffer. It is an error to call Close while channels are still using
|
||||||
|
//the buffer (I'm not really sure what would happen if you do so).
|
||||||
|
func (buf *SharedBuffer) Close() {
|
||||||
|
// TODO: what if there are still active channels using this buffer?
|
||||||
|
close(buf.in)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (buf *SharedBuffer) mainLoop() {
|
||||||
|
for {
|
||||||
|
i, val, ok := reflect.Select(buf.cases)
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
if !ok {
|
||||||
|
//Close was called on the SharedBuffer itself
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewChannel was called on the SharedBuffer
|
||||||
|
ch := val.Interface().(*sharedBufferChannel)
|
||||||
|
buf.chans = append(buf.chans, ch)
|
||||||
|
buf.cases = append(buf.cases,
|
||||||
|
reflect.SelectCase{Dir: reflect.SelectRecv},
|
||||||
|
reflect.SelectCase{Dir: reflect.SelectSend},
|
||||||
|
)
|
||||||
|
if buf.size == Infinity || buf.count < int(buf.size) {
|
||||||
|
buf.cases[len(buf.cases)-2].Chan = reflect.ValueOf(ch.in)
|
||||||
|
}
|
||||||
|
} else if i%2 == 0 {
|
||||||
|
//Send
|
||||||
|
if buf.count == int(buf.size) {
|
||||||
|
//room in the buffer again, re-enable all recv cases
|
||||||
|
for j := range buf.chans {
|
||||||
|
if !buf.chans[j].closed {
|
||||||
|
buf.cases[(j*2)+1].Chan = reflect.ValueOf(buf.chans[j].in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.count--
|
||||||
|
ch := buf.chans[(i-1)/2]
|
||||||
|
if ch.buf.Length() > 0 {
|
||||||
|
buf.cases[i].Send = reflect.ValueOf(ch.buf.Peek())
|
||||||
|
ch.buf.Remove()
|
||||||
|
} else {
|
||||||
|
//nothing left for this channel to send, disable sending
|
||||||
|
buf.cases[i].Chan = reflect.Value{}
|
||||||
|
buf.cases[i].Send = reflect.Value{}
|
||||||
|
if ch.closed {
|
||||||
|
// and it was closed, so close the output channel
|
||||||
|
//TODO: shrink slice
|
||||||
|
close(ch.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ch := buf.chans[i/2]
|
||||||
|
if ok {
|
||||||
|
//Receive
|
||||||
|
buf.count++
|
||||||
|
if ch.buf.Length() == 0 && !buf.cases[i+1].Chan.IsValid() {
|
||||||
|
//this channel now has something to send
|
||||||
|
buf.cases[i+1].Chan = reflect.ValueOf(ch.out)
|
||||||
|
buf.cases[i+1].Send = val
|
||||||
|
} else {
|
||||||
|
ch.buf.Add(val.Interface())
|
||||||
|
}
|
||||||
|
if buf.count == int(buf.size) {
|
||||||
|
//buffer full, disable recv cases
|
||||||
|
for j := range buf.chans {
|
||||||
|
buf.cases[(j*2)+1].Chan = reflect.Value{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Close
|
||||||
|
buf.cases[i].Chan = reflect.Value{}
|
||||||
|
ch.closed = true
|
||||||
|
if ch.buf.Length() == 0 && !buf.cases[i+1].Chan.IsValid() {
|
||||||
|
//nothing pending, close the out channel right away
|
||||||
|
//TODO: shrink slice
|
||||||
|
close(ch.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (buf *SharedBuffer) Len() int {
|
||||||
|
return buf.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (buf *SharedBuffer) Cap() BufferCap {
|
||||||
|
return buf.size
|
||||||
|
}
|
||||||
23
vendor/github.com/eapache/queue/.gitignore
generated
vendored
Normal file
23
vendor/github.com/eapache/queue/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
7
vendor/github.com/eapache/queue/.travis.yml
generated
vendored
Normal file
7
vendor/github.com/eapache/queue/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.2
|
||||||
|
- 1.3
|
||||||
|
- 1.4
|
||||||
21
vendor/github.com/eapache/queue/LICENSE
generated
vendored
Normal file
21
vendor/github.com/eapache/queue/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Evan Huus
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
16
vendor/github.com/eapache/queue/README.md
generated
vendored
Normal file
16
vendor/github.com/eapache/queue/README.md
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
Queue
|
||||||
|
=====
|
||||||
|
|
||||||
|
[](https://travis-ci.org/eapache/queue)
|
||||||
|
[](https://godoc.org/github.com/eapache/queue)
|
||||||
|
[](https://eapache.github.io/conduct.html)
|
||||||
|
|
||||||
|
A fast Golang queue using a ring-buffer, based on the version suggested by Dariusz Górecki.
|
||||||
|
Using this instead of other, simpler, queue implementations (slice+append or linked list) provides
|
||||||
|
substantial memory and time benefits, and fewer GC pauses.
|
||||||
|
|
||||||
|
The queue implemented here is as fast as it is in part because it is *not* thread-safe.
|
||||||
|
|
||||||
|
Follows semantic versioning using https://gopkg.in/ - import from
|
||||||
|
[`gopkg.in/eapache/queue.v1`](https://gopkg.in/eapache/queue.v1)
|
||||||
|
for guaranteed API stability.
|
||||||
102
vendor/github.com/eapache/queue/queue.go
generated
vendored
Normal file
102
vendor/github.com/eapache/queue/queue.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
Package queue provides a fast, ring-buffer queue based on the version suggested by Dariusz Górecki.
|
||||||
|
Using this instead of other, simpler, queue implementations (slice+append or linked list) provides
|
||||||
|
substantial memory and time benefits, and fewer GC pauses.
|
||||||
|
|
||||||
|
The queue implemented here is as fast as it is for an additional reason: it is *not* thread-safe.
|
||||||
|
*/
|
||||||
|
package queue
|
||||||
|
|
||||||
|
// minQueueLen is smallest capacity that queue may have.
|
||||||
|
// Must be power of 2 for bitwise modulus: x % n == x & (n - 1).
|
||||||
|
const minQueueLen = 16
|
||||||
|
|
||||||
|
// Queue represents a single instance of the queue data structure.
|
||||||
|
type Queue struct {
|
||||||
|
buf []interface{}
|
||||||
|
head, tail, count int
|
||||||
|
}
|
||||||
|
|
||||||
|
// New constructs and returns a new Queue.
|
||||||
|
func New() *Queue {
|
||||||
|
return &Queue{
|
||||||
|
buf: make([]interface{}, minQueueLen),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length returns the number of elements currently stored in the queue.
|
||||||
|
func (q *Queue) Length() int {
|
||||||
|
return q.count
|
||||||
|
}
|
||||||
|
|
||||||
|
// resizes the queue to fit exactly twice its current contents
|
||||||
|
// this can result in shrinking if the queue is less than half-full
|
||||||
|
func (q *Queue) resize() {
|
||||||
|
newBuf := make([]interface{}, q.count<<1)
|
||||||
|
|
||||||
|
if q.tail > q.head {
|
||||||
|
copy(newBuf, q.buf[q.head:q.tail])
|
||||||
|
} else {
|
||||||
|
n := copy(newBuf, q.buf[q.head:])
|
||||||
|
copy(newBuf[n:], q.buf[:q.tail])
|
||||||
|
}
|
||||||
|
|
||||||
|
q.head = 0
|
||||||
|
q.tail = q.count
|
||||||
|
q.buf = newBuf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add puts an element on the end of the queue.
|
||||||
|
func (q *Queue) Add(elem interface{}) {
|
||||||
|
if q.count == len(q.buf) {
|
||||||
|
q.resize()
|
||||||
|
}
|
||||||
|
|
||||||
|
q.buf[q.tail] = elem
|
||||||
|
// bitwise modulus
|
||||||
|
q.tail = (q.tail + 1) & (len(q.buf) - 1)
|
||||||
|
q.count++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek returns the element at the head of the queue. This call panics
|
||||||
|
// if the queue is empty.
|
||||||
|
func (q *Queue) Peek() interface{} {
|
||||||
|
if q.count <= 0 {
|
||||||
|
panic("queue: Peek() called on empty queue")
|
||||||
|
}
|
||||||
|
return q.buf[q.head]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the element at index i in the queue. If the index is
|
||||||
|
// invalid, the call will panic. This method accepts both positive and
|
||||||
|
// negative index values. Index 0 refers to the first element, and
|
||||||
|
// index -1 refers to the last.
|
||||||
|
func (q *Queue) Get(i int) interface{} {
|
||||||
|
// If indexing backwards, convert to positive index.
|
||||||
|
if i < 0 {
|
||||||
|
i += q.count
|
||||||
|
}
|
||||||
|
if i < 0 || i >= q.count {
|
||||||
|
panic("queue: Get() called with index out of range")
|
||||||
|
}
|
||||||
|
// bitwise modulus
|
||||||
|
return q.buf[(q.head+i)&(len(q.buf)-1)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes and returns the element from the front of the queue. If the
|
||||||
|
// queue is empty, the call will panic.
|
||||||
|
func (q *Queue) Remove() interface{} {
|
||||||
|
if q.count <= 0 {
|
||||||
|
panic("queue: Remove() called on empty queue")
|
||||||
|
}
|
||||||
|
ret := q.buf[q.head]
|
||||||
|
q.buf[q.head] = nil
|
||||||
|
// bitwise modulus
|
||||||
|
q.head = (q.head + 1) & (len(q.buf) - 1)
|
||||||
|
q.count--
|
||||||
|
// Resize down if buffer 1/4 full.
|
||||||
|
if len(q.buf) > minQueueLen && (q.count<<2) == len(q.buf) {
|
||||||
|
q.resize()
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
5
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
Normal file
5
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
6
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
6
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Setup a Global .gitignore for OS and editor generated files:
|
||||||
|
# https://help.github.com/articles/ignoring-files
|
||||||
|
# git config --global core.excludesfile ~/.gitignore_global
|
||||||
|
|
||||||
|
.vagrant
|
||||||
|
*.sublime-project
|
||||||
30
vendor/github.com/fsnotify/fsnotify/.travis.yml
generated
vendored
Normal file
30
vendor/github.com/fsnotify/fsnotify/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- tip
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
fast_finish: true
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- go get -u github.com/golang/lint/golint
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v --race ./...
|
||||||
|
|
||||||
|
after_script:
|
||||||
|
- test -z "$(gofmt -s -l -w . | tee /dev/stderr)"
|
||||||
|
- test -z "$(golint ./... | tee /dev/stderr)"
|
||||||
|
- go vet ./...
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email: false
|
||||||
52
vendor/github.com/fsnotify/fsnotify/AUTHORS
generated
vendored
Normal file
52
vendor/github.com/fsnotify/fsnotify/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Names should be added to this file as
|
||||||
|
# Name or Organization <email address>
|
||||||
|
# The email address is not required for organizations.
|
||||||
|
|
||||||
|
# You can update this list using the following command:
|
||||||
|
#
|
||||||
|
# $ git shortlog -se | awk '{print $2 " " $3 " " $4}'
|
||||||
|
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Aaron L <aaron@bettercoder.net>
|
||||||
|
Adrien Bustany <adrien@bustany.org>
|
||||||
|
Amit Krishnan <amit.krishnan@oracle.com>
|
||||||
|
Anmol Sethi <me@anmol.io>
|
||||||
|
Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
|
||||||
|
Bruno Bigras <bigras.bruno@gmail.com>
|
||||||
|
Caleb Spare <cespare@gmail.com>
|
||||||
|
Case Nelson <case@teammating.com>
|
||||||
|
Chris Howey <chris@howey.me> <howeyc@gmail.com>
|
||||||
|
Christoffer Buchholz <christoffer.buchholz@gmail.com>
|
||||||
|
Daniel Wagner-Hall <dawagner@gmail.com>
|
||||||
|
Dave Cheney <dave@cheney.net>
|
||||||
|
Evan Phoenix <evan@fallingsnow.net>
|
||||||
|
Francisco Souza <f@souza.cc>
|
||||||
|
Hari haran <hariharan.uno@gmail.com>
|
||||||
|
John C Barstow
|
||||||
|
Kelvin Fo <vmirage@gmail.com>
|
||||||
|
Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
|
||||||
|
Matt Layher <mdlayher@gmail.com>
|
||||||
|
Nathan Youngman <git@nathany.com>
|
||||||
|
Nickolai Zeldovich <nickolai@csail.mit.edu>
|
||||||
|
Patrick <patrick@dropbox.com>
|
||||||
|
Paul Hammond <paul@paulhammond.org>
|
||||||
|
Pawel Knap <pawelknap88@gmail.com>
|
||||||
|
Pieter Droogendijk <pieter@binky.org.uk>
|
||||||
|
Pursuit92 <JoshChase@techpursuit.net>
|
||||||
|
Riku Voipio <riku.voipio@linaro.org>
|
||||||
|
Rob Figueiredo <robfig@gmail.com>
|
||||||
|
Rodrigo Chiossi <rodrigochiossi@gmail.com>
|
||||||
|
Slawek Ligus <root@ooz.ie>
|
||||||
|
Soge Zhang <zhssoge@gmail.com>
|
||||||
|
Tiffany Jernigan <tiffany.jernigan@intel.com>
|
||||||
|
Tilak Sharma <tilaks@google.com>
|
||||||
|
Tom Payne <twpayne@gmail.com>
|
||||||
|
Travis Cline <travis.cline@gmail.com>
|
||||||
|
Tudor Golubenco <tudor.g@gmail.com>
|
||||||
|
Vahe Khachikyan <vahe@live.ca>
|
||||||
|
Yukang <moorekang@gmail.com>
|
||||||
|
bronze1man <bronze1man@gmail.com>
|
||||||
|
debrando <denis.brandolini@gmail.com>
|
||||||
|
henrikedwards <henrik.edwards@gmail.com>
|
||||||
|
铁哥 <guotie.9@gmail.com>
|
||||||
317
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
317
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## v1.4.7 / 2018-01-09
|
||||||
|
|
||||||
|
* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine)
|
||||||
|
* Tests: Fix missing verb on format string (thanks @rchiossi)
|
||||||
|
* Linux: Fix deadlock in Remove (thanks @aarondl)
|
||||||
|
* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne)
|
||||||
|
* Docs: Moved FAQ into the README (thanks @vahe)
|
||||||
|
* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich)
|
||||||
|
* Docs: replace references to OS X with macOS
|
||||||
|
|
||||||
|
## v1.4.2 / 2016-10-10
|
||||||
|
|
||||||
|
* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack)
|
||||||
|
|
||||||
|
## v1.4.1 / 2016-10-04
|
||||||
|
|
||||||
|
* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack)
|
||||||
|
|
||||||
|
## v1.4.0 / 2016-10-01
|
||||||
|
|
||||||
|
* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie)
|
||||||
|
|
||||||
|
## v1.3.1 / 2016-06-28
|
||||||
|
|
||||||
|
* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc)
|
||||||
|
|
||||||
|
## v1.3.0 / 2016-04-19
|
||||||
|
|
||||||
|
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
|
||||||
|
|
||||||
|
## v1.2.10 / 2016-03-02
|
||||||
|
|
||||||
|
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
|
||||||
|
|
||||||
|
## v1.2.9 / 2016-01-13
|
||||||
|
|
||||||
|
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
|
||||||
|
|
||||||
|
## v1.2.8 / 2015-12-17
|
||||||
|
|
||||||
|
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
|
||||||
|
* inotify: fix race in test
|
||||||
|
* enable race detection for continuous integration (Linux, Mac, Windows)
|
||||||
|
|
||||||
|
## v1.2.5 / 2015-10-17
|
||||||
|
|
||||||
|
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
|
||||||
|
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
|
||||||
|
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
|
||||||
|
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
|
||||||
|
|
||||||
|
## v1.2.1 / 2015-10-14
|
||||||
|
|
||||||
|
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
|
||||||
|
|
||||||
|
## v1.2.0 / 2015-02-08
|
||||||
|
|
||||||
|
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
|
||||||
|
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
|
||||||
|
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
|
||||||
|
|
||||||
|
## v1.1.1 / 2015-02-05
|
||||||
|
|
||||||
|
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
|
||||||
|
|
||||||
|
## v1.1.0 / 2014-12-12
|
||||||
|
|
||||||
|
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
|
||||||
|
* add low-level functions
|
||||||
|
* only need to store flags on directories
|
||||||
|
* less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13)
|
||||||
|
* done can be an unbuffered channel
|
||||||
|
* remove calls to os.NewSyscallError
|
||||||
|
* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher)
|
||||||
|
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
|
||||||
|
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||||
|
|
||||||
|
## v1.0.4 / 2014-09-07
|
||||||
|
|
||||||
|
* kqueue: add dragonfly to the build tags.
|
||||||
|
* Rename source code files, rearrange code so exported APIs are at the top.
|
||||||
|
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
|
||||||
|
|
||||||
|
## v1.0.3 / 2014-08-19
|
||||||
|
|
||||||
|
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
|
||||||
|
|
||||||
|
## v1.0.2 / 2014-08-17
|
||||||
|
|
||||||
|
* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||||
|
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
|
||||||
|
|
||||||
|
## v1.0.0 / 2014-08-15
|
||||||
|
|
||||||
|
* [API] Remove AddWatch on Windows, use Add.
|
||||||
|
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
|
||||||
|
* Minor updates based on feedback from golint.
|
||||||
|
|
||||||
|
## dev / 2014-07-09
|
||||||
|
|
||||||
|
* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify).
|
||||||
|
* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
|
||||||
|
|
||||||
|
## dev / 2014-07-04
|
||||||
|
|
||||||
|
* kqueue: fix incorrect mutex used in Close()
|
||||||
|
* Update example to demonstrate usage of Op.
|
||||||
|
|
||||||
|
## dev / 2014-06-28
|
||||||
|
|
||||||
|
* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4)
|
||||||
|
* Fix for String() method on Event (thanks Alex Brainman)
|
||||||
|
* Don't build on Plan 9 or Solaris (thanks @4ad)
|
||||||
|
|
||||||
|
## dev / 2014-06-21
|
||||||
|
|
||||||
|
* Events channel of type Event rather than *Event.
|
||||||
|
* [internal] use syscall constants directly for inotify and kqueue.
|
||||||
|
* [internal] kqueue: rename events to kevents and fileEvent to event.
|
||||||
|
|
||||||
|
## dev / 2014-06-19
|
||||||
|
|
||||||
|
* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
|
||||||
|
* [internal] remove cookie from Event struct (unused).
|
||||||
|
* [internal] Event struct has the same definition across every OS.
|
||||||
|
* [internal] remove internal watch and removeWatch methods.
|
||||||
|
|
||||||
|
## dev / 2014-06-12
|
||||||
|
|
||||||
|
* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
|
||||||
|
* [API] Pluralized channel names: Events and Errors.
|
||||||
|
* [API] Renamed FileEvent struct to Event.
|
||||||
|
* [API] Op constants replace methods like IsCreate().
|
||||||
|
|
||||||
|
## dev / 2014-06-12
|
||||||
|
|
||||||
|
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||||
|
|
||||||
|
## dev / 2014-05-23
|
||||||
|
|
||||||
|
* [API] Remove current implementation of WatchFlags.
|
||||||
|
* current implementation doesn't take advantage of OS for efficiency
|
||||||
|
* provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes
|
||||||
|
* no tests for the current implementation
|
||||||
|
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
|
||||||
|
|
||||||
|
## v0.9.3 / 2014-12-31
|
||||||
|
|
||||||
|
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||||
|
|
||||||
|
## v0.9.2 / 2014-08-17
|
||||||
|
|
||||||
|
* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||||
|
|
||||||
|
## v0.9.1 / 2014-06-12
|
||||||
|
|
||||||
|
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||||
|
|
||||||
|
## v0.9.0 / 2014-01-17
|
||||||
|
|
||||||
|
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
|
||||||
|
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
|
||||||
|
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
|
||||||
|
|
||||||
|
## v0.8.12 / 2013-11-13
|
||||||
|
|
||||||
|
* [API] Remove FD_SET and friends from Linux adapter
|
||||||
|
|
||||||
|
## v0.8.11 / 2013-11-02
|
||||||
|
|
||||||
|
* [Doc] Add Changelog [#72][] (thanks @nathany)
|
||||||
|
* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond)
|
||||||
|
|
||||||
|
## v0.8.10 / 2013-10-19
|
||||||
|
|
||||||
|
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
|
||||||
|
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
|
||||||
|
* [Doc] specify OS-specific limits in README (thanks @debrando)
|
||||||
|
|
||||||
|
## v0.8.9 / 2013-09-08
|
||||||
|
|
||||||
|
* [Doc] Contributing (thanks @nathany)
|
||||||
|
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
|
||||||
|
* [Doc] GoCI badge in README (Linux only) [#60][]
|
||||||
|
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
|
||||||
|
|
||||||
|
## v0.8.8 / 2013-06-17
|
||||||
|
|
||||||
|
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
|
||||||
|
|
||||||
|
## v0.8.7 / 2013-06-03
|
||||||
|
|
||||||
|
* [API] Make syscall flags internal
|
||||||
|
* [Fix] inotify: ignore event changes
|
||||||
|
* [Fix] race in symlink test [#45][] (reported by @srid)
|
||||||
|
* [Fix] tests on Windows
|
||||||
|
* lower case error messages
|
||||||
|
|
||||||
|
## v0.8.6 / 2013-05-23
|
||||||
|
|
||||||
|
* kqueue: Use EVT_ONLY flag on Darwin
|
||||||
|
* [Doc] Update README with full example
|
||||||
|
|
||||||
|
## v0.8.5 / 2013-05-09
|
||||||
|
|
||||||
|
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
|
||||||
|
|
||||||
|
## v0.8.4 / 2013-04-07
|
||||||
|
|
||||||
|
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
|
||||||
|
|
||||||
|
## v0.8.3 / 2013-03-13
|
||||||
|
|
||||||
|
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
|
||||||
|
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
|
||||||
|
|
||||||
|
## v0.8.2 / 2013-02-07
|
||||||
|
|
||||||
|
* [Doc] add Authors
|
||||||
|
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
|
||||||
|
|
||||||
|
## v0.8.1 / 2013-01-09
|
||||||
|
|
||||||
|
* [Fix] Windows path separators
|
||||||
|
* [Doc] BSD License
|
||||||
|
|
||||||
|
## v0.8.0 / 2012-11-09
|
||||||
|
|
||||||
|
* kqueue: directory watching improvements (thanks @vmirage)
|
||||||
|
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
|
||||||
|
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
|
||||||
|
|
||||||
|
## v0.7.4 / 2012-10-09
|
||||||
|
|
||||||
|
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
|
||||||
|
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
|
||||||
|
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
|
||||||
|
* [Fix] kqueue: modify after recreation of file
|
||||||
|
|
||||||
|
## v0.7.3 / 2012-09-27
|
||||||
|
|
||||||
|
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
|
||||||
|
* [Fix] kqueue: no longer get duplicate CREATE events
|
||||||
|
|
||||||
|
## v0.7.2 / 2012-09-01
|
||||||
|
|
||||||
|
* kqueue: events for created directories
|
||||||
|
|
||||||
|
## v0.7.1 / 2012-07-14
|
||||||
|
|
||||||
|
* [Fix] for renaming files
|
||||||
|
|
||||||
|
## v0.7.0 / 2012-07-02
|
||||||
|
|
||||||
|
* [Feature] FSNotify flags
|
||||||
|
* [Fix] inotify: Added file name back to event path
|
||||||
|
|
||||||
|
## v0.6.0 / 2012-06-06
|
||||||
|
|
||||||
|
* kqueue: watch files after directory created (thanks @tmc)
|
||||||
|
|
||||||
|
## v0.5.1 / 2012-05-22
|
||||||
|
|
||||||
|
* [Fix] inotify: remove all watches before Close()
|
||||||
|
|
||||||
|
## v0.5.0 / 2012-05-03
|
||||||
|
|
||||||
|
* [API] kqueue: return errors during watch instead of sending over channel
|
||||||
|
* kqueue: match symlink behavior on Linux
|
||||||
|
* inotify: add `DELETE_SELF` (requested by @taralx)
|
||||||
|
* [Fix] kqueue: handle EINTR (reported by @robfig)
|
||||||
|
* [Doc] Godoc example [#1][] (thanks @davecheney)
|
||||||
|
|
||||||
|
## v0.4.0 / 2012-03-30
|
||||||
|
|
||||||
|
* Go 1 released: build with go tool
|
||||||
|
* [Feature] Windows support using winfsnotify
|
||||||
|
* Windows does not have attribute change notifications
|
||||||
|
* Roll attribute notifications into IsModify
|
||||||
|
|
||||||
|
## v0.3.0 / 2012-02-19
|
||||||
|
|
||||||
|
* kqueue: add files when watch directory
|
||||||
|
|
||||||
|
## v0.2.0 / 2011-12-30
|
||||||
|
|
||||||
|
* update to latest Go weekly code
|
||||||
|
|
||||||
|
## v0.1.0 / 2011-10-19
|
||||||
|
|
||||||
|
* kqueue: add watch on file creation to match inotify
|
||||||
|
* kqueue: create file event
|
||||||
|
* inotify: ignore `IN_IGNORED` events
|
||||||
|
* event String()
|
||||||
|
* linux: common FileEvent functions
|
||||||
|
* initial commit
|
||||||
|
|
||||||
|
[#79]: https://github.com/howeyc/fsnotify/pull/79
|
||||||
|
[#77]: https://github.com/howeyc/fsnotify/pull/77
|
||||||
|
[#72]: https://github.com/howeyc/fsnotify/issues/72
|
||||||
|
[#71]: https://github.com/howeyc/fsnotify/issues/71
|
||||||
|
[#70]: https://github.com/howeyc/fsnotify/issues/70
|
||||||
|
[#63]: https://github.com/howeyc/fsnotify/issues/63
|
||||||
|
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||||
|
[#60]: https://github.com/howeyc/fsnotify/issues/60
|
||||||
|
[#59]: https://github.com/howeyc/fsnotify/issues/59
|
||||||
|
[#49]: https://github.com/howeyc/fsnotify/issues/49
|
||||||
|
[#45]: https://github.com/howeyc/fsnotify/issues/45
|
||||||
|
[#40]: https://github.com/howeyc/fsnotify/issues/40
|
||||||
|
[#36]: https://github.com/howeyc/fsnotify/issues/36
|
||||||
|
[#33]: https://github.com/howeyc/fsnotify/issues/33
|
||||||
|
[#29]: https://github.com/howeyc/fsnotify/issues/29
|
||||||
|
[#25]: https://github.com/howeyc/fsnotify/issues/25
|
||||||
|
[#24]: https://github.com/howeyc/fsnotify/issues/24
|
||||||
|
[#21]: https://github.com/howeyc/fsnotify/issues/21
|
||||||
77
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
77
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
|
||||||
|
* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/fsnotify/fsnotify/issues).
|
||||||
|
* Please indicate the platform you are using fsnotify on.
|
||||||
|
* A code example to reproduce the problem is appreciated.
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
### Contributor License Agreement
|
||||||
|
|
||||||
|
fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
|
||||||
|
|
||||||
|
Please indicate that you have signed the CLA in your pull request.
|
||||||
|
|
||||||
|
### How fsnotify is Developed
|
||||||
|
|
||||||
|
* Development is done on feature branches.
|
||||||
|
* Tests are run on BSD, Linux, macOS and Windows.
|
||||||
|
* Pull requests are reviewed and [applied to master][am] using [hub][].
|
||||||
|
* Maintainers may modify or squash commits rather than asking contributors to.
|
||||||
|
* To issue a new release, the maintainers will:
|
||||||
|
* Update the CHANGELOG
|
||||||
|
* Tag a version, which will become available through gopkg.in.
|
||||||
|
|
||||||
|
### How to Fork
|
||||||
|
|
||||||
|
For smooth sailing, always use the original import path. Installing with `go get` makes this easy.
|
||||||
|
|
||||||
|
1. Install from GitHub (`go get -u github.com/fsnotify/fsnotify`)
|
||||||
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||||
|
3. Ensure everything works and the tests pass (see below)
|
||||||
|
4. Commit your changes (`git commit -am 'Add some feature'`)
|
||||||
|
|
||||||
|
Contribute upstream:
|
||||||
|
|
||||||
|
1. Fork fsnotify on GitHub
|
||||||
|
2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
|
||||||
|
3. Push to the branch (`git push fork my-new-feature`)
|
||||||
|
4. Create a new Pull Request on GitHub
|
||||||
|
|
||||||
|
This workflow is [thoroughly explained by Katrina Owen](https://splice.com/blog/contributing-open-source-git-repositories-go/).
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
fsnotify uses build tags to compile different code on Linux, BSD, macOS, and Windows.
|
||||||
|
|
||||||
|
Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on.
|
||||||
|
|
||||||
|
To aid in cross-platform testing there is a Vagrantfile for Linux and BSD.
|
||||||
|
|
||||||
|
* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/)
|
||||||
|
* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder.
|
||||||
|
* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password)
|
||||||
|
* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd fsnotify/fsnotify; go test'`.
|
||||||
|
* When you're done, you will want to halt or destroy the Vagrant boxes.
|
||||||
|
|
||||||
|
Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory.
|
||||||
|
|
||||||
|
Right now there is no equivalent solution for Windows and macOS, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads).
|
||||||
|
|
||||||
|
### Maintainers
|
||||||
|
|
||||||
|
Help maintaining fsnotify is welcome. To be a maintainer:
|
||||||
|
|
||||||
|
* Submit a pull request and sign the CLA as above.
|
||||||
|
* You must be able to run the test suite on Mac, Windows, Linux and BSD.
|
||||||
|
|
||||||
|
To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][].
|
||||||
|
|
||||||
|
All code changes should be internal pull requests.
|
||||||
|
|
||||||
|
Releases are tagged using [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
[hub]: https://github.com/github/hub
|
||||||
|
[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs
|
||||||
28
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
28
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
Copyright (c) 2012 fsnotify Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
79
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
79
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# File system notifications for Go
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/fsnotify/fsnotify) [](https://goreportcard.com/report/github.com/fsnotify/fsnotify)
|
||||||
|
|
||||||
|
fsnotify utilizes [golang.org/x/sys](https://godoc.org/golang.org/x/sys) rather than `syscall` from the standard library. Ensure you have the latest version installed by running:
|
||||||
|
|
||||||
|
```console
|
||||||
|
go get -u golang.org/x/sys/...
|
||||||
|
```
|
||||||
|
|
||||||
|
Cross platform: Windows, Linux, BSD and macOS.
|
||||||
|
|
||||||
|
|Adapter |OS |Status |
|
||||||
|
|----------|----------|----------|
|
||||||
|
|inotify |Linux 2.6.27 or later, Android\*|Supported [](https://travis-ci.org/fsnotify/fsnotify)|
|
||||||
|
|kqueue |BSD, macOS, iOS\*|Supported [](https://travis-ci.org/fsnotify/fsnotify)|
|
||||||
|
|ReadDirectoryChangesW|Windows|Supported [](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)|
|
||||||
|
|FSEvents |macOS |[Planned](https://github.com/fsnotify/fsnotify/issues/11)|
|
||||||
|
|FEN |Solaris 11 |[In Progress](https://github.com/fsnotify/fsnotify/issues/12)|
|
||||||
|
|fanotify |Linux 2.6.37+ | |
|
||||||
|
|USN Journals |Windows |[Maybe](https://github.com/fsnotify/fsnotify/issues/53)|
|
||||||
|
|Polling |*All* |[Maybe](https://github.com/fsnotify/fsnotify/issues/9)|
|
||||||
|
|
||||||
|
\* Android and iOS are untested.
|
||||||
|
|
||||||
|
Please see [the documentation](https://godoc.org/github.com/fsnotify/fsnotify) and consult the [FAQ](#faq) for usage information.
|
||||||
|
|
||||||
|
## API stability
|
||||||
|
|
||||||
|
fsnotify is a fork of [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA).
|
||||||
|
|
||||||
|
All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/). Further API changes are [planned](https://github.com/fsnotify/fsnotify/milestones), and will be tagged with a new major revision number.
|
||||||
|
|
||||||
|
Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Please refer to [CONTRIBUTING][] before opening an issue or pull request.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
See [example_test.go](https://github.com/fsnotify/fsnotify/blob/master/example_test.go).
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
**When a file is moved to another directory is it still being watched?**
|
||||||
|
|
||||||
|
No (it shouldn't be, unless you are watching where it was moved to).
|
||||||
|
|
||||||
|
**When I watch a directory, are all subdirectories watched as well?**
|
||||||
|
|
||||||
|
No, you must add watches for any directory you want to watch (a recursive watcher is on the roadmap [#18][]).
|
||||||
|
|
||||||
|
**Do I have to watch the Error and Event channels in a separate goroutine?**
|
||||||
|
|
||||||
|
As of now, yes. Looking into making this single-thread friendly (see [howeyc #7][#7])
|
||||||
|
|
||||||
|
**Why am I receiving multiple events for the same file on OS X?**
|
||||||
|
|
||||||
|
Spotlight indexing on OS X can result in multiple events (see [howeyc #62][#62]). A temporary workaround is to add your folder(s) to the *Spotlight Privacy settings* until we have a native FSEvents implementation (see [#11][]).
|
||||||
|
|
||||||
|
**How many files can be watched at once?**
|
||||||
|
|
||||||
|
There are OS-specific limits as to how many watches can be created:
|
||||||
|
* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit, reaching this limit results in a "no space left on device" error.
|
||||||
|
* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error.
|
||||||
|
|
||||||
|
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||||
|
[#18]: https://github.com/fsnotify/fsnotify/issues/18
|
||||||
|
[#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||||
|
[#7]: https://github.com/howeyc/fsnotify/issues/7
|
||||||
|
|
||||||
|
[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md
|
||||||
|
|
||||||
|
## Related Projects
|
||||||
|
|
||||||
|
* [notify](https://github.com/rjeczalik/notify)
|
||||||
|
* [fsevents](https://github.com/fsnotify/fsevents)
|
||||||
|
|
||||||
37
vendor/github.com/fsnotify/fsnotify/fen.go
generated
vendored
Normal file
37
vendor/github.com/fsnotify/fsnotify/fen.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build solaris
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops watching the the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
66
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
Normal file
66
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !plan9
|
||||||
|
|
||||||
|
// Package fsnotify provides a platform-independent interface for file system notifications.
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Event represents a single file system notification.
|
||||||
|
type Event struct {
|
||||||
|
Name string // Relative path to the file or directory.
|
||||||
|
Op Op // File operation that triggered the event.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Op describes a set of file operations.
|
||||||
|
type Op uint32
|
||||||
|
|
||||||
|
// These are the generalized file operations that can trigger a notification.
|
||||||
|
const (
|
||||||
|
Create Op = 1 << iota
|
||||||
|
Write
|
||||||
|
Remove
|
||||||
|
Rename
|
||||||
|
Chmod
|
||||||
|
)
|
||||||
|
|
||||||
|
func (op Op) String() string {
|
||||||
|
// Use a buffer for efficient string concatenation
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
if op&Create == Create {
|
||||||
|
buffer.WriteString("|CREATE")
|
||||||
|
}
|
||||||
|
if op&Remove == Remove {
|
||||||
|
buffer.WriteString("|REMOVE")
|
||||||
|
}
|
||||||
|
if op&Write == Write {
|
||||||
|
buffer.WriteString("|WRITE")
|
||||||
|
}
|
||||||
|
if op&Rename == Rename {
|
||||||
|
buffer.WriteString("|RENAME")
|
||||||
|
}
|
||||||
|
if op&Chmod == Chmod {
|
||||||
|
buffer.WriteString("|CHMOD")
|
||||||
|
}
|
||||||
|
if buffer.Len() == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return buffer.String()[1:] // Strip leading pipe
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the event in the form
|
||||||
|
// "file: REMOVE|WRITE|..."
|
||||||
|
func (e Event) String() string {
|
||||||
|
return fmt.Sprintf("%q: %s", e.Name, e.Op.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common errors that can be reported by a watcher
|
||||||
|
var ErrEventOverflow = errors.New("fsnotify queue overflow")
|
||||||
337
vendor/github.com/fsnotify/fsnotify/inotify.go
generated
vendored
Normal file
337
vendor/github.com/fsnotify/fsnotify/inotify.go
generated
vendored
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
mu sync.Mutex // Map access
|
||||||
|
fd int
|
||||||
|
poller *fdPoller
|
||||||
|
watches map[string]*watch // Map of inotify watches (key: path)
|
||||||
|
paths map[int]string // Map of watched paths (key: watch descriptor)
|
||||||
|
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||||
|
doneResp chan struct{} // Channel to respond to Close
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
// Create inotify fd
|
||||||
|
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC)
|
||||||
|
if fd == -1 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
// Create epoll
|
||||||
|
poller, err := newFdPoller(fd)
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(fd)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
w := &Watcher{
|
||||||
|
fd: fd,
|
||||||
|
poller: poller,
|
||||||
|
watches: make(map[string]*watch),
|
||||||
|
paths: make(map[int]string),
|
||||||
|
Events: make(chan Event),
|
||||||
|
Errors: make(chan error),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
doneResp: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) isClosed() bool {
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send 'close' signal to goroutine, and set the Watcher to closed.
|
||||||
|
close(w.done)
|
||||||
|
|
||||||
|
// Wake up goroutine
|
||||||
|
w.poller.wake()
|
||||||
|
|
||||||
|
// Wait for goroutine to close
|
||||||
|
<-w.doneResp
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
if w.isClosed() {
|
||||||
|
return errors.New("inotify instance already closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
|
||||||
|
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
|
||||||
|
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
|
||||||
|
|
||||||
|
var flags uint32 = agnosticEvents
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
watchEntry := w.watches[name]
|
||||||
|
if watchEntry != nil {
|
||||||
|
flags |= watchEntry.flags | unix.IN_MASK_ADD
|
||||||
|
}
|
||||||
|
wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
|
||||||
|
if wd == -1 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
|
||||||
|
if watchEntry == nil {
|
||||||
|
w.watches[name] = &watch{wd: uint32(wd), flags: flags}
|
||||||
|
w.paths[wd] = name
|
||||||
|
} else {
|
||||||
|
watchEntry.wd = uint32(wd)
|
||||||
|
watchEntry.flags = flags
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
|
// Fetch the watch.
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
watch, ok := w.watches[name]
|
||||||
|
|
||||||
|
// Remove it from inotify.
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We successfully removed the watch if InotifyRmWatch doesn't return an
|
||||||
|
// error, we need to clean up our internal state to ensure it matches
|
||||||
|
// inotify's kernel state.
|
||||||
|
delete(w.paths, int(watch.wd))
|
||||||
|
delete(w.watches, name)
|
||||||
|
|
||||||
|
// inotify_rm_watch will return EINVAL if the file has been deleted;
|
||||||
|
// the inotify will already have been removed.
|
||||||
|
// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
|
||||||
|
// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
|
||||||
|
// so that EINVAL means that the wd is being rm_watch()ed or its file removed
|
||||||
|
// by another thread and we have not received IN_IGNORE event.
|
||||||
|
success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
|
||||||
|
if success == -1 {
|
||||||
|
// TODO: Perhaps it's not helpful to return an error here in every case.
|
||||||
|
// the only two possible errors are:
|
||||||
|
// EBADF, which happens when w.fd is not a valid file descriptor of any kind.
|
||||||
|
// EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor.
|
||||||
|
// Watch descriptors are invalidated when they are removed explicitly or implicitly;
|
||||||
|
// explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted.
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type watch struct {
|
||||||
|
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
||||||
|
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from the inotify file descriptor, converts the
|
||||||
|
// received events into Event objects and sends them via the Events channel
|
||||||
|
func (w *Watcher) readEvents() {
|
||||||
|
var (
|
||||||
|
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||||
|
n int // Number of bytes read with read()
|
||||||
|
errno error // Syscall errno
|
||||||
|
ok bool // For poller.wait
|
||||||
|
)
|
||||||
|
|
||||||
|
defer close(w.doneResp)
|
||||||
|
defer close(w.Errors)
|
||||||
|
defer close(w.Events)
|
||||||
|
defer unix.Close(w.fd)
|
||||||
|
defer w.poller.close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// See if we have been closed.
|
||||||
|
if w.isClosed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, errno = w.poller.wait()
|
||||||
|
if errno != nil {
|
||||||
|
select {
|
||||||
|
case w.Errors <- errno:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
n, errno = unix.Read(w.fd, buf[:])
|
||||||
|
// If a signal interrupted execution, see if we've been asked to close, and try again.
|
||||||
|
// http://man7.org/linux/man-pages/man7/signal.7.html :
|
||||||
|
// "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable"
|
||||||
|
if errno == unix.EINTR {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// unix.Read might have been woken up by Close. If so, we're done.
|
||||||
|
if w.isClosed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < unix.SizeofInotifyEvent {
|
||||||
|
var err error
|
||||||
|
if n == 0 {
|
||||||
|
// If EOF is received. This should really never happen.
|
||||||
|
err = io.EOF
|
||||||
|
} else if n < 0 {
|
||||||
|
// If an error occurred while reading.
|
||||||
|
err = errno
|
||||||
|
} else {
|
||||||
|
// Read was too short.
|
||||||
|
err = errors.New("notify: short read in readEvents()")
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset uint32
|
||||||
|
// We don't know how many events we just read into the buffer
|
||||||
|
// While the offset points to at least one whole event...
|
||||||
|
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
||||||
|
// Point "raw" to the event in the buffer
|
||||||
|
raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||||
|
|
||||||
|
mask := uint32(raw.Mask)
|
||||||
|
nameLen := uint32(raw.Len)
|
||||||
|
|
||||||
|
if mask&unix.IN_Q_OVERFLOW != 0 {
|
||||||
|
select {
|
||||||
|
case w.Errors <- ErrEventOverflow:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the event happened to the watched directory or the watched file, the kernel
|
||||||
|
// doesn't append the filename to the event, but we would like to always fill the
|
||||||
|
// the "Name" field with a valid filename. We retrieve the path of the watch from
|
||||||
|
// the "paths" map.
|
||||||
|
w.mu.Lock()
|
||||||
|
name, ok := w.paths[int(raw.Wd)]
|
||||||
|
// IN_DELETE_SELF occurs when the file/directory being watched is removed.
|
||||||
|
// This is a sign to clean up the maps, otherwise we are no longer in sync
|
||||||
|
// with the inotify kernel state which has already deleted the watch
|
||||||
|
// automatically.
|
||||||
|
if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
||||||
|
delete(w.paths, int(raw.Wd))
|
||||||
|
delete(w.watches, name)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if nameLen > 0 {
|
||||||
|
// Point "bytes" at the first byte of the filename
|
||||||
|
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))
|
||||||
|
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||||
|
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
||||||
|
}
|
||||||
|
|
||||||
|
event := newEvent(name, mask)
|
||||||
|
|
||||||
|
// Send the events that are not ignored on the events channel
|
||||||
|
if !event.ignoreLinux(mask) {
|
||||||
|
select {
|
||||||
|
case w.Events <- event:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next event in the buffer
|
||||||
|
offset += unix.SizeofInotifyEvent + nameLen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certain types of events can be "ignored" and not sent over the Events
|
||||||
|
// channel. Such as events marked ignore by the kernel, or MODIFY events
|
||||||
|
// against files that do not exist.
|
||||||
|
func (e *Event) ignoreLinux(mask uint32) bool {
|
||||||
|
// Ignore anything the inotify API says to ignore
|
||||||
|
if mask&unix.IN_IGNORED == unix.IN_IGNORED {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the event is not a DELETE or RENAME, the file must exist.
|
||||||
|
// Otherwise the event is ignored.
|
||||||
|
// *Note*: this was put in place because it was seen that a MODIFY
|
||||||
|
// event was sent after the DELETE. This ignores that MODIFY and
|
||||||
|
// assumes a DELETE will come or has come if the file doesn't exist.
|
||||||
|
if !(e.Op&Remove == Remove || e.Op&Rename == Rename) {
|
||||||
|
_, statErr := os.Lstat(e.Name)
|
||||||
|
return os.IsNotExist(statErr)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEvent returns an platform-independent Event based on an inotify mask.
|
||||||
|
func newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
|
||||||
|
e.Op |= Create
|
||||||
|
}
|
||||||
|
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
187
vendor/github.com/fsnotify/fsnotify/inotify_poller.go
generated
vendored
Normal file
187
vendor/github.com/fsnotify/fsnotify/inotify_poller.go
generated
vendored
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fdPoller struct {
|
||||||
|
fd int // File descriptor (as returned by the inotify_init() syscall)
|
||||||
|
epfd int // Epoll file descriptor
|
||||||
|
pipe [2]int // Pipe for waking up
|
||||||
|
}
|
||||||
|
|
||||||
|
func emptyPoller(fd int) *fdPoller {
|
||||||
|
poller := new(fdPoller)
|
||||||
|
poller.fd = fd
|
||||||
|
poller.epfd = -1
|
||||||
|
poller.pipe[0] = -1
|
||||||
|
poller.pipe[1] = -1
|
||||||
|
return poller
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new inotify poller.
|
||||||
|
// This creates an inotify handler, and an epoll handler.
|
||||||
|
func newFdPoller(fd int) (*fdPoller, error) {
|
||||||
|
var errno error
|
||||||
|
poller := emptyPoller(fd)
|
||||||
|
defer func() {
|
||||||
|
if errno != nil {
|
||||||
|
poller.close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
poller.fd = fd
|
||||||
|
|
||||||
|
// Create epoll fd
|
||||||
|
poller.epfd, errno = unix.EpollCreate1(0)
|
||||||
|
if poller.epfd == -1 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
// Create pipe; pipe[0] is the read end, pipe[1] the write end.
|
||||||
|
errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK)
|
||||||
|
if errno != nil {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register inotify fd with epoll
|
||||||
|
event := unix.EpollEvent{
|
||||||
|
Fd: int32(poller.fd),
|
||||||
|
Events: unix.EPOLLIN,
|
||||||
|
}
|
||||||
|
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event)
|
||||||
|
if errno != nil {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register pipe fd with epoll
|
||||||
|
event = unix.EpollEvent{
|
||||||
|
Fd: int32(poller.pipe[0]),
|
||||||
|
Events: unix.EPOLLIN,
|
||||||
|
}
|
||||||
|
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event)
|
||||||
|
if errno != nil {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
return poller, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait using epoll.
|
||||||
|
// Returns true if something is ready to be read,
|
||||||
|
// false if there is not.
|
||||||
|
func (poller *fdPoller) wait() (bool, error) {
|
||||||
|
// 3 possible events per fd, and 2 fds, makes a maximum of 6 events.
|
||||||
|
// I don't know whether epoll_wait returns the number of events returned,
|
||||||
|
// or the total number of events ready.
|
||||||
|
// I decided to catch both by making the buffer one larger than the maximum.
|
||||||
|
events := make([]unix.EpollEvent, 7)
|
||||||
|
for {
|
||||||
|
n, errno := unix.EpollWait(poller.epfd, events, -1)
|
||||||
|
if n == -1 {
|
||||||
|
if errno == unix.EINTR {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return false, errno
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
// If there are no events, try again.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n > 6 {
|
||||||
|
// This should never happen. More events were returned than should be possible.
|
||||||
|
return false, errors.New("epoll_wait returned more events than I know what to do with")
|
||||||
|
}
|
||||||
|
ready := events[:n]
|
||||||
|
epollhup := false
|
||||||
|
epollerr := false
|
||||||
|
epollin := false
|
||||||
|
for _, event := range ready {
|
||||||
|
if event.Fd == int32(poller.fd) {
|
||||||
|
if event.Events&unix.EPOLLHUP != 0 {
|
||||||
|
// This should not happen, but if it does, treat it as a wakeup.
|
||||||
|
epollhup = true
|
||||||
|
}
|
||||||
|
if event.Events&unix.EPOLLERR != 0 {
|
||||||
|
// If an error is waiting on the file descriptor, we should pretend
|
||||||
|
// something is ready to read, and let unix.Read pick up the error.
|
||||||
|
epollerr = true
|
||||||
|
}
|
||||||
|
if event.Events&unix.EPOLLIN != 0 {
|
||||||
|
// There is data to read.
|
||||||
|
epollin = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if event.Fd == int32(poller.pipe[0]) {
|
||||||
|
if event.Events&unix.EPOLLHUP != 0 {
|
||||||
|
// Write pipe descriptor was closed, by us. This means we're closing down the
|
||||||
|
// watcher, and we should wake up.
|
||||||
|
}
|
||||||
|
if event.Events&unix.EPOLLERR != 0 {
|
||||||
|
// If an error is waiting on the pipe file descriptor.
|
||||||
|
// This is an absolute mystery, and should never ever happen.
|
||||||
|
return false, errors.New("Error on the pipe descriptor.")
|
||||||
|
}
|
||||||
|
if event.Events&unix.EPOLLIN != 0 {
|
||||||
|
// This is a regular wakeup, so we have to clear the buffer.
|
||||||
|
err := poller.clearWake()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if epollhup || epollerr || epollin {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the write end of the poller.
|
||||||
|
func (poller *fdPoller) wake() error {
|
||||||
|
buf := make([]byte, 1)
|
||||||
|
n, errno := unix.Write(poller.pipe[1], buf)
|
||||||
|
if n == -1 {
|
||||||
|
if errno == unix.EAGAIN {
|
||||||
|
// Buffer is full, poller will wake.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (poller *fdPoller) clearWake() error {
|
||||||
|
// You have to be woken up a LOT in order to get to 100!
|
||||||
|
buf := make([]byte, 100)
|
||||||
|
n, errno := unix.Read(poller.pipe[0], buf)
|
||||||
|
if n == -1 {
|
||||||
|
if errno == unix.EAGAIN {
|
||||||
|
// Buffer is empty, someone else cleared our wake.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close all poller file descriptors, but not the one passed to it.
|
||||||
|
func (poller *fdPoller) close() {
|
||||||
|
if poller.pipe[1] != -1 {
|
||||||
|
unix.Close(poller.pipe[1])
|
||||||
|
}
|
||||||
|
if poller.pipe[0] != -1 {
|
||||||
|
unix.Close(poller.pipe[0])
|
||||||
|
}
|
||||||
|
if poller.epfd != -1 {
|
||||||
|
unix.Close(poller.epfd)
|
||||||
|
}
|
||||||
|
}
|
||||||
521
vendor/github.com/fsnotify/fsnotify/kqueue.go
generated
vendored
Normal file
521
vendor/github.com/fsnotify/fsnotify/kqueue.go
generated
vendored
Normal file
@@ -0,0 +1,521 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build freebsd openbsd netbsd dragonfly darwin
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||||
|
|
||||||
|
kq int // File descriptor (as returned by the kqueue() syscall).
|
||||||
|
|
||||||
|
mu sync.Mutex // Protects access to watcher data
|
||||||
|
watches map[string]int // Map of watched file descriptors (key: path).
|
||||||
|
externalWatches map[string]bool // Map of watches added by user of the library.
|
||||||
|
dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue.
|
||||||
|
paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events.
|
||||||
|
fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events).
|
||||||
|
isClosed bool // Set to true when Close() is first called
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathInfo struct {
|
||||||
|
name string
|
||||||
|
isDir bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
kq, err := kqueue()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &Watcher{
|
||||||
|
kq: kq,
|
||||||
|
watches: make(map[string]int),
|
||||||
|
dirFlags: make(map[string]uint32),
|
||||||
|
paths: make(map[int]pathInfo),
|
||||||
|
fileExists: make(map[string]bool),
|
||||||
|
externalWatches: make(map[string]bool),
|
||||||
|
Events: make(chan Event),
|
||||||
|
Errors: make(chan error),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w.isClosed = true
|
||||||
|
|
||||||
|
// copy paths to remove while locked
|
||||||
|
var pathsToRemove = make([]string, 0, len(w.watches))
|
||||||
|
for name := range w.watches {
|
||||||
|
pathsToRemove = append(pathsToRemove, name)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
// unlock before calling Remove, which also locks
|
||||||
|
|
||||||
|
for _, name := range pathsToRemove {
|
||||||
|
w.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a "quit" message to the reader goroutine
|
||||||
|
close(w.done)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
w.externalWatches[name] = true
|
||||||
|
w.mu.Unlock()
|
||||||
|
_, err := w.addWatch(name, noteAllEvents)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops watching the the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
w.mu.Lock()
|
||||||
|
watchfd, ok := w.watches[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerRemove = unix.EV_DELETE
|
||||||
|
if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
unix.Close(watchfd)
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
isDir := w.paths[watchfd].isDir
|
||||||
|
delete(w.watches, name)
|
||||||
|
delete(w.paths, watchfd)
|
||||||
|
delete(w.dirFlags, name)
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
// Find all watched paths that are in this directory that are not external.
|
||||||
|
if isDir {
|
||||||
|
var pathsToRemove []string
|
||||||
|
w.mu.Lock()
|
||||||
|
for _, path := range w.paths {
|
||||||
|
wdir, _ := filepath.Split(path.name)
|
||||||
|
if filepath.Clean(wdir) == name {
|
||||||
|
if !w.externalWatches[path.name] {
|
||||||
|
pathsToRemove = append(pathsToRemove, path.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
for _, name := range pathsToRemove {
|
||||||
|
// Since these are internal, not much sense in propagating error
|
||||||
|
// to the user, as that will just confuse them with an error about
|
||||||
|
// a path they did not explicitly watch themselves.
|
||||||
|
w.Remove(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
||||||
|
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
|
||||||
|
|
||||||
|
// keventWaitTime to block on each read from kevent
|
||||||
|
var keventWaitTime = durationToTimespec(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// addWatch adds name to the watched file set.
|
||||||
|
// The flags are interpreted as described in kevent(2).
|
||||||
|
// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
|
||||||
|
func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
|
||||||
|
var isDir bool
|
||||||
|
// Make ./name and name equivalent
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return "", errors.New("kevent instance already closed")
|
||||||
|
}
|
||||||
|
watchfd, alreadyWatching := w.watches[name]
|
||||||
|
// We already have a watch, but we can still override flags.
|
||||||
|
if alreadyWatching {
|
||||||
|
isDir = w.paths[watchfd].isDir
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if !alreadyWatching {
|
||||||
|
fi, err := os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't watch sockets.
|
||||||
|
if fi.Mode()&os.ModeSocket == os.ModeSocket {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't watch named pipes.
|
||||||
|
if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Follow Symlinks
|
||||||
|
// Unfortunately, Linux can add bogus symlinks to watch list without
|
||||||
|
// issue, and Windows can't do symlinks period (AFAIK). To maintain
|
||||||
|
// consistency, we will act like everything is fine. There will simply
|
||||||
|
// be no file events for broken symlinks.
|
||||||
|
// Hence the returns of nil on errors.
|
||||||
|
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||||
|
name, err = filepath.EvalSymlinks(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
_, alreadyWatching = w.watches[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if alreadyWatching {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err = os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watchfd, err = unix.Open(name, openMode, 0700)
|
||||||
|
if watchfd == -1 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
isDir = fi.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE
|
||||||
|
if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil {
|
||||||
|
unix.Close(watchfd)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !alreadyWatching {
|
||||||
|
w.mu.Lock()
|
||||||
|
w.watches[name] = watchfd
|
||||||
|
w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDir {
|
||||||
|
// Watch the directory if it has not been watched before,
|
||||||
|
// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
|
||||||
|
w.mu.Lock()
|
||||||
|
|
||||||
|
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
|
||||||
|
(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
|
||||||
|
// Store flags so this watch can be updated later
|
||||||
|
w.dirFlags[name] = flags
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if watchDir {
|
||||||
|
if err := w.watchDirectoryFiles(name); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from kqueue and converts the received kevents into
|
||||||
|
// Event values that it sends down the Events channel.
|
||||||
|
func (w *Watcher) readEvents() {
|
||||||
|
eventBuffer := make([]unix.Kevent_t, 10)
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
// See if there is a message on the "done" channel
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
break loop
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get new events
|
||||||
|
kevents, err := read(w.kq, eventBuffer, &keventWaitTime)
|
||||||
|
// EINTR is okay, the syscall was interrupted before timeout expired.
|
||||||
|
if err != nil && err != unix.EINTR {
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
case <-w.done:
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush the events we received to the Events channel
|
||||||
|
for len(kevents) > 0 {
|
||||||
|
kevent := &kevents[0]
|
||||||
|
watchfd := int(kevent.Ident)
|
||||||
|
mask := uint32(kevent.Fflags)
|
||||||
|
w.mu.Lock()
|
||||||
|
path := w.paths[watchfd]
|
||||||
|
w.mu.Unlock()
|
||||||
|
event := newEvent(path.name, mask)
|
||||||
|
|
||||||
|
if path.isDir && !(event.Op&Remove == Remove) {
|
||||||
|
// Double check to make sure the directory exists. This can happen when
|
||||||
|
// we do a rm -fr on a recursively watched folders and we receive a
|
||||||
|
// modification event first but the folder has been deleted and later
|
||||||
|
// receive the delete event
|
||||||
|
if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
|
||||||
|
// mark is as delete event
|
||||||
|
event.Op |= Remove
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Op&Rename == Rename || event.Op&Remove == Remove {
|
||||||
|
w.Remove(event.Name)
|
||||||
|
w.mu.Lock()
|
||||||
|
delete(w.fileExists, event.Name)
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
||||||
|
w.sendDirectoryChangeEvents(event.Name)
|
||||||
|
} else {
|
||||||
|
// Send the event on the Events channel.
|
||||||
|
select {
|
||||||
|
case w.Events <- event:
|
||||||
|
case <-w.done:
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Op&Remove == Remove {
|
||||||
|
// Look for a file that may have overwritten this.
|
||||||
|
// For example, mv f1 f2 will delete f2, then create f2.
|
||||||
|
if path.isDir {
|
||||||
|
fileDir := filepath.Clean(event.Name)
|
||||||
|
w.mu.Lock()
|
||||||
|
_, found := w.watches[fileDir]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if found {
|
||||||
|
// make sure the directory exists before we watch for changes. When we
|
||||||
|
// do a recursive watch and perform rm -fr, the parent directory might
|
||||||
|
// have gone missing, ignore the missing directory and let the
|
||||||
|
// upcoming delete event remove the watch from the parent directory.
|
||||||
|
if _, err := os.Lstat(fileDir); err == nil {
|
||||||
|
w.sendDirectoryChangeEvents(fileDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filePath := filepath.Clean(event.Name)
|
||||||
|
if fileInfo, err := os.Lstat(filePath); err == nil {
|
||||||
|
w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to next event
|
||||||
|
kevents = kevents[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
err := unix.Close(w.kq)
|
||||||
|
if err != nil {
|
||||||
|
// only way the previous loop breaks is if w.done was closed so we need to async send to w.Errors.
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(w.Events)
|
||||||
|
close(w.Errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEvent returns an platform-independent Event based on kqueue Fflags.
|
||||||
|
func newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCreateEvent(name string) Event {
|
||||||
|
return Event{Name: name, Op: Create}
|
||||||
|
}
|
||||||
|
|
||||||
|
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
|
||||||
|
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
||||||
|
// Get all files
|
||||||
|
files, err := ioutil.ReadDir(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fileInfo := range files {
|
||||||
|
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||||
|
filePath, err = w.internalWatch(filePath, fileInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.fileExists[filePath] = true
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendDirectoryEvents searches the directory for newly created files
|
||||||
|
// and sends them over the event channel. This functionality is to have
|
||||||
|
// the BSD version of fsnotify match Linux inotify which provides a
|
||||||
|
// create event for files created in a watched directory.
|
||||||
|
func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
|
||||||
|
// Get all files
|
||||||
|
files, err := ioutil.ReadDir(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for new files
|
||||||
|
for _, fileInfo := range files {
|
||||||
|
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||||
|
err := w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
|
||||||
|
func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
|
||||||
|
w.mu.Lock()
|
||||||
|
_, doesExist := w.fileExists[filePath]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if !doesExist {
|
||||||
|
// Send create event
|
||||||
|
select {
|
||||||
|
case w.Events <- newCreateEvent(filePath):
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// like watchDirectoryFiles (but without doing another ReadDir)
|
||||||
|
filePath, err = w.internalWatch(filePath, fileInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.fileExists[filePath] = true
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
|
||||||
|
if fileInfo.IsDir() {
|
||||||
|
// mimic Linux providing delete events for subdirectories
|
||||||
|
// but preserve the flags used if currently watching subdirectory
|
||||||
|
w.mu.Lock()
|
||||||
|
flags := w.dirFlags[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
|
||||||
|
return w.addWatch(name, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// watch file to mimic Linux inotify
|
||||||
|
return w.addWatch(name, noteAllEvents)
|
||||||
|
}
|
||||||
|
|
||||||
|
// kqueue creates a new kernel event queue and returns a descriptor.
|
||||||
|
func kqueue() (kq int, err error) {
|
||||||
|
kq, err = unix.Kqueue()
|
||||||
|
if kq == -1 {
|
||||||
|
return kq, err
|
||||||
|
}
|
||||||
|
return kq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// register events with the queue
|
||||||
|
func register(kq int, fds []int, flags int, fflags uint32) error {
|
||||||
|
changes := make([]unix.Kevent_t, len(fds))
|
||||||
|
|
||||||
|
for i, fd := range fds {
|
||||||
|
// SetKevent converts int to the platform-specific types:
|
||||||
|
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
|
||||||
|
changes[i].Fflags = fflags
|
||||||
|
}
|
||||||
|
|
||||||
|
// register the events
|
||||||
|
success, err := unix.Kevent(kq, changes, nil, nil)
|
||||||
|
if success == -1 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// read retrieves pending events, or waits until an event occurs.
|
||||||
|
// A timeout of nil blocks indefinitely, while 0 polls the queue.
|
||||||
|
func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) {
|
||||||
|
n, err := unix.Kevent(kq, nil, events, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return events[0:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// durationToTimespec prepares a timeout value
|
||||||
|
func durationToTimespec(d time.Duration) unix.Timespec {
|
||||||
|
return unix.NsecToTimespec(d.Nanoseconds())
|
||||||
|
}
|
||||||
11
vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go
generated
vendored
Normal file
11
vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build freebsd openbsd netbsd dragonfly
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
const openMode = unix.O_NONBLOCK | unix.O_RDONLY
|
||||||
12
vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go
generated
vendored
Normal file
12
vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build darwin
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
// note: this constant is not defined on BSD
|
||||||
|
const openMode = unix.O_EVTONLY
|
||||||
561
vendor/github.com/fsnotify/fsnotify/windows.go
generated
vendored
Normal file
561
vendor/github.com/fsnotify/fsnotify/windows.go
generated
vendored
Normal file
@@ -0,0 +1,561 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
isClosed bool // Set to true when Close() is first called
|
||||||
|
mu sync.Mutex // Map access
|
||||||
|
port syscall.Handle // Handle to completion port
|
||||||
|
watches watchMap // Map of watches (key: i-number)
|
||||||
|
input chan *input // Inputs to the reader are sent on this channel
|
||||||
|
quit chan chan<- error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
|
||||||
|
if e != nil {
|
||||||
|
return nil, os.NewSyscallError("CreateIoCompletionPort", e)
|
||||||
|
}
|
||||||
|
w := &Watcher{
|
||||||
|
port: port,
|
||||||
|
watches: make(watchMap),
|
||||||
|
input: make(chan *input, 1),
|
||||||
|
Events: make(chan Event, 50),
|
||||||
|
Errors: make(chan error),
|
||||||
|
quit: make(chan chan<- error, 1),
|
||||||
|
}
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
if w.isClosed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w.isClosed = true
|
||||||
|
|
||||||
|
// Send "quit" message to the reader goroutine
|
||||||
|
ch := make(chan error)
|
||||||
|
w.quit <- ch
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
if w.isClosed {
|
||||||
|
return errors.New("watcher already closed")
|
||||||
|
}
|
||||||
|
in := &input{
|
||||||
|
op: opAddWatch,
|
||||||
|
path: filepath.Clean(name),
|
||||||
|
flags: sysFSALLEVENTS,
|
||||||
|
reply: make(chan error),
|
||||||
|
}
|
||||||
|
w.input <- in
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-in.reply
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops watching the the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
in := &input{
|
||||||
|
op: opRemoveWatch,
|
||||||
|
path: filepath.Clean(name),
|
||||||
|
reply: make(chan error),
|
||||||
|
}
|
||||||
|
w.input <- in
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-in.reply
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Options for AddWatch
|
||||||
|
sysFSONESHOT = 0x80000000
|
||||||
|
sysFSONLYDIR = 0x1000000
|
||||||
|
|
||||||
|
// Events
|
||||||
|
sysFSACCESS = 0x1
|
||||||
|
sysFSALLEVENTS = 0xfff
|
||||||
|
sysFSATTRIB = 0x4
|
||||||
|
sysFSCLOSE = 0x18
|
||||||
|
sysFSCREATE = 0x100
|
||||||
|
sysFSDELETE = 0x200
|
||||||
|
sysFSDELETESELF = 0x400
|
||||||
|
sysFSMODIFY = 0x2
|
||||||
|
sysFSMOVE = 0xc0
|
||||||
|
sysFSMOVEDFROM = 0x40
|
||||||
|
sysFSMOVEDTO = 0x80
|
||||||
|
sysFSMOVESELF = 0x800
|
||||||
|
|
||||||
|
// Special events
|
||||||
|
sysFSIGNORED = 0x8000
|
||||||
|
sysFSQOVERFLOW = 0x4000
|
||||||
|
)
|
||||||
|
|
||||||
|
func newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
|
||||||
|
e.Op |= Create
|
||||||
|
}
|
||||||
|
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&sysFSMODIFY == sysFSMODIFY {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&sysFSATTRIB == sysFSATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
opAddWatch = iota
|
||||||
|
opRemoveWatch
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
provisional uint64 = 1 << (32 + iota)
|
||||||
|
)
|
||||||
|
|
||||||
|
type input struct {
|
||||||
|
op int
|
||||||
|
path string
|
||||||
|
flags uint32
|
||||||
|
reply chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
type inode struct {
|
||||||
|
handle syscall.Handle
|
||||||
|
volume uint32
|
||||||
|
index uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type watch struct {
|
||||||
|
ov syscall.Overlapped
|
||||||
|
ino *inode // i-number
|
||||||
|
path string // Directory path
|
||||||
|
mask uint64 // Directory itself is being watched with these notify flags
|
||||||
|
names map[string]uint64 // Map of names being watched and their notify flags
|
||||||
|
rename string // Remembers the old name while renaming a file
|
||||||
|
buf [4096]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type indexMap map[uint64]*watch
|
||||||
|
type watchMap map[uint32]indexMap
|
||||||
|
|
||||||
|
func (w *Watcher) wakeupReader() error {
|
||||||
|
e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
|
||||||
|
if e != nil {
|
||||||
|
return os.NewSyscallError("PostQueuedCompletionStatus", e)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDir(pathname string) (dir string, err error) {
|
||||||
|
attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
|
||||||
|
if e != nil {
|
||||||
|
return "", os.NewSyscallError("GetFileAttributes", e)
|
||||||
|
}
|
||||||
|
if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||||
|
dir = pathname
|
||||||
|
} else {
|
||||||
|
dir, _ = filepath.Split(pathname)
|
||||||
|
dir = filepath.Clean(dir)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIno(path string) (ino *inode, err error) {
|
||||||
|
h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
|
||||||
|
syscall.FILE_LIST_DIRECTORY,
|
||||||
|
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
|
||||||
|
nil, syscall.OPEN_EXISTING,
|
||||||
|
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
|
||||||
|
if e != nil {
|
||||||
|
return nil, os.NewSyscallError("CreateFile", e)
|
||||||
|
}
|
||||||
|
var fi syscall.ByHandleFileInformation
|
||||||
|
if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
|
||||||
|
syscall.CloseHandle(h)
|
||||||
|
return nil, os.NewSyscallError("GetFileInformationByHandle", e)
|
||||||
|
}
|
||||||
|
ino = &inode{
|
||||||
|
handle: h,
|
||||||
|
volume: fi.VolumeSerialNumber,
|
||||||
|
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
|
||||||
|
}
|
||||||
|
return ino, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (m watchMap) get(ino *inode) *watch {
|
||||||
|
if i := m[ino.volume]; i != nil {
|
||||||
|
return i[ino.index]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (m watchMap) set(ino *inode, watch *watch) {
|
||||||
|
i := m[ino.volume]
|
||||||
|
if i == nil {
|
||||||
|
i = make(indexMap)
|
||||||
|
m[ino.volume] = i
|
||||||
|
}
|
||||||
|
i[ino.index] = watch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) addWatch(pathname string, flags uint64) error {
|
||||||
|
dir, err := getDir(pathname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if flags&sysFSONLYDIR != 0 && pathname != dir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ino, err := getIno(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
watchEntry := w.watches.get(ino)
|
||||||
|
w.mu.Unlock()
|
||||||
|
if watchEntry == nil {
|
||||||
|
if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
|
||||||
|
syscall.CloseHandle(ino.handle)
|
||||||
|
return os.NewSyscallError("CreateIoCompletionPort", e)
|
||||||
|
}
|
||||||
|
watchEntry = &watch{
|
||||||
|
ino: ino,
|
||||||
|
path: dir,
|
||||||
|
names: make(map[string]uint64),
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
w.watches.set(ino, watchEntry)
|
||||||
|
w.mu.Unlock()
|
||||||
|
flags |= provisional
|
||||||
|
} else {
|
||||||
|
syscall.CloseHandle(ino.handle)
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
watchEntry.mask |= flags
|
||||||
|
} else {
|
||||||
|
watchEntry.names[filepath.Base(pathname)] |= flags
|
||||||
|
}
|
||||||
|
if err = w.startRead(watchEntry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
watchEntry.mask &= ^provisional
|
||||||
|
} else {
|
||||||
|
watchEntry.names[filepath.Base(pathname)] &= ^provisional
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) remWatch(pathname string) error {
|
||||||
|
dir, err := getDir(pathname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ino, err := getIno(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
watch := w.watches.get(ino)
|
||||||
|
w.mu.Unlock()
|
||||||
|
if watch == nil {
|
||||||
|
return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||||
|
watch.mask = 0
|
||||||
|
} else {
|
||||||
|
name := filepath.Base(pathname)
|
||||||
|
w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
return w.startRead(watch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) deleteWatch(watch *watch) {
|
||||||
|
for name, mask := range watch.names {
|
||||||
|
if mask&provisional == 0 {
|
||||||
|
w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED)
|
||||||
|
}
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
if watch.mask != 0 {
|
||||||
|
if watch.mask&provisional == 0 {
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||||
|
}
|
||||||
|
watch.mask = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) startRead(watch *watch) error {
|
||||||
|
if e := syscall.CancelIo(watch.ino.handle); e != nil {
|
||||||
|
w.Errors <- os.NewSyscallError("CancelIo", e)
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
}
|
||||||
|
mask := toWindowsFlags(watch.mask)
|
||||||
|
for _, m := range watch.names {
|
||||||
|
mask |= toWindowsFlags(m)
|
||||||
|
}
|
||||||
|
if mask == 0 {
|
||||||
|
if e := syscall.CloseHandle(watch.ino.handle); e != nil {
|
||||||
|
w.Errors <- os.NewSyscallError("CloseHandle", e)
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
delete(w.watches[watch.ino.volume], watch.ino.index)
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
|
||||||
|
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
|
||||||
|
if e != nil {
|
||||||
|
err := os.NewSyscallError("ReadDirectoryChanges", e)
|
||||||
|
if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
|
||||||
|
// Watched directory was probably removed
|
||||||
|
if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) {
|
||||||
|
if watch.mask&sysFSONESHOT != 0 {
|
||||||
|
watch.mask = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from the I/O completion port, converts the
|
||||||
|
// received events into Event objects and sends them via the Events channel.
|
||||||
|
// Entry point to the I/O thread.
|
||||||
|
func (w *Watcher) readEvents() {
|
||||||
|
var (
|
||||||
|
n, key uint32
|
||||||
|
ov *syscall.Overlapped
|
||||||
|
)
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
|
for {
|
||||||
|
e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
|
||||||
|
watch := (*watch)(unsafe.Pointer(ov))
|
||||||
|
|
||||||
|
if watch == nil {
|
||||||
|
select {
|
||||||
|
case ch := <-w.quit:
|
||||||
|
w.mu.Lock()
|
||||||
|
var indexes []indexMap
|
||||||
|
for _, index := range w.watches {
|
||||||
|
indexes = append(indexes, index)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
for _, index := range indexes {
|
||||||
|
for _, watch := range index {
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if e := syscall.CloseHandle(w.port); e != nil {
|
||||||
|
err = os.NewSyscallError("CloseHandle", e)
|
||||||
|
}
|
||||||
|
close(w.Events)
|
||||||
|
close(w.Errors)
|
||||||
|
ch <- err
|
||||||
|
return
|
||||||
|
case in := <-w.input:
|
||||||
|
switch in.op {
|
||||||
|
case opAddWatch:
|
||||||
|
in.reply <- w.addWatch(in.path, uint64(in.flags))
|
||||||
|
case opRemoveWatch:
|
||||||
|
in.reply <- w.remWatch(in.path)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e {
|
||||||
|
case syscall.ERROR_MORE_DATA:
|
||||||
|
if watch == nil {
|
||||||
|
w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
|
||||||
|
} else {
|
||||||
|
// The i/o succeeded but the buffer is full.
|
||||||
|
// In theory we should be building up a full packet.
|
||||||
|
// In practice we can get away with just carrying on.
|
||||||
|
n = uint32(unsafe.Sizeof(watch.buf))
|
||||||
|
}
|
||||||
|
case syscall.ERROR_ACCESS_DENIED:
|
||||||
|
// Watched directory was probably removed
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
continue
|
||||||
|
case syscall.ERROR_OPERATION_ABORTED:
|
||||||
|
// CancelIo was called on this handle
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
|
||||||
|
continue
|
||||||
|
case nil:
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset uint32
|
||||||
|
for {
|
||||||
|
if n == 0 {
|
||||||
|
w.Events <- newEvent("", sysFSQOVERFLOW)
|
||||||
|
w.Errors <- errors.New("short read in readEvents()")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point "raw" to the event in the buffer
|
||||||
|
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
|
||||||
|
buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
|
||||||
|
name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
|
||||||
|
fullname := filepath.Join(watch.path, name)
|
||||||
|
|
||||||
|
var mask uint64
|
||||||
|
switch raw.Action {
|
||||||
|
case syscall.FILE_ACTION_REMOVED:
|
||||||
|
mask = sysFSDELETESELF
|
||||||
|
case syscall.FILE_ACTION_MODIFIED:
|
||||||
|
mask = sysFSMODIFY
|
||||||
|
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
||||||
|
watch.rename = name
|
||||||
|
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
||||||
|
if watch.names[watch.rename] != 0 {
|
||||||
|
watch.names[name] |= watch.names[watch.rename]
|
||||||
|
delete(watch.names, watch.rename)
|
||||||
|
mask = sysFSMOVESELF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendNameEvent := func() {
|
||||||
|
if w.sendEvent(fullname, watch.names[name]&mask) {
|
||||||
|
if watch.names[name]&sysFSONESHOT != 0 {
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
||||||
|
sendNameEvent()
|
||||||
|
}
|
||||||
|
if raw.Action == syscall.FILE_ACTION_REMOVED {
|
||||||
|
w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
|
||||||
|
if watch.mask&sysFSONESHOT != 0 {
|
||||||
|
watch.mask = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
||||||
|
fullname = filepath.Join(watch.path, watch.rename)
|
||||||
|
sendNameEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next event in the buffer
|
||||||
|
if raw.NextEntryOffset == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
offset += raw.NextEntryOffset
|
||||||
|
|
||||||
|
// Error!
|
||||||
|
if offset >= n {
|
||||||
|
w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.startRead(watch); err != nil {
|
||||||
|
w.Errors <- err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) sendEvent(name string, mask uint64) bool {
|
||||||
|
if mask == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
event := newEvent(name, uint32(mask))
|
||||||
|
select {
|
||||||
|
case ch := <-w.quit:
|
||||||
|
w.quit <- ch
|
||||||
|
case w.Events <- event:
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func toWindowsFlags(mask uint64) uint32 {
|
||||||
|
var m uint32
|
||||||
|
if mask&sysFSACCESS != 0 {
|
||||||
|
m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
|
||||||
|
}
|
||||||
|
if mask&sysFSMODIFY != 0 {
|
||||||
|
m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
|
||||||
|
}
|
||||||
|
if mask&sysFSATTRIB != 0 {
|
||||||
|
m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
|
||||||
|
}
|
||||||
|
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
|
||||||
|
m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func toFSnotifyFlags(action uint32) uint64 {
|
||||||
|
switch action {
|
||||||
|
case syscall.FILE_ACTION_ADDED:
|
||||||
|
return sysFSCREATE
|
||||||
|
case syscall.FILE_ACTION_REMOVED:
|
||||||
|
return sysFSDELETE
|
||||||
|
case syscall.FILE_ACTION_MODIFIED:
|
||||||
|
return sysFSMODIFY
|
||||||
|
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
||||||
|
return sysFSMOVEDFROM
|
||||||
|
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
||||||
|
return sysFSMOVEDTO
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
191
vendor/github.com/golang/glog/LICENSE
generated
vendored
Normal file
191
vendor/github.com/golang/glog/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction, and
|
||||||
|
distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||||
|
owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||||
|
that control, are controlled by, or are under common control with that entity.
|
||||||
|
For the purposes of this definition, "control" means (i) the power, direct or
|
||||||
|
indirect, to cause the direction or management of such entity, whether by
|
||||||
|
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||||
|
permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications, including
|
||||||
|
but not limited to software source code, documentation source, and configuration
|
||||||
|
files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical transformation or
|
||||||
|
translation of a Source form, including but not limited to compiled object code,
|
||||||
|
generated documentation, and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||||
|
available under the License, as indicated by a copyright notice that is included
|
||||||
|
in or attached to the work (an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||||
|
is based on (or derived from) the Work and for which the editorial revisions,
|
||||||
|
annotations, elaborations, or other modifications represent, as a whole, an
|
||||||
|
original work of authorship. For the purposes of this License, Derivative Works
|
||||||
|
shall not include works that remain separable from, or merely link (or bind by
|
||||||
|
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including the original version
|
||||||
|
of the Work and any modifications or additions to that Work or Derivative Works
|
||||||
|
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||||
|
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||||
|
on behalf of the copyright owner. For the purposes of this definition,
|
||||||
|
"submitted" means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems, and
|
||||||
|
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||||
|
the purpose of discussing and improving the Work, but excluding communication
|
||||||
|
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||||
|
owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||||
|
of whom a Contribution has been received by Licensor and subsequently
|
||||||
|
incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License.
|
||||||
|
|
||||||
|
Subject to the terms and conditions of this License, each Contributor hereby
|
||||||
|
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||||
|
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||||
|
Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License.
|
||||||
|
|
||||||
|
Subject to the terms and conditions of this License, each Contributor hereby
|
||||||
|
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||||
|
irrevocable (except as stated in this section) patent license to make, have
|
||||||
|
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||||
|
such license applies only to those patent claims licensable by such Contributor
|
||||||
|
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||||
|
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||||
|
submitted. If You institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||||
|
Contribution incorporated within the Work constitutes direct or contributory
|
||||||
|
patent infringement, then any patent licenses granted to You under this License
|
||||||
|
for that Work shall terminate as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution.
|
||||||
|
|
||||||
|
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||||
|
in any medium, with or without modifications, and in Source or Object form,
|
||||||
|
provided that You meet the following conditions:
|
||||||
|
|
||||||
|
You must give any other recipients of the Work or Derivative Works a copy of
|
||||||
|
this License; and
|
||||||
|
You must cause any modified files to carry prominent notices stating that You
|
||||||
|
changed the files; and
|
||||||
|
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||||
|
all copyright, patent, trademark, and attribution notices from the Source form
|
||||||
|
of the Work, excluding those notices that do not pertain to any part of the
|
||||||
|
Derivative Works; and
|
||||||
|
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||||
|
Derivative Works that You distribute must include a readable copy of the
|
||||||
|
attribution notices contained within such NOTICE file, excluding those notices
|
||||||
|
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||||
|
following places: within a NOTICE text file distributed as part of the
|
||||||
|
Derivative Works; within the Source form or documentation, if provided along
|
||||||
|
with the Derivative Works; or, within a display generated by the Derivative
|
||||||
|
Works, if and wherever such third-party notices normally appear. The contents of
|
||||||
|
the NOTICE file are for informational purposes only and do not modify the
|
||||||
|
License. You may add Your own attribution notices within Derivative Works that
|
||||||
|
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||||
|
provided that such additional attribution notices cannot be construed as
|
||||||
|
modifying the License.
|
||||||
|
You may add Your own copyright statement to Your modifications and may provide
|
||||||
|
additional or different license terms and conditions for use, reproduction, or
|
||||||
|
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||||
|
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||||
|
with the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions.
|
||||||
|
|
||||||
|
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||||
|
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||||
|
conditions of this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||||
|
any separate license agreement you may have executed with Licensor regarding
|
||||||
|
such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks.
|
||||||
|
|
||||||
|
This License does not grant permission to use the trade names, trademarks,
|
||||||
|
service marks, or product names of the Licensor, except as required for
|
||||||
|
reasonable and customary use in describing the origin of the Work and
|
||||||
|
reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||||
|
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||||
|
including, without limitation, any warranties or conditions of TITLE,
|
||||||
|
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||||
|
solely responsible for determining the appropriateness of using or
|
||||||
|
redistributing the Work and assume any risks associated with Your exercise of
|
||||||
|
permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability.
|
||||||
|
|
||||||
|
In no event and under no legal theory, whether in tort (including negligence),
|
||||||
|
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||||
|
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special, incidental,
|
||||||
|
or consequential damages of any character arising as a result of this License or
|
||||||
|
out of the use or inability to use the Work (including but not limited to
|
||||||
|
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||||
|
any and all other commercial damages or losses), even if such Contributor has
|
||||||
|
been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability.
|
||||||
|
|
||||||
|
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||||
|
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||||
|
other liability obligations and/or rights consistent with this License. However,
|
||||||
|
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||||
|
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||||
|
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason of your
|
||||||
|
accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following boilerplate
|
||||||
|
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||||
|
identifying information. (Don't include the brackets!) The text should be
|
||||||
|
enclosed in the appropriate comment syntax for the file format. We also
|
||||||
|
recommend that a file or class name and description of purpose be included on
|
||||||
|
the same "printed page" as the copyright notice for easier identification within
|
||||||
|
third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
||||||
44
vendor/github.com/golang/glog/README
generated
vendored
Normal file
44
vendor/github.com/golang/glog/README
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
glog
|
||||||
|
====
|
||||||
|
|
||||||
|
Leveled execution logs for Go.
|
||||||
|
|
||||||
|
This is an efficient pure Go implementation of leveled logs in the
|
||||||
|
manner of the open source C++ package
|
||||||
|
https://github.com/google/glog
|
||||||
|
|
||||||
|
By binding methods to booleans it is possible to use the log package
|
||||||
|
without paying the expense of evaluating the arguments to the log.
|
||||||
|
Through the -vmodule flag, the package also provides fine-grained
|
||||||
|
control over logging at the file level.
|
||||||
|
|
||||||
|
The comment from glog.go introduces the ideas:
|
||||||
|
|
||||||
|
Package glog implements logging analogous to the Google-internal
|
||||||
|
C++ INFO/ERROR/V setup. It provides functions Info, Warning,
|
||||||
|
Error, Fatal, plus formatting variants such as Infof. It
|
||||||
|
also provides V-style logging controlled by the -v and
|
||||||
|
-vmodule=file=2 flags.
|
||||||
|
|
||||||
|
Basic examples:
|
||||||
|
|
||||||
|
glog.Info("Prepare to repel boarders")
|
||||||
|
|
||||||
|
glog.Fatalf("Initialization failed: %s", err)
|
||||||
|
|
||||||
|
See the documentation for the V function for an explanation
|
||||||
|
of these examples:
|
||||||
|
|
||||||
|
if glog.V(2) {
|
||||||
|
glog.Info("Starting transaction...")
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.V(2).Infoln("Processed", nItems, "elements")
|
||||||
|
|
||||||
|
|
||||||
|
The repository contains an open source version of the log package
|
||||||
|
used inside Google. The master copy of the source lives inside
|
||||||
|
Google, not here. The code in this repo is for export only and is not itself
|
||||||
|
under development. Feature requests will be ignored.
|
||||||
|
|
||||||
|
Send bug reports to golang-nuts@googlegroups.com.
|
||||||
1180
vendor/github.com/golang/glog/glog.go
generated
vendored
Normal file
1180
vendor/github.com/golang/glog/glog.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
124
vendor/github.com/golang/glog/glog_file.go
generated
vendored
Normal file
124
vendor/github.com/golang/glog/glog_file.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
|
||||||
|
//
|
||||||
|
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// File I/O for logs.
|
||||||
|
|
||||||
|
package glog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxSize is the maximum size of a log file in bytes.
|
||||||
|
var MaxSize uint64 = 1024 * 1024 * 1800
|
||||||
|
|
||||||
|
// logDirs lists the candidate directories for new log files.
|
||||||
|
var logDirs []string
|
||||||
|
|
||||||
|
// If non-empty, overrides the choice of directory in which to write logs.
|
||||||
|
// See createLogDirs for the full list of possible destinations.
|
||||||
|
var logDir = flag.String("log_dir", "", "If non-empty, write log files in this directory")
|
||||||
|
|
||||||
|
func createLogDirs() {
|
||||||
|
if *logDir != "" {
|
||||||
|
logDirs = append(logDirs, *logDir)
|
||||||
|
}
|
||||||
|
logDirs = append(logDirs, os.TempDir())
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
pid = os.Getpid()
|
||||||
|
program = filepath.Base(os.Args[0])
|
||||||
|
host = "unknownhost"
|
||||||
|
userName = "unknownuser"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
h, err := os.Hostname()
|
||||||
|
if err == nil {
|
||||||
|
host = shortHostname(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
current, err := user.Current()
|
||||||
|
if err == nil {
|
||||||
|
userName = current.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitize userName since it may contain filepath separators on Windows.
|
||||||
|
userName = strings.Replace(userName, `\`, "_", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortHostname returns its argument, truncating at the first period.
|
||||||
|
// For instance, given "www.google.com" it returns "www".
|
||||||
|
func shortHostname(hostname string) string {
|
||||||
|
if i := strings.Index(hostname, "."); i >= 0 {
|
||||||
|
return hostname[:i]
|
||||||
|
}
|
||||||
|
return hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
// logName returns a new log file name containing tag, with start time t, and
|
||||||
|
// the name for the symlink for tag.
|
||||||
|
func logName(tag string, t time.Time) (name, link string) {
|
||||||
|
name = fmt.Sprintf("%s.%s.%s.log.%s.%04d%02d%02d-%02d%02d%02d.%d",
|
||||||
|
program,
|
||||||
|
host,
|
||||||
|
userName,
|
||||||
|
tag,
|
||||||
|
t.Year(),
|
||||||
|
t.Month(),
|
||||||
|
t.Day(),
|
||||||
|
t.Hour(),
|
||||||
|
t.Minute(),
|
||||||
|
t.Second(),
|
||||||
|
pid)
|
||||||
|
return name, program + "." + tag
|
||||||
|
}
|
||||||
|
|
||||||
|
var onceLogDirs sync.Once
|
||||||
|
|
||||||
|
// create creates a new log file and returns the file and its filename, which
|
||||||
|
// contains tag ("INFO", "FATAL", etc.) and t. If the file is created
|
||||||
|
// successfully, create also attempts to update the symlink for that tag, ignoring
|
||||||
|
// errors.
|
||||||
|
func create(tag string, t time.Time) (f *os.File, filename string, err error) {
|
||||||
|
onceLogDirs.Do(createLogDirs)
|
||||||
|
if len(logDirs) == 0 {
|
||||||
|
return nil, "", errors.New("log: no log dirs")
|
||||||
|
}
|
||||||
|
name, link := logName(tag, t)
|
||||||
|
var lastErr error
|
||||||
|
for _, dir := range logDirs {
|
||||||
|
fname := filepath.Join(dir, name)
|
||||||
|
f, err := os.Create(fname)
|
||||||
|
if err == nil {
|
||||||
|
symlink := filepath.Join(dir, link)
|
||||||
|
os.Remove(symlink) // ignore err
|
||||||
|
os.Symlink(name, symlink) // ignore err
|
||||||
|
return f, fname, nil
|
||||||
|
}
|
||||||
|
lastErr = err
|
||||||
|
}
|
||||||
|
return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr)
|
||||||
|
}
|
||||||
3
vendor/github.com/golang/protobuf/AUTHORS
generated
vendored
Normal file
3
vendor/github.com/golang/protobuf/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# This source code refers to The Go Authors for copyright purposes.
|
||||||
|
# The master list of authors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/AUTHORS.
|
||||||
3
vendor/github.com/golang/protobuf/CONTRIBUTORS
generated
vendored
Normal file
3
vendor/github.com/golang/protobuf/CONTRIBUTORS
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# This source code was written by the Go contributors.
|
||||||
|
# The master list of contributors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/CONTRIBUTORS.
|
||||||
28
vendor/github.com/golang/protobuf/LICENSE
generated
vendored
Normal file
28
vendor/github.com/golang/protobuf/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
324
vendor/github.com/golang/protobuf/proto/buffer.go
generated
vendored
Normal file
324
vendor/github.com/golang/protobuf/proto/buffer.go
generated
vendored
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/encoding/prototext"
|
||||||
|
"google.golang.org/protobuf/encoding/protowire"
|
||||||
|
"google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
WireVarint = 0
|
||||||
|
WireFixed32 = 5
|
||||||
|
WireFixed64 = 1
|
||||||
|
WireBytes = 2
|
||||||
|
WireStartGroup = 3
|
||||||
|
WireEndGroup = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// EncodeVarint returns the varint encoded bytes of v.
|
||||||
|
func EncodeVarint(v uint64) []byte {
|
||||||
|
return protowire.AppendVarint(nil, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SizeVarint returns the length of the varint encoded bytes of v.
|
||||||
|
// This is equal to len(EncodeVarint(v)).
|
||||||
|
func SizeVarint(v uint64) int {
|
||||||
|
return protowire.SizeVarint(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeVarint parses a varint encoded integer from b,
|
||||||
|
// returning the integer value and the length of the varint.
|
||||||
|
// It returns (0, 0) if there is a parse error.
|
||||||
|
func DecodeVarint(b []byte) (uint64, int) {
|
||||||
|
v, n := protowire.ConsumeVarint(b)
|
||||||
|
if n < 0 {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
return v, n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer is a buffer for encoding and decoding the protobuf wire format.
|
||||||
|
// It may be reused between invocations to reduce memory usage.
|
||||||
|
type Buffer struct {
|
||||||
|
buf []byte
|
||||||
|
idx int
|
||||||
|
deterministic bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuffer allocates a new Buffer initialized with buf,
|
||||||
|
// where the contents of buf are considered the unread portion of the buffer.
|
||||||
|
func NewBuffer(buf []byte) *Buffer {
|
||||||
|
return &Buffer{buf: buf}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeterministic specifies whether to use deterministic serialization.
|
||||||
|
//
|
||||||
|
// Deterministic serialization guarantees that for a given binary, equal
|
||||||
|
// messages will always be serialized to the same bytes. This implies:
|
||||||
|
//
|
||||||
|
// - Repeated serialization of a message will return the same bytes.
|
||||||
|
// - Different processes of the same binary (which may be executing on
|
||||||
|
// different machines) will serialize equal messages to the same bytes.
|
||||||
|
//
|
||||||
|
// Note that the deterministic serialization is NOT canonical across
|
||||||
|
// languages. It is not guaranteed to remain stable over time. It is unstable
|
||||||
|
// across different builds with schema changes due to unknown fields.
|
||||||
|
// Users who need canonical serialization (e.g., persistent storage in a
|
||||||
|
// canonical form, fingerprinting, etc.) should define their own
|
||||||
|
// canonicalization specification and implement their own serializer rather
|
||||||
|
// than relying on this API.
|
||||||
|
//
|
||||||
|
// If deterministic serialization is requested, map entries will be sorted
|
||||||
|
// by keys in lexographical order. This is an implementation detail and
|
||||||
|
// subject to change.
|
||||||
|
func (b *Buffer) SetDeterministic(deterministic bool) {
|
||||||
|
b.deterministic = deterministic
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBuf sets buf as the internal buffer,
|
||||||
|
// where the contents of buf are considered the unread portion of the buffer.
|
||||||
|
func (b *Buffer) SetBuf(buf []byte) {
|
||||||
|
b.buf = buf
|
||||||
|
b.idx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset clears the internal buffer of all written and unread data.
|
||||||
|
func (b *Buffer) Reset() {
|
||||||
|
b.buf = b.buf[:0]
|
||||||
|
b.idx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the internal buffer.
|
||||||
|
func (b *Buffer) Bytes() []byte {
|
||||||
|
return b.buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unread returns the unread portion of the buffer.
|
||||||
|
func (b *Buffer) Unread() []byte {
|
||||||
|
return b.buf[b.idx:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal appends the wire-format encoding of m to the buffer.
|
||||||
|
func (b *Buffer) Marshal(m Message) error {
|
||||||
|
var err error
|
||||||
|
b.buf, err = marshalAppend(b.buf, m, b.deterministic)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal parses the wire-format message in the buffer and
|
||||||
|
// places the decoded results in m.
|
||||||
|
// It does not reset m before unmarshaling.
|
||||||
|
func (b *Buffer) Unmarshal(m Message) error {
|
||||||
|
err := UnmarshalMerge(b.Unread(), m)
|
||||||
|
b.idx = len(b.buf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type unknownFields struct{ XXX_unrecognized protoimpl.UnknownFields }
|
||||||
|
|
||||||
|
func (m *unknownFields) String() string { panic("not implemented") }
|
||||||
|
func (m *unknownFields) Reset() { panic("not implemented") }
|
||||||
|
func (m *unknownFields) ProtoMessage() { panic("not implemented") }
|
||||||
|
|
||||||
|
// DebugPrint dumps the encoded bytes of b with a header and footer including s
|
||||||
|
// to stdout. This is only intended for debugging.
|
||||||
|
func (*Buffer) DebugPrint(s string, b []byte) {
|
||||||
|
m := MessageReflect(new(unknownFields))
|
||||||
|
m.SetUnknown(b)
|
||||||
|
b, _ = prototext.MarshalOptions{AllowPartial: true, Indent: "\t"}.Marshal(m.Interface())
|
||||||
|
fmt.Printf("==== %s ====\n%s==== %s ====\n", s, b, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeVarint appends an unsigned varint encoding to the buffer.
|
||||||
|
func (b *Buffer) EncodeVarint(v uint64) error {
|
||||||
|
b.buf = protowire.AppendVarint(b.buf, v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeZigzag32 appends a 32-bit zig-zag varint encoding to the buffer.
|
||||||
|
func (b *Buffer) EncodeZigzag32(v uint64) error {
|
||||||
|
return b.EncodeVarint(uint64((uint32(v) << 1) ^ uint32((int32(v) >> 31))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeZigzag64 appends a 64-bit zig-zag varint encoding to the buffer.
|
||||||
|
func (b *Buffer) EncodeZigzag64(v uint64) error {
|
||||||
|
return b.EncodeVarint(uint64((uint64(v) << 1) ^ uint64((int64(v) >> 63))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeFixed32 appends a 32-bit little-endian integer to the buffer.
|
||||||
|
func (b *Buffer) EncodeFixed32(v uint64) error {
|
||||||
|
b.buf = protowire.AppendFixed32(b.buf, uint32(v))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeFixed64 appends a 64-bit little-endian integer to the buffer.
|
||||||
|
func (b *Buffer) EncodeFixed64(v uint64) error {
|
||||||
|
b.buf = protowire.AppendFixed64(b.buf, uint64(v))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeRawBytes appends a length-prefixed raw bytes to the buffer.
|
||||||
|
func (b *Buffer) EncodeRawBytes(v []byte) error {
|
||||||
|
b.buf = protowire.AppendBytes(b.buf, v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeStringBytes appends a length-prefixed raw bytes to the buffer.
|
||||||
|
// It does not validate whether v contains valid UTF-8.
|
||||||
|
func (b *Buffer) EncodeStringBytes(v string) error {
|
||||||
|
b.buf = protowire.AppendString(b.buf, v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMessage appends a length-prefixed encoded message to the buffer.
|
||||||
|
func (b *Buffer) EncodeMessage(m Message) error {
|
||||||
|
var err error
|
||||||
|
b.buf = protowire.AppendVarint(b.buf, uint64(Size(m)))
|
||||||
|
b.buf, err = marshalAppend(b.buf, m, b.deterministic)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeVarint consumes an encoded unsigned varint from the buffer.
|
||||||
|
func (b *Buffer) DecodeVarint() (uint64, error) {
|
||||||
|
v, n := protowire.ConsumeVarint(b.buf[b.idx:])
|
||||||
|
if n < 0 {
|
||||||
|
return 0, protowire.ParseError(n)
|
||||||
|
}
|
||||||
|
b.idx += n
|
||||||
|
return uint64(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeZigzag32 consumes an encoded 32-bit zig-zag varint from the buffer.
|
||||||
|
func (b *Buffer) DecodeZigzag32() (uint64, error) {
|
||||||
|
v, err := b.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint64((uint32(v) >> 1) ^ uint32((int32(v&1)<<31)>>31)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeZigzag64 consumes an encoded 64-bit zig-zag varint from the buffer.
|
||||||
|
func (b *Buffer) DecodeZigzag64() (uint64, error) {
|
||||||
|
v, err := b.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint64((uint64(v) >> 1) ^ uint64((int64(v&1)<<63)>>63)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeFixed32 consumes a 32-bit little-endian integer from the buffer.
|
||||||
|
func (b *Buffer) DecodeFixed32() (uint64, error) {
|
||||||
|
v, n := protowire.ConsumeFixed32(b.buf[b.idx:])
|
||||||
|
if n < 0 {
|
||||||
|
return 0, protowire.ParseError(n)
|
||||||
|
}
|
||||||
|
b.idx += n
|
||||||
|
return uint64(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeFixed64 consumes a 64-bit little-endian integer from the buffer.
|
||||||
|
func (b *Buffer) DecodeFixed64() (uint64, error) {
|
||||||
|
v, n := protowire.ConsumeFixed64(b.buf[b.idx:])
|
||||||
|
if n < 0 {
|
||||||
|
return 0, protowire.ParseError(n)
|
||||||
|
}
|
||||||
|
b.idx += n
|
||||||
|
return uint64(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeRawBytes consumes a length-prefixed raw bytes from the buffer.
|
||||||
|
// If alloc is specified, it returns a copy the raw bytes
|
||||||
|
// rather than a sub-slice of the buffer.
|
||||||
|
func (b *Buffer) DecodeRawBytes(alloc bool) ([]byte, error) {
|
||||||
|
v, n := protowire.ConsumeBytes(b.buf[b.idx:])
|
||||||
|
if n < 0 {
|
||||||
|
return nil, protowire.ParseError(n)
|
||||||
|
}
|
||||||
|
b.idx += n
|
||||||
|
if alloc {
|
||||||
|
v = append([]byte(nil), v...)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeStringBytes consumes a length-prefixed raw bytes from the buffer.
|
||||||
|
// It does not validate whether the raw bytes contain valid UTF-8.
|
||||||
|
func (b *Buffer) DecodeStringBytes() (string, error) {
|
||||||
|
v, n := protowire.ConsumeString(b.buf[b.idx:])
|
||||||
|
if n < 0 {
|
||||||
|
return "", protowire.ParseError(n)
|
||||||
|
}
|
||||||
|
b.idx += n
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMessage consumes a length-prefixed message from the buffer.
|
||||||
|
// It does not reset m before unmarshaling.
|
||||||
|
func (b *Buffer) DecodeMessage(m Message) error {
|
||||||
|
v, err := b.DecodeRawBytes(false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return UnmarshalMerge(v, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeGroup consumes a message group from the buffer.
|
||||||
|
// It assumes that the start group marker has already been consumed and
|
||||||
|
// consumes all bytes until (and including the end group marker).
|
||||||
|
// It does not reset m before unmarshaling.
|
||||||
|
func (b *Buffer) DecodeGroup(m Message) error {
|
||||||
|
v, n, err := consumeGroup(b.buf[b.idx:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.idx += n
|
||||||
|
return UnmarshalMerge(v, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// consumeGroup parses b until it finds an end group marker, returning
|
||||||
|
// the raw bytes of the message (excluding the end group marker) and the
|
||||||
|
// the total length of the message (including the end group marker).
|
||||||
|
func consumeGroup(b []byte) ([]byte, int, error) {
|
||||||
|
b0 := b
|
||||||
|
depth := 1 // assume this follows a start group marker
|
||||||
|
for {
|
||||||
|
_, wtyp, tagLen := protowire.ConsumeTag(b)
|
||||||
|
if tagLen < 0 {
|
||||||
|
return nil, 0, protowire.ParseError(tagLen)
|
||||||
|
}
|
||||||
|
b = b[tagLen:]
|
||||||
|
|
||||||
|
var valLen int
|
||||||
|
switch wtyp {
|
||||||
|
case protowire.VarintType:
|
||||||
|
_, valLen = protowire.ConsumeVarint(b)
|
||||||
|
case protowire.Fixed32Type:
|
||||||
|
_, valLen = protowire.ConsumeFixed32(b)
|
||||||
|
case protowire.Fixed64Type:
|
||||||
|
_, valLen = protowire.ConsumeFixed64(b)
|
||||||
|
case protowire.BytesType:
|
||||||
|
_, valLen = protowire.ConsumeBytes(b)
|
||||||
|
case protowire.StartGroupType:
|
||||||
|
depth++
|
||||||
|
case protowire.EndGroupType:
|
||||||
|
depth--
|
||||||
|
default:
|
||||||
|
return nil, 0, errors.New("proto: cannot parse reserved wire type")
|
||||||
|
}
|
||||||
|
if valLen < 0 {
|
||||||
|
return nil, 0, protowire.ParseError(valLen)
|
||||||
|
}
|
||||||
|
b = b[valLen:]
|
||||||
|
|
||||||
|
if depth == 0 {
|
||||||
|
return b0[:len(b0)-len(b)-tagLen], len(b0) - len(b), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
63
vendor/github.com/golang/protobuf/proto/defaults.go
generated
vendored
Normal file
63
vendor/github.com/golang/protobuf/proto/defaults.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetDefaults sets unpopulated scalar fields to their default values.
|
||||||
|
// Fields within a oneof are not set even if they have a default value.
|
||||||
|
// SetDefaults is recursively called upon any populated message fields.
|
||||||
|
func SetDefaults(m Message) {
|
||||||
|
if m != nil {
|
||||||
|
setDefaults(MessageReflect(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setDefaults(m protoreflect.Message) {
|
||||||
|
fds := m.Descriptor().Fields()
|
||||||
|
for i := 0; i < fds.Len(); i++ {
|
||||||
|
fd := fds.Get(i)
|
||||||
|
if !m.Has(fd) {
|
||||||
|
if fd.HasDefault() && fd.ContainingOneof() == nil {
|
||||||
|
v := fd.Default()
|
||||||
|
if fd.Kind() == protoreflect.BytesKind {
|
||||||
|
v = protoreflect.ValueOf(append([]byte(nil), v.Bytes()...)) // copy the default bytes
|
||||||
|
}
|
||||||
|
m.Set(fd, v)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
|
||||||
|
switch {
|
||||||
|
// Handle singular message.
|
||||||
|
case fd.Cardinality() != protoreflect.Repeated:
|
||||||
|
if fd.Message() != nil {
|
||||||
|
setDefaults(m.Get(fd).Message())
|
||||||
|
}
|
||||||
|
// Handle list of messages.
|
||||||
|
case fd.IsList():
|
||||||
|
if fd.Message() != nil {
|
||||||
|
ls := m.Get(fd).List()
|
||||||
|
for i := 0; i < ls.Len(); i++ {
|
||||||
|
setDefaults(ls.Get(i).Message())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Handle map of messages.
|
||||||
|
case fd.IsMap():
|
||||||
|
if fd.MapValue().Message() != nil {
|
||||||
|
ms := m.Get(fd).Map()
|
||||||
|
ms.Range(func(_ protoreflect.MapKey, v protoreflect.Value) bool {
|
||||||
|
setDefaults(v.Message())
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
92
vendor/github.com/golang/protobuf/proto/deprecated.go
generated
vendored
Normal file
92
vendor/github.com/golang/protobuf/proto/deprecated.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Deprecated: No longer returned.
|
||||||
|
ErrNil = errors.New("proto: Marshal called with nil")
|
||||||
|
|
||||||
|
// Deprecated: No longer returned.
|
||||||
|
ErrTooLarge = errors.New("proto: message encodes to over 2 GB")
|
||||||
|
|
||||||
|
// Deprecated: No longer returned.
|
||||||
|
ErrInternalBadWireType = errors.New("proto: internal error: bad wiretype for oneof")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
type Stats struct{ Emalloc, Dmalloc, Encode, Decode, Chit, Cmiss, Size uint64 }
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func GetStats() Stats { return Stats{} }
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func MarshalMessageSet(interface{}) ([]byte, error) {
|
||||||
|
return nil, errors.New("proto: not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func UnmarshalMessageSet([]byte, interface{}) error {
|
||||||
|
return errors.New("proto: not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func MarshalMessageSetJSON(interface{}) ([]byte, error) {
|
||||||
|
return nil, errors.New("proto: not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func UnmarshalMessageSetJSON([]byte, interface{}) error {
|
||||||
|
return errors.New("proto: not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func RegisterMessageSetType(Message, int32, string) {}
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func EnumName(m map[int32]string, v int32) string {
|
||||||
|
s, ok := m[v]
|
||||||
|
if ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return strconv.Itoa(int(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func UnmarshalJSONEnum(m map[string]int32, data []byte, enumName string) (int32, error) {
|
||||||
|
if data[0] == '"' {
|
||||||
|
// New style: enums are strings.
|
||||||
|
var repr string
|
||||||
|
if err := json.Unmarshal(data, &repr); err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
val, ok := m[repr]
|
||||||
|
if !ok {
|
||||||
|
return 0, fmt.Errorf("unrecognized enum %s value %q", enumName, repr)
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
// Old style: enums are ints.
|
||||||
|
var val int32
|
||||||
|
if err := json.Unmarshal(data, &val); err != nil {
|
||||||
|
return 0, fmt.Errorf("cannot unmarshal %#q into enum %s", data, enumName)
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
type InternalMessageInfo struct{}
|
||||||
|
|
||||||
|
func (*InternalMessageInfo) DiscardUnknown(Message) { panic("not implemented") }
|
||||||
|
func (*InternalMessageInfo) Marshal([]byte, Message, bool) ([]byte, error) { panic("not implemented") }
|
||||||
|
func (*InternalMessageInfo) Merge(Message, Message) { panic("not implemented") }
|
||||||
|
func (*InternalMessageInfo) Size(Message) int { panic("not implemented") }
|
||||||
|
func (*InternalMessageInfo) Unmarshal(Message, []byte) error { panic("not implemented") }
|
||||||
58
vendor/github.com/golang/protobuf/proto/discard.go
generated
vendored
Normal file
58
vendor/github.com/golang/protobuf/proto/discard.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DiscardUnknown recursively discards all unknown fields from this message
|
||||||
|
// and all embedded messages.
|
||||||
|
//
|
||||||
|
// When unmarshaling a message with unrecognized fields, the tags and values
|
||||||
|
// of such fields are preserved in the Message. This allows a later call to
|
||||||
|
// marshal to be able to produce a message that continues to have those
|
||||||
|
// unrecognized fields. To avoid this, DiscardUnknown is used to
|
||||||
|
// explicitly clear the unknown fields after unmarshaling.
|
||||||
|
func DiscardUnknown(m Message) {
|
||||||
|
if m != nil {
|
||||||
|
discardUnknown(MessageReflect(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func discardUnknown(m protoreflect.Message) {
|
||||||
|
m.Range(func(fd protoreflect.FieldDescriptor, val protoreflect.Value) bool {
|
||||||
|
switch {
|
||||||
|
// Handle singular message.
|
||||||
|
case fd.Cardinality() != protoreflect.Repeated:
|
||||||
|
if fd.Message() != nil {
|
||||||
|
discardUnknown(m.Get(fd).Message())
|
||||||
|
}
|
||||||
|
// Handle list of messages.
|
||||||
|
case fd.IsList():
|
||||||
|
if fd.Message() != nil {
|
||||||
|
ls := m.Get(fd).List()
|
||||||
|
for i := 0; i < ls.Len(); i++ {
|
||||||
|
discardUnknown(ls.Get(i).Message())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Handle map of messages.
|
||||||
|
case fd.IsMap():
|
||||||
|
if fd.MapValue().Message() != nil {
|
||||||
|
ms := m.Get(fd).Map()
|
||||||
|
ms.Range(func(_ protoreflect.MapKey, v protoreflect.Value) bool {
|
||||||
|
discardUnknown(v.Message())
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
// Discard unknown fields.
|
||||||
|
if len(m.GetUnknown()) > 0 {
|
||||||
|
m.SetUnknown(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
356
vendor/github.com/golang/protobuf/proto/extensions.go
generated
vendored
Normal file
356
vendor/github.com/golang/protobuf/proto/extensions.go
generated
vendored
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/encoding/protowire"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
"google.golang.org/protobuf/reflect/protoregistry"
|
||||||
|
"google.golang.org/protobuf/runtime/protoiface"
|
||||||
|
"google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// ExtensionDesc represents an extension descriptor and
|
||||||
|
// is used to interact with an extension field in a message.
|
||||||
|
//
|
||||||
|
// Variables of this type are generated in code by protoc-gen-go.
|
||||||
|
ExtensionDesc = protoimpl.ExtensionInfo
|
||||||
|
|
||||||
|
// ExtensionRange represents a range of message extensions.
|
||||||
|
// Used in code generated by protoc-gen-go.
|
||||||
|
ExtensionRange = protoiface.ExtensionRangeV1
|
||||||
|
|
||||||
|
// Deprecated: Do not use; this is an internal type.
|
||||||
|
Extension = protoimpl.ExtensionFieldV1
|
||||||
|
|
||||||
|
// Deprecated: Do not use; this is an internal type.
|
||||||
|
XXX_InternalExtensions = protoimpl.ExtensionFields
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrMissingExtension reports whether the extension was not present.
|
||||||
|
var ErrMissingExtension = errors.New("proto: missing extension")
|
||||||
|
|
||||||
|
var errNotExtendable = errors.New("proto: not an extendable proto.Message")
|
||||||
|
|
||||||
|
// HasExtension reports whether the extension field is present in m
|
||||||
|
// either as an explicitly populated field or as an unknown field.
|
||||||
|
func HasExtension(m Message, xt *ExtensionDesc) (has bool) {
|
||||||
|
mr := MessageReflect(m)
|
||||||
|
if mr == nil || !mr.IsValid() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether any populated known field matches the field number.
|
||||||
|
xtd := xt.TypeDescriptor()
|
||||||
|
if isValidExtension(mr.Descriptor(), xtd) {
|
||||||
|
has = mr.Has(xtd)
|
||||||
|
} else {
|
||||||
|
mr.Range(func(fd protoreflect.FieldDescriptor, _ protoreflect.Value) bool {
|
||||||
|
has = int32(fd.Number()) == xt.Field
|
||||||
|
return !has
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether any unknown field matches the field number.
|
||||||
|
for b := mr.GetUnknown(); !has && len(b) > 0; {
|
||||||
|
num, _, n := protowire.ConsumeField(b)
|
||||||
|
has = int32(num) == xt.Field
|
||||||
|
b = b[n:]
|
||||||
|
}
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearExtension removes the extension field from m
|
||||||
|
// either as an explicitly populated field or as an unknown field.
|
||||||
|
func ClearExtension(m Message, xt *ExtensionDesc) {
|
||||||
|
mr := MessageReflect(m)
|
||||||
|
if mr == nil || !mr.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
xtd := xt.TypeDescriptor()
|
||||||
|
if isValidExtension(mr.Descriptor(), xtd) {
|
||||||
|
mr.Clear(xtd)
|
||||||
|
} else {
|
||||||
|
mr.Range(func(fd protoreflect.FieldDescriptor, _ protoreflect.Value) bool {
|
||||||
|
if int32(fd.Number()) == xt.Field {
|
||||||
|
mr.Clear(fd)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
clearUnknown(mr, fieldNum(xt.Field))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAllExtensions clears all extensions from m.
|
||||||
|
// This includes populated fields and unknown fields in the extension range.
|
||||||
|
func ClearAllExtensions(m Message) {
|
||||||
|
mr := MessageReflect(m)
|
||||||
|
if mr == nil || !mr.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mr.Range(func(fd protoreflect.FieldDescriptor, _ protoreflect.Value) bool {
|
||||||
|
if fd.IsExtension() {
|
||||||
|
mr.Clear(fd)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
clearUnknown(mr, mr.Descriptor().ExtensionRanges())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExtension retrieves a proto2 extended field from m.
|
||||||
|
//
|
||||||
|
// If the descriptor is type complete (i.e., ExtensionDesc.ExtensionType is non-nil),
|
||||||
|
// then GetExtension parses the encoded field and returns a Go value of the specified type.
|
||||||
|
// If the field is not present, then the default value is returned (if one is specified),
|
||||||
|
// otherwise ErrMissingExtension is reported.
|
||||||
|
//
|
||||||
|
// If the descriptor is type incomplete (i.e., ExtensionDesc.ExtensionType is nil),
|
||||||
|
// then GetExtension returns the raw encoded bytes for the extension field.
|
||||||
|
func GetExtension(m Message, xt *ExtensionDesc) (interface{}, error) {
|
||||||
|
mr := MessageReflect(m)
|
||||||
|
if mr == nil || !mr.IsValid() || mr.Descriptor().ExtensionRanges().Len() == 0 {
|
||||||
|
return nil, errNotExtendable
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the unknown fields for this extension field.
|
||||||
|
var bo protoreflect.RawFields
|
||||||
|
for bi := mr.GetUnknown(); len(bi) > 0; {
|
||||||
|
num, _, n := protowire.ConsumeField(bi)
|
||||||
|
if int32(num) == xt.Field {
|
||||||
|
bo = append(bo, bi[:n]...)
|
||||||
|
}
|
||||||
|
bi = bi[n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// For type incomplete descriptors, only retrieve the unknown fields.
|
||||||
|
if xt.ExtensionType == nil {
|
||||||
|
return []byte(bo), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the extension field only exists as unknown fields, unmarshal it.
|
||||||
|
// This is rarely done since proto.Unmarshal eagerly unmarshals extensions.
|
||||||
|
xtd := xt.TypeDescriptor()
|
||||||
|
if !isValidExtension(mr.Descriptor(), xtd) {
|
||||||
|
return nil, fmt.Errorf("proto: bad extended type; %T does not extend %T", xt.ExtendedType, m)
|
||||||
|
}
|
||||||
|
if !mr.Has(xtd) && len(bo) > 0 {
|
||||||
|
m2 := mr.New()
|
||||||
|
if err := (proto.UnmarshalOptions{
|
||||||
|
Resolver: extensionResolver{xt},
|
||||||
|
}.Unmarshal(bo, m2.Interface())); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if m2.Has(xtd) {
|
||||||
|
mr.Set(xtd, m2.Get(xtd))
|
||||||
|
clearUnknown(mr, fieldNum(xt.Field))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the message has the extension field set or a default.
|
||||||
|
var pv protoreflect.Value
|
||||||
|
switch {
|
||||||
|
case mr.Has(xtd):
|
||||||
|
pv = mr.Get(xtd)
|
||||||
|
case xtd.HasDefault():
|
||||||
|
pv = xtd.Default()
|
||||||
|
default:
|
||||||
|
return nil, ErrMissingExtension
|
||||||
|
}
|
||||||
|
|
||||||
|
v := xt.InterfaceOf(pv)
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if isScalarKind(rv.Kind()) {
|
||||||
|
rv2 := reflect.New(rv.Type())
|
||||||
|
rv2.Elem().Set(rv)
|
||||||
|
v = rv2.Interface()
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extensionResolver is a custom extension resolver that stores a single
|
||||||
|
// extension type that takes precedence over the global registry.
|
||||||
|
type extensionResolver struct{ xt protoreflect.ExtensionType }
|
||||||
|
|
||||||
|
func (r extensionResolver) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) {
|
||||||
|
if xtd := r.xt.TypeDescriptor(); xtd.FullName() == field {
|
||||||
|
return r.xt, nil
|
||||||
|
}
|
||||||
|
return protoregistry.GlobalTypes.FindExtensionByName(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r extensionResolver) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) {
|
||||||
|
if xtd := r.xt.TypeDescriptor(); xtd.ContainingMessage().FullName() == message && xtd.Number() == field {
|
||||||
|
return r.xt, nil
|
||||||
|
}
|
||||||
|
return protoregistry.GlobalTypes.FindExtensionByNumber(message, field)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExtensions returns a list of the extensions values present in m,
|
||||||
|
// corresponding with the provided list of extension descriptors, xts.
|
||||||
|
// If an extension is missing in m, the corresponding value is nil.
|
||||||
|
func GetExtensions(m Message, xts []*ExtensionDesc) ([]interface{}, error) {
|
||||||
|
mr := MessageReflect(m)
|
||||||
|
if mr == nil || !mr.IsValid() {
|
||||||
|
return nil, errNotExtendable
|
||||||
|
}
|
||||||
|
|
||||||
|
vs := make([]interface{}, len(xts))
|
||||||
|
for i, xt := range xts {
|
||||||
|
v, err := GetExtension(m, xt)
|
||||||
|
if err != nil {
|
||||||
|
if err == ErrMissingExtension {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return vs, err
|
||||||
|
}
|
||||||
|
vs[i] = v
|
||||||
|
}
|
||||||
|
return vs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetExtension sets an extension field in m to the provided value.
|
||||||
|
func SetExtension(m Message, xt *ExtensionDesc, v interface{}) error {
|
||||||
|
mr := MessageReflect(m)
|
||||||
|
if mr == nil || !mr.IsValid() || mr.Descriptor().ExtensionRanges().Len() == 0 {
|
||||||
|
return errNotExtendable
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if reflect.TypeOf(v) != reflect.TypeOf(xt.ExtensionType) {
|
||||||
|
return fmt.Errorf("proto: bad extension value type. got: %T, want: %T", v, xt.ExtensionType)
|
||||||
|
}
|
||||||
|
if rv.Kind() == reflect.Ptr {
|
||||||
|
if rv.IsNil() {
|
||||||
|
return fmt.Errorf("proto: SetExtension called with nil value of type %T", v)
|
||||||
|
}
|
||||||
|
if isScalarKind(rv.Elem().Kind()) {
|
||||||
|
v = rv.Elem().Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xtd := xt.TypeDescriptor()
|
||||||
|
if !isValidExtension(mr.Descriptor(), xtd) {
|
||||||
|
return fmt.Errorf("proto: bad extended type; %T does not extend %T", xt.ExtendedType, m)
|
||||||
|
}
|
||||||
|
mr.Set(xtd, xt.ValueOf(v))
|
||||||
|
clearUnknown(mr, fieldNum(xt.Field))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRawExtension inserts b into the unknown fields of m.
|
||||||
|
//
|
||||||
|
// Deprecated: Use Message.ProtoReflect.SetUnknown instead.
|
||||||
|
func SetRawExtension(m Message, fnum int32, b []byte) {
|
||||||
|
mr := MessageReflect(m)
|
||||||
|
if mr == nil || !mr.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the raw field is valid.
|
||||||
|
for b0 := b; len(b0) > 0; {
|
||||||
|
num, _, n := protowire.ConsumeField(b0)
|
||||||
|
if int32(num) != fnum {
|
||||||
|
panic(fmt.Sprintf("mismatching field number: got %d, want %d", num, fnum))
|
||||||
|
}
|
||||||
|
b0 = b0[n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
ClearExtension(m, &ExtensionDesc{Field: fnum})
|
||||||
|
mr.SetUnknown(append(mr.GetUnknown(), b...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtensionDescs returns a list of extension descriptors found in m,
|
||||||
|
// containing descriptors for both populated extension fields in m and
|
||||||
|
// also unknown fields of m that are in the extension range.
|
||||||
|
// For the later case, an type incomplete descriptor is provided where only
|
||||||
|
// the ExtensionDesc.Field field is populated.
|
||||||
|
// The order of the extension descriptors is undefined.
|
||||||
|
func ExtensionDescs(m Message) ([]*ExtensionDesc, error) {
|
||||||
|
mr := MessageReflect(m)
|
||||||
|
if mr == nil || !mr.IsValid() || mr.Descriptor().ExtensionRanges().Len() == 0 {
|
||||||
|
return nil, errNotExtendable
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect a set of known extension descriptors.
|
||||||
|
extDescs := make(map[protoreflect.FieldNumber]*ExtensionDesc)
|
||||||
|
mr.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
|
||||||
|
if fd.IsExtension() {
|
||||||
|
xt := fd.(protoreflect.ExtensionTypeDescriptor)
|
||||||
|
if xd, ok := xt.Type().(*ExtensionDesc); ok {
|
||||||
|
extDescs[fd.Number()] = xd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
// Collect a set of unknown extension descriptors.
|
||||||
|
extRanges := mr.Descriptor().ExtensionRanges()
|
||||||
|
for b := mr.GetUnknown(); len(b) > 0; {
|
||||||
|
num, _, n := protowire.ConsumeField(b)
|
||||||
|
if extRanges.Has(num) && extDescs[num] == nil {
|
||||||
|
extDescs[num] = nil
|
||||||
|
}
|
||||||
|
b = b[n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transpose the set of descriptors into a list.
|
||||||
|
var xts []*ExtensionDesc
|
||||||
|
for num, xt := range extDescs {
|
||||||
|
if xt == nil {
|
||||||
|
xt = &ExtensionDesc{Field: int32(num)}
|
||||||
|
}
|
||||||
|
xts = append(xts, xt)
|
||||||
|
}
|
||||||
|
return xts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidExtension reports whether xtd is a valid extension descriptor for md.
|
||||||
|
func isValidExtension(md protoreflect.MessageDescriptor, xtd protoreflect.ExtensionTypeDescriptor) bool {
|
||||||
|
return xtd.ContainingMessage() == md && md.ExtensionRanges().Has(xtd.Number())
|
||||||
|
}
|
||||||
|
|
||||||
|
// isScalarKind reports whether k is a protobuf scalar kind (except bytes).
|
||||||
|
// This function exists for historical reasons since the representation of
|
||||||
|
// scalars differs between v1 and v2, where v1 uses *T and v2 uses T.
|
||||||
|
func isScalarKind(k reflect.Kind) bool {
|
||||||
|
switch k {
|
||||||
|
case reflect.Bool, reflect.Int32, reflect.Int64, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clearUnknown removes unknown fields from m where remover.Has reports true.
|
||||||
|
func clearUnknown(m protoreflect.Message, remover interface {
|
||||||
|
Has(protoreflect.FieldNumber) bool
|
||||||
|
}) {
|
||||||
|
var bo protoreflect.RawFields
|
||||||
|
for bi := m.GetUnknown(); len(bi) > 0; {
|
||||||
|
num, _, n := protowire.ConsumeField(bi)
|
||||||
|
if !remover.Has(num) {
|
||||||
|
bo = append(bo, bi[:n]...)
|
||||||
|
}
|
||||||
|
bi = bi[n:]
|
||||||
|
}
|
||||||
|
if bi := m.GetUnknown(); len(bi) != len(bo) {
|
||||||
|
m.SetUnknown(bo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fieldNum protoreflect.FieldNumber
|
||||||
|
|
||||||
|
func (n1 fieldNum) Has(n2 protoreflect.FieldNumber) bool {
|
||||||
|
return protoreflect.FieldNumber(n1) == n2
|
||||||
|
}
|
||||||
306
vendor/github.com/golang/protobuf/proto/properties.go
generated
vendored
Normal file
306
vendor/github.com/golang/protobuf/proto/properties.go
generated
vendored
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
"google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StructProperties represents protocol buffer type information for a
|
||||||
|
// generated protobuf message in the open-struct API.
|
||||||
|
//
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
type StructProperties struct {
|
||||||
|
// Prop are the properties for each field.
|
||||||
|
//
|
||||||
|
// Fields belonging to a oneof are stored in OneofTypes instead, with a
|
||||||
|
// single Properties representing the parent oneof held here.
|
||||||
|
//
|
||||||
|
// The order of Prop matches the order of fields in the Go struct.
|
||||||
|
// Struct fields that are not related to protobufs have a "XXX_" prefix
|
||||||
|
// in the Properties.Name and must be ignored by the user.
|
||||||
|
Prop []*Properties
|
||||||
|
|
||||||
|
// OneofTypes contains information about the oneof fields in this message.
|
||||||
|
// It is keyed by the protobuf field name.
|
||||||
|
OneofTypes map[string]*OneofProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properties represents the type information for a protobuf message field.
|
||||||
|
//
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
type Properties struct {
|
||||||
|
// Name is a placeholder name with little meaningful semantic value.
|
||||||
|
// If the name has an "XXX_" prefix, the entire Properties must be ignored.
|
||||||
|
Name string
|
||||||
|
// OrigName is the protobuf field name or oneof name.
|
||||||
|
OrigName string
|
||||||
|
// JSONName is the JSON name for the protobuf field.
|
||||||
|
JSONName string
|
||||||
|
// Enum is a placeholder name for enums.
|
||||||
|
// For historical reasons, this is neither the Go name for the enum,
|
||||||
|
// nor the protobuf name for the enum.
|
||||||
|
Enum string // Deprecated: Do not use.
|
||||||
|
// Weak contains the full name of the weakly referenced message.
|
||||||
|
Weak string
|
||||||
|
// Wire is a string representation of the wire type.
|
||||||
|
Wire string
|
||||||
|
// WireType is the protobuf wire type for the field.
|
||||||
|
WireType int
|
||||||
|
// Tag is the protobuf field number.
|
||||||
|
Tag int
|
||||||
|
// Required reports whether this is a required field.
|
||||||
|
Required bool
|
||||||
|
// Optional reports whether this is a optional field.
|
||||||
|
Optional bool
|
||||||
|
// Repeated reports whether this is a repeated field.
|
||||||
|
Repeated bool
|
||||||
|
// Packed reports whether this is a packed repeated field of scalars.
|
||||||
|
Packed bool
|
||||||
|
// Proto3 reports whether this field operates under the proto3 syntax.
|
||||||
|
Proto3 bool
|
||||||
|
// Oneof reports whether this field belongs within a oneof.
|
||||||
|
Oneof bool
|
||||||
|
|
||||||
|
// Default is the default value in string form.
|
||||||
|
Default string
|
||||||
|
// HasDefault reports whether the field has a default value.
|
||||||
|
HasDefault bool
|
||||||
|
|
||||||
|
// MapKeyProp is the properties for the key field for a map field.
|
||||||
|
MapKeyProp *Properties
|
||||||
|
// MapValProp is the properties for the value field for a map field.
|
||||||
|
MapValProp *Properties
|
||||||
|
}
|
||||||
|
|
||||||
|
// OneofProperties represents the type information for a protobuf oneof.
|
||||||
|
//
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
type OneofProperties struct {
|
||||||
|
// Type is a pointer to the generated wrapper type for the field value.
|
||||||
|
// This is nil for messages that are not in the open-struct API.
|
||||||
|
Type reflect.Type
|
||||||
|
// Field is the index into StructProperties.Prop for the containing oneof.
|
||||||
|
Field int
|
||||||
|
// Prop is the properties for the field.
|
||||||
|
Prop *Properties
|
||||||
|
}
|
||||||
|
|
||||||
|
// String formats the properties in the protobuf struct field tag style.
|
||||||
|
func (p *Properties) String() string {
|
||||||
|
s := p.Wire
|
||||||
|
s += "," + strconv.Itoa(p.Tag)
|
||||||
|
if p.Required {
|
||||||
|
s += ",req"
|
||||||
|
}
|
||||||
|
if p.Optional {
|
||||||
|
s += ",opt"
|
||||||
|
}
|
||||||
|
if p.Repeated {
|
||||||
|
s += ",rep"
|
||||||
|
}
|
||||||
|
if p.Packed {
|
||||||
|
s += ",packed"
|
||||||
|
}
|
||||||
|
s += ",name=" + p.OrigName
|
||||||
|
if p.JSONName != "" {
|
||||||
|
s += ",json=" + p.JSONName
|
||||||
|
}
|
||||||
|
if len(p.Enum) > 0 {
|
||||||
|
s += ",enum=" + p.Enum
|
||||||
|
}
|
||||||
|
if len(p.Weak) > 0 {
|
||||||
|
s += ",weak=" + p.Weak
|
||||||
|
}
|
||||||
|
if p.Proto3 {
|
||||||
|
s += ",proto3"
|
||||||
|
}
|
||||||
|
if p.Oneof {
|
||||||
|
s += ",oneof"
|
||||||
|
}
|
||||||
|
if p.HasDefault {
|
||||||
|
s += ",def=" + p.Default
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse populates p by parsing a string in the protobuf struct field tag style.
|
||||||
|
func (p *Properties) Parse(tag string) {
|
||||||
|
// For example: "bytes,49,opt,name=foo,def=hello!"
|
||||||
|
for len(tag) > 0 {
|
||||||
|
i := strings.IndexByte(tag, ',')
|
||||||
|
if i < 0 {
|
||||||
|
i = len(tag)
|
||||||
|
}
|
||||||
|
switch s := tag[:i]; {
|
||||||
|
case strings.HasPrefix(s, "name="):
|
||||||
|
p.OrigName = s[len("name="):]
|
||||||
|
case strings.HasPrefix(s, "json="):
|
||||||
|
p.JSONName = s[len("json="):]
|
||||||
|
case strings.HasPrefix(s, "enum="):
|
||||||
|
p.Enum = s[len("enum="):]
|
||||||
|
case strings.HasPrefix(s, "weak="):
|
||||||
|
p.Weak = s[len("weak="):]
|
||||||
|
case strings.Trim(s, "0123456789") == "":
|
||||||
|
n, _ := strconv.ParseUint(s, 10, 32)
|
||||||
|
p.Tag = int(n)
|
||||||
|
case s == "opt":
|
||||||
|
p.Optional = true
|
||||||
|
case s == "req":
|
||||||
|
p.Required = true
|
||||||
|
case s == "rep":
|
||||||
|
p.Repeated = true
|
||||||
|
case s == "varint" || s == "zigzag32" || s == "zigzag64":
|
||||||
|
p.Wire = s
|
||||||
|
p.WireType = WireVarint
|
||||||
|
case s == "fixed32":
|
||||||
|
p.Wire = s
|
||||||
|
p.WireType = WireFixed32
|
||||||
|
case s == "fixed64":
|
||||||
|
p.Wire = s
|
||||||
|
p.WireType = WireFixed64
|
||||||
|
case s == "bytes":
|
||||||
|
p.Wire = s
|
||||||
|
p.WireType = WireBytes
|
||||||
|
case s == "group":
|
||||||
|
p.Wire = s
|
||||||
|
p.WireType = WireStartGroup
|
||||||
|
case s == "packed":
|
||||||
|
p.Packed = true
|
||||||
|
case s == "proto3":
|
||||||
|
p.Proto3 = true
|
||||||
|
case s == "oneof":
|
||||||
|
p.Oneof = true
|
||||||
|
case strings.HasPrefix(s, "def="):
|
||||||
|
// The default tag is special in that everything afterwards is the
|
||||||
|
// default regardless of the presence of commas.
|
||||||
|
p.HasDefault = true
|
||||||
|
p.Default, i = tag[len("def="):], len(tag)
|
||||||
|
}
|
||||||
|
tag = strings.TrimPrefix(tag[i:], ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init populates the properties from a protocol buffer struct tag.
|
||||||
|
//
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func (p *Properties) Init(typ reflect.Type, name, tag string, f *reflect.StructField) {
|
||||||
|
p.Name = name
|
||||||
|
p.OrigName = name
|
||||||
|
if tag == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Parse(tag)
|
||||||
|
|
||||||
|
if typ != nil && typ.Kind() == reflect.Map {
|
||||||
|
p.MapKeyProp = new(Properties)
|
||||||
|
p.MapKeyProp.Init(nil, "Key", f.Tag.Get("protobuf_key"), nil)
|
||||||
|
p.MapValProp = new(Properties)
|
||||||
|
p.MapValProp.Init(nil, "Value", f.Tag.Get("protobuf_val"), nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var propertiesCache sync.Map // map[reflect.Type]*StructProperties
|
||||||
|
|
||||||
|
// GetProperties returns the list of properties for the type represented by t,
|
||||||
|
// which must be a generated protocol buffer message in the open-struct API,
|
||||||
|
// where protobuf message fields are represented by exported Go struct fields.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protobuf reflection instead.
|
||||||
|
func GetProperties(t reflect.Type) *StructProperties {
|
||||||
|
if p, ok := propertiesCache.Load(t); ok {
|
||||||
|
return p.(*StructProperties)
|
||||||
|
}
|
||||||
|
p, _ := propertiesCache.LoadOrStore(t, newProperties(t))
|
||||||
|
return p.(*StructProperties)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProperties(t reflect.Type) *StructProperties {
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
panic(fmt.Sprintf("%v is not a generated message in the open-struct API", t))
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasOneof bool
|
||||||
|
prop := new(StructProperties)
|
||||||
|
|
||||||
|
// Construct a list of properties for each field in the struct.
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
p := new(Properties)
|
||||||
|
f := t.Field(i)
|
||||||
|
tagField := f.Tag.Get("protobuf")
|
||||||
|
p.Init(f.Type, f.Name, tagField, &f)
|
||||||
|
|
||||||
|
tagOneof := f.Tag.Get("protobuf_oneof")
|
||||||
|
if tagOneof != "" {
|
||||||
|
hasOneof = true
|
||||||
|
p.OrigName = tagOneof
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename unrelated struct fields with the "XXX_" prefix since so much
|
||||||
|
// user code simply checks for this to exclude special fields.
|
||||||
|
if tagField == "" && tagOneof == "" && !strings.HasPrefix(p.Name, "XXX_") {
|
||||||
|
p.Name = "XXX_" + p.Name
|
||||||
|
p.OrigName = "XXX_" + p.OrigName
|
||||||
|
} else if p.Weak != "" {
|
||||||
|
p.Name = p.OrigName // avoid possible "XXX_" prefix on weak field
|
||||||
|
}
|
||||||
|
|
||||||
|
prop.Prop = append(prop.Prop, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct a mapping of oneof field names to properties.
|
||||||
|
if hasOneof {
|
||||||
|
var oneofWrappers []interface{}
|
||||||
|
if fn, ok := reflect.PtrTo(t).MethodByName("XXX_OneofFuncs"); ok {
|
||||||
|
oneofWrappers = fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[3].Interface().([]interface{})
|
||||||
|
}
|
||||||
|
if fn, ok := reflect.PtrTo(t).MethodByName("XXX_OneofWrappers"); ok {
|
||||||
|
oneofWrappers = fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[0].Interface().([]interface{})
|
||||||
|
}
|
||||||
|
if m, ok := reflect.Zero(reflect.PtrTo(t)).Interface().(protoreflect.ProtoMessage); ok {
|
||||||
|
if m, ok := m.ProtoReflect().(interface{ ProtoMessageInfo() *protoimpl.MessageInfo }); ok {
|
||||||
|
oneofWrappers = m.ProtoMessageInfo().OneofWrappers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prop.OneofTypes = make(map[string]*OneofProperties)
|
||||||
|
for _, wrapper := range oneofWrappers {
|
||||||
|
p := &OneofProperties{
|
||||||
|
Type: reflect.ValueOf(wrapper).Type(), // *T
|
||||||
|
Prop: new(Properties),
|
||||||
|
}
|
||||||
|
f := p.Type.Elem().Field(0)
|
||||||
|
p.Prop.Name = f.Name
|
||||||
|
p.Prop.Parse(f.Tag.Get("protobuf"))
|
||||||
|
|
||||||
|
// Determine the struct field that contains this oneof.
|
||||||
|
// Each wrapper is assignable to exactly one parent field.
|
||||||
|
var foundOneof bool
|
||||||
|
for i := 0; i < t.NumField() && !foundOneof; i++ {
|
||||||
|
if p.Type.AssignableTo(t.Field(i).Type) {
|
||||||
|
p.Field = i
|
||||||
|
foundOneof = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundOneof {
|
||||||
|
panic(fmt.Sprintf("%v is not a generated message in the open-struct API", t))
|
||||||
|
}
|
||||||
|
prop.OneofTypes[p.Prop.OrigName] = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prop
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *StructProperties) Len() int { return len(sp.Prop) }
|
||||||
|
func (sp *StructProperties) Less(i, j int) bool { return false }
|
||||||
|
func (sp *StructProperties) Swap(i, j int) { return }
|
||||||
167
vendor/github.com/golang/protobuf/proto/proto.go
generated
vendored
Normal file
167
vendor/github.com/golang/protobuf/proto/proto.go
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package proto provides functionality for handling protocol buffer messages.
|
||||||
|
// In particular, it provides marshaling and unmarshaling between a protobuf
|
||||||
|
// message and the binary wire format.
|
||||||
|
//
|
||||||
|
// See https://developers.google.com/protocol-buffers/docs/gotutorial for
|
||||||
|
// more information.
|
||||||
|
//
|
||||||
|
// Deprecated: Use the "google.golang.org/protobuf/proto" package instead.
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoV2 "google.golang.org/protobuf/proto"
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
"google.golang.org/protobuf/runtime/protoiface"
|
||||||
|
"google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProtoPackageIsVersion1 = true
|
||||||
|
ProtoPackageIsVersion2 = true
|
||||||
|
ProtoPackageIsVersion3 = true
|
||||||
|
ProtoPackageIsVersion4 = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// GeneratedEnum is any enum type generated by protoc-gen-go
|
||||||
|
// which is a named int32 kind.
|
||||||
|
// This type exists for documentation purposes.
|
||||||
|
type GeneratedEnum interface{}
|
||||||
|
|
||||||
|
// GeneratedMessage is any message type generated by protoc-gen-go
|
||||||
|
// which is a pointer to a named struct kind.
|
||||||
|
// This type exists for documentation purposes.
|
||||||
|
type GeneratedMessage interface{}
|
||||||
|
|
||||||
|
// Message is a protocol buffer message.
|
||||||
|
//
|
||||||
|
// This is the v1 version of the message interface and is marginally better
|
||||||
|
// than an empty interface as it lacks any method to programatically interact
|
||||||
|
// with the contents of the message.
|
||||||
|
//
|
||||||
|
// A v2 message is declared in "google.golang.org/protobuf/proto".Message and
|
||||||
|
// exposes protobuf reflection as a first-class feature of the interface.
|
||||||
|
//
|
||||||
|
// To convert a v1 message to a v2 message, use the MessageV2 function.
|
||||||
|
// To convert a v2 message to a v1 message, use the MessageV1 function.
|
||||||
|
type Message = protoiface.MessageV1
|
||||||
|
|
||||||
|
// MessageV1 converts either a v1 or v2 message to a v1 message.
|
||||||
|
// It returns nil if m is nil.
|
||||||
|
func MessageV1(m GeneratedMessage) protoiface.MessageV1 {
|
||||||
|
return protoimpl.X.ProtoMessageV1Of(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageV2 converts either a v1 or v2 message to a v2 message.
|
||||||
|
// It returns nil if m is nil.
|
||||||
|
func MessageV2(m GeneratedMessage) protoV2.Message {
|
||||||
|
return protoimpl.X.ProtoMessageV2Of(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageReflect returns a reflective view for a message.
|
||||||
|
// It returns nil if m is nil.
|
||||||
|
func MessageReflect(m Message) protoreflect.Message {
|
||||||
|
return protoimpl.X.MessageOf(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshaler is implemented by messages that can marshal themselves.
|
||||||
|
// This interface is used by the following functions: Size, Marshal,
|
||||||
|
// Buffer.Marshal, and Buffer.EncodeMessage.
|
||||||
|
//
|
||||||
|
// Deprecated: Do not implement.
|
||||||
|
type Marshaler interface {
|
||||||
|
// Marshal formats the encoded bytes of the message.
|
||||||
|
// It should be deterministic and emit valid protobuf wire data.
|
||||||
|
// The caller takes ownership of the returned buffer.
|
||||||
|
Marshal() ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshaler is implemented by messages that can unmarshal themselves.
|
||||||
|
// This interface is used by the following functions: Unmarshal, UnmarshalMerge,
|
||||||
|
// Buffer.Unmarshal, Buffer.DecodeMessage, and Buffer.DecodeGroup.
|
||||||
|
//
|
||||||
|
// Deprecated: Do not implement.
|
||||||
|
type Unmarshaler interface {
|
||||||
|
// Unmarshal parses the encoded bytes of the protobuf wire input.
|
||||||
|
// The provided buffer is only valid for during method call.
|
||||||
|
// It should not reset the receiver message.
|
||||||
|
Unmarshal([]byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merger is implemented by messages that can merge themselves.
|
||||||
|
// This interface is used by the following functions: Clone and Merge.
|
||||||
|
//
|
||||||
|
// Deprecated: Do not implement.
|
||||||
|
type Merger interface {
|
||||||
|
// Merge merges the contents of src into the receiver message.
|
||||||
|
// It clones all data structures in src such that it aliases no mutable
|
||||||
|
// memory referenced by src.
|
||||||
|
Merge(src Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequiredNotSetError is an error type returned when
|
||||||
|
// marshaling or unmarshaling a message with missing required fields.
|
||||||
|
type RequiredNotSetError struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RequiredNotSetError) Error() string {
|
||||||
|
if e.err != nil {
|
||||||
|
return e.err.Error()
|
||||||
|
}
|
||||||
|
return "proto: required field not set"
|
||||||
|
}
|
||||||
|
func (e *RequiredNotSetError) RequiredNotSet() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkRequiredNotSet(m protoV2.Message) error {
|
||||||
|
if err := protoV2.CheckInitialized(m); err != nil {
|
||||||
|
return &RequiredNotSetError{err: err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone returns a deep copy of src.
|
||||||
|
func Clone(src Message) Message {
|
||||||
|
return MessageV1(protoV2.Clone(MessageV2(src)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merges src into dst, which must be messages of the same type.
|
||||||
|
//
|
||||||
|
// Populated scalar fields in src are copied to dst, while populated
|
||||||
|
// singular messages in src are merged into dst by recursively calling Merge.
|
||||||
|
// The elements of every list field in src is appended to the corresponded
|
||||||
|
// list fields in dst. The entries of every map field in src is copied into
|
||||||
|
// the corresponding map field in dst, possibly replacing existing entries.
|
||||||
|
// The unknown fields of src are appended to the unknown fields of dst.
|
||||||
|
func Merge(dst, src Message) {
|
||||||
|
protoV2.Merge(MessageV2(dst), MessageV2(src))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal reports whether two messages are equal.
|
||||||
|
// If two messages marshal to the same bytes under deterministic serialization,
|
||||||
|
// then Equal is guaranteed to report true.
|
||||||
|
//
|
||||||
|
// Two messages are equal if they are the same protobuf message type,
|
||||||
|
// have the same set of populated known and extension field values,
|
||||||
|
// and the same set of unknown fields values.
|
||||||
|
//
|
||||||
|
// Scalar values are compared with the equivalent of the == operator in Go,
|
||||||
|
// except bytes values which are compared using bytes.Equal and
|
||||||
|
// floating point values which specially treat NaNs as equal.
|
||||||
|
// Message values are compared by recursively calling Equal.
|
||||||
|
// Lists are equal if each element value is also equal.
|
||||||
|
// Maps are equal if they have the same set of keys, where the pair of values
|
||||||
|
// for each key is also equal.
|
||||||
|
func Equal(x, y Message) bool {
|
||||||
|
return protoV2.Equal(MessageV2(x), MessageV2(y))
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMessageSet(md protoreflect.MessageDescriptor) bool {
|
||||||
|
ms, ok := md.(interface{ IsMessageSet() bool })
|
||||||
|
return ok && ms.IsMessageSet()
|
||||||
|
}
|
||||||
323
vendor/github.com/golang/protobuf/proto/registry.go
generated
vendored
Normal file
323
vendor/github.com/golang/protobuf/proto/registry.go
generated
vendored
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
"google.golang.org/protobuf/reflect/protoregistry"
|
||||||
|
"google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// filePath is the path to the proto source file.
|
||||||
|
type filePath = string // e.g., "google/protobuf/descriptor.proto"
|
||||||
|
|
||||||
|
// fileDescGZIP is the compressed contents of the encoded FileDescriptorProto.
|
||||||
|
type fileDescGZIP = []byte
|
||||||
|
|
||||||
|
var fileCache sync.Map // map[filePath]fileDescGZIP
|
||||||
|
|
||||||
|
// RegisterFile is called from generated code to register the compressed
|
||||||
|
// FileDescriptorProto with the file path for a proto source file.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoregistry.GlobalFiles.RegisterFile instead.
|
||||||
|
func RegisterFile(s filePath, d fileDescGZIP) {
|
||||||
|
// Decompress the descriptor.
|
||||||
|
zr, err := gzip.NewReader(bytes.NewReader(d))
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("proto: invalid compressed file descriptor: %v", err))
|
||||||
|
}
|
||||||
|
b, err := ioutil.ReadAll(zr)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("proto: invalid compressed file descriptor: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct a protoreflect.FileDescriptor from the raw descriptor.
|
||||||
|
// Note that DescBuilder.Build automatically registers the constructed
|
||||||
|
// file descriptor with the v2 registry.
|
||||||
|
protoimpl.DescBuilder{RawDescriptor: b}.Build()
|
||||||
|
|
||||||
|
// Locally cache the raw descriptor form for the file.
|
||||||
|
fileCache.Store(s, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileDescriptor returns the compressed FileDescriptorProto given the file path
|
||||||
|
// for a proto source file. It returns nil if not found.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoregistry.GlobalFiles.FindFileByPath instead.
|
||||||
|
func FileDescriptor(s filePath) fileDescGZIP {
|
||||||
|
if v, ok := fileCache.Load(s); ok {
|
||||||
|
return v.(fileDescGZIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the descriptor in the v2 registry.
|
||||||
|
var b []byte
|
||||||
|
if fd, _ := protoregistry.GlobalFiles.FindFileByPath(s); fd != nil {
|
||||||
|
if fd, ok := fd.(interface{ ProtoLegacyRawDesc() []byte }); ok {
|
||||||
|
b = fd.ProtoLegacyRawDesc()
|
||||||
|
} else {
|
||||||
|
// TODO: Use protodesc.ToFileDescriptorProto to construct
|
||||||
|
// a descriptorpb.FileDescriptorProto and marshal it.
|
||||||
|
// However, doing so causes the proto package to have a dependency
|
||||||
|
// on descriptorpb, leading to cyclic dependency issues.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locally cache the raw descriptor form for the file.
|
||||||
|
if len(b) > 0 {
|
||||||
|
v, _ := fileCache.LoadOrStore(s, protoimpl.X.CompressGZIP(b))
|
||||||
|
return v.(fileDescGZIP)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// enumName is the name of an enum. For historical reasons, the enum name is
|
||||||
|
// neither the full Go name nor the full protobuf name of the enum.
|
||||||
|
// The name is the dot-separated combination of just the proto package that the
|
||||||
|
// enum is declared within followed by the Go type name of the generated enum.
|
||||||
|
type enumName = string // e.g., "my.proto.package.GoMessage_GoEnum"
|
||||||
|
|
||||||
|
// enumsByName maps enum values by name to their numeric counterpart.
|
||||||
|
type enumsByName = map[string]int32
|
||||||
|
|
||||||
|
// enumsByNumber maps enum values by number to their name counterpart.
|
||||||
|
type enumsByNumber = map[int32]string
|
||||||
|
|
||||||
|
var enumCache sync.Map // map[enumName]enumsByName
|
||||||
|
var numFilesCache sync.Map // map[protoreflect.FullName]int
|
||||||
|
|
||||||
|
// RegisterEnum is called from the generated code to register the mapping of
|
||||||
|
// enum value names to enum numbers for the enum identified by s.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoregistry.GlobalTypes.RegisterEnum instead.
|
||||||
|
func RegisterEnum(s enumName, _ enumsByNumber, m enumsByName) {
|
||||||
|
if _, ok := enumCache.Load(s); ok {
|
||||||
|
panic("proto: duplicate enum registered: " + s)
|
||||||
|
}
|
||||||
|
enumCache.Store(s, m)
|
||||||
|
|
||||||
|
// This does not forward registration to the v2 registry since this API
|
||||||
|
// lacks sufficient information to construct a complete v2 enum descriptor.
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnumValueMap returns the mapping from enum value names to enum numbers for
|
||||||
|
// the enum of the given name. It returns nil if not found.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoregistry.GlobalTypes.FindEnumByName instead.
|
||||||
|
func EnumValueMap(s enumName) enumsByName {
|
||||||
|
if v, ok := enumCache.Load(s); ok {
|
||||||
|
return v.(enumsByName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the cache is stale. If the number of files in the current
|
||||||
|
// package differs, then it means that some enums may have been recently
|
||||||
|
// registered upstream that we do not know about.
|
||||||
|
var protoPkg protoreflect.FullName
|
||||||
|
if i := strings.LastIndexByte(s, '.'); i >= 0 {
|
||||||
|
protoPkg = protoreflect.FullName(s[:i])
|
||||||
|
}
|
||||||
|
v, _ := numFilesCache.Load(protoPkg)
|
||||||
|
numFiles, _ := v.(int)
|
||||||
|
if protoregistry.GlobalFiles.NumFilesByPackage(protoPkg) == numFiles {
|
||||||
|
return nil // cache is up-to-date; was not found earlier
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the enum cache for all enums declared in the given proto package.
|
||||||
|
numFiles = 0
|
||||||
|
protoregistry.GlobalFiles.RangeFilesByPackage(protoPkg, func(fd protoreflect.FileDescriptor) bool {
|
||||||
|
walkEnums(fd, func(ed protoreflect.EnumDescriptor) {
|
||||||
|
name := protoimpl.X.LegacyEnumName(ed)
|
||||||
|
if _, ok := enumCache.Load(name); !ok {
|
||||||
|
m := make(enumsByName)
|
||||||
|
evs := ed.Values()
|
||||||
|
for i := evs.Len() - 1; i >= 0; i-- {
|
||||||
|
ev := evs.Get(i)
|
||||||
|
m[string(ev.Name())] = int32(ev.Number())
|
||||||
|
}
|
||||||
|
enumCache.LoadOrStore(name, m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
numFiles++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
numFilesCache.Store(protoPkg, numFiles)
|
||||||
|
|
||||||
|
// Check cache again for enum map.
|
||||||
|
if v, ok := enumCache.Load(s); ok {
|
||||||
|
return v.(enumsByName)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// walkEnums recursively walks all enums declared in d.
|
||||||
|
func walkEnums(d interface {
|
||||||
|
Enums() protoreflect.EnumDescriptors
|
||||||
|
Messages() protoreflect.MessageDescriptors
|
||||||
|
}, f func(protoreflect.EnumDescriptor)) {
|
||||||
|
eds := d.Enums()
|
||||||
|
for i := eds.Len() - 1; i >= 0; i-- {
|
||||||
|
f(eds.Get(i))
|
||||||
|
}
|
||||||
|
mds := d.Messages()
|
||||||
|
for i := mds.Len() - 1; i >= 0; i-- {
|
||||||
|
walkEnums(mds.Get(i), f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// messageName is the full name of protobuf message.
|
||||||
|
type messageName = string
|
||||||
|
|
||||||
|
var messageTypeCache sync.Map // map[messageName]reflect.Type
|
||||||
|
|
||||||
|
// RegisterType is called from generated code to register the message Go type
|
||||||
|
// for a message of the given name.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoregistry.GlobalTypes.RegisterMessage instead.
|
||||||
|
func RegisterType(m Message, s messageName) {
|
||||||
|
mt := protoimpl.X.LegacyMessageTypeOf(m, protoreflect.FullName(s))
|
||||||
|
if err := protoregistry.GlobalTypes.RegisterMessage(mt); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
messageTypeCache.Store(s, reflect.TypeOf(m))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterMapType is called from generated code to register the Go map type
|
||||||
|
// for a protobuf message representing a map entry.
|
||||||
|
//
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func RegisterMapType(m interface{}, s messageName) {
|
||||||
|
t := reflect.TypeOf(m)
|
||||||
|
if t.Kind() != reflect.Map {
|
||||||
|
panic(fmt.Sprintf("invalid map kind: %v", t))
|
||||||
|
}
|
||||||
|
if _, ok := messageTypeCache.Load(s); ok {
|
||||||
|
panic(fmt.Errorf("proto: duplicate proto message registered: %s", s))
|
||||||
|
}
|
||||||
|
messageTypeCache.Store(s, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageType returns the message type for a named message.
|
||||||
|
// It returns nil if not found.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoregistry.GlobalTypes.FindMessageByName instead.
|
||||||
|
func MessageType(s messageName) reflect.Type {
|
||||||
|
if v, ok := messageTypeCache.Load(s); ok {
|
||||||
|
return v.(reflect.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive the message type from the v2 registry.
|
||||||
|
var t reflect.Type
|
||||||
|
if mt, _ := protoregistry.GlobalTypes.FindMessageByName(protoreflect.FullName(s)); mt != nil {
|
||||||
|
t = messageGoType(mt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we could not get a concrete type, it is possible that it is a
|
||||||
|
// pseudo-message for a map entry.
|
||||||
|
if t == nil {
|
||||||
|
d, _ := protoregistry.GlobalFiles.FindDescriptorByName(protoreflect.FullName(s))
|
||||||
|
if md, _ := d.(protoreflect.MessageDescriptor); md != nil && md.IsMapEntry() {
|
||||||
|
kt := goTypeForField(md.Fields().ByNumber(1))
|
||||||
|
vt := goTypeForField(md.Fields().ByNumber(2))
|
||||||
|
t = reflect.MapOf(kt, vt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locally cache the message type for the given name.
|
||||||
|
if t != nil {
|
||||||
|
v, _ := messageTypeCache.LoadOrStore(s, t)
|
||||||
|
return v.(reflect.Type)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func goTypeForField(fd protoreflect.FieldDescriptor) reflect.Type {
|
||||||
|
switch k := fd.Kind(); k {
|
||||||
|
case protoreflect.EnumKind:
|
||||||
|
if et, _ := protoregistry.GlobalTypes.FindEnumByName(fd.Enum().FullName()); et != nil {
|
||||||
|
return enumGoType(et)
|
||||||
|
}
|
||||||
|
return reflect.TypeOf(protoreflect.EnumNumber(0))
|
||||||
|
case protoreflect.MessageKind, protoreflect.GroupKind:
|
||||||
|
if mt, _ := protoregistry.GlobalTypes.FindMessageByName(fd.Message().FullName()); mt != nil {
|
||||||
|
return messageGoType(mt)
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*protoreflect.Message)(nil)).Elem()
|
||||||
|
default:
|
||||||
|
return reflect.TypeOf(fd.Default().Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func enumGoType(et protoreflect.EnumType) reflect.Type {
|
||||||
|
return reflect.TypeOf(et.New(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func messageGoType(mt protoreflect.MessageType) reflect.Type {
|
||||||
|
return reflect.TypeOf(MessageV1(mt.Zero().Interface()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageName returns the full protobuf name for the given message type.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoreflect.MessageDescriptor.FullName instead.
|
||||||
|
func MessageName(m Message) messageName {
|
||||||
|
if m == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if m, ok := m.(interface{ XXX_MessageName() messageName }); ok {
|
||||||
|
return m.XXX_MessageName()
|
||||||
|
}
|
||||||
|
return messageName(protoimpl.X.MessageDescriptorOf(m).FullName())
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterExtension is called from the generated code to register
|
||||||
|
// the extension descriptor.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoregistry.GlobalTypes.RegisterExtension instead.
|
||||||
|
func RegisterExtension(d *ExtensionDesc) {
|
||||||
|
if err := protoregistry.GlobalTypes.RegisterExtension(d); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type extensionsByNumber = map[int32]*ExtensionDesc
|
||||||
|
|
||||||
|
var extensionCache sync.Map // map[messageName]extensionsByNumber
|
||||||
|
|
||||||
|
// RegisteredExtensions returns a map of the registered extensions for the
|
||||||
|
// provided protobuf message, indexed by the extension field number.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoregistry.GlobalTypes.RangeExtensionsByMessage instead.
|
||||||
|
func RegisteredExtensions(m Message) extensionsByNumber {
|
||||||
|
// Check whether the cache is stale. If the number of extensions for
|
||||||
|
// the given message differs, then it means that some extensions were
|
||||||
|
// recently registered upstream that we do not know about.
|
||||||
|
s := MessageName(m)
|
||||||
|
v, _ := extensionCache.Load(s)
|
||||||
|
xs, _ := v.(extensionsByNumber)
|
||||||
|
if protoregistry.GlobalTypes.NumExtensionsByMessage(protoreflect.FullName(s)) == len(xs) {
|
||||||
|
return xs // cache is up-to-date
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache is stale, re-compute the extensions map.
|
||||||
|
xs = make(extensionsByNumber)
|
||||||
|
protoregistry.GlobalTypes.RangeExtensionsByMessage(protoreflect.FullName(s), func(xt protoreflect.ExtensionType) bool {
|
||||||
|
if xd, ok := xt.(*ExtensionDesc); ok {
|
||||||
|
xs[int32(xt.TypeDescriptor().Number())] = xd
|
||||||
|
} else {
|
||||||
|
// TODO: This implies that the protoreflect.ExtensionType is a
|
||||||
|
// custom type not generated by protoc-gen-go. We could try and
|
||||||
|
// convert the type to an ExtensionDesc.
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
extensionCache.Store(s, xs)
|
||||||
|
return xs
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user