Merge pull request #1383 from containous/merge-v1.2.1-master

Merge v1.2.1 master
This commit is contained in:
Emile Vauge 2017-04-12 09:13:03 +02:00 committed by GitHub
commit 12a0026e21
410 changed files with 28204 additions and 9758 deletions

View file

@ -1,5 +1,89 @@
# Change Log # Change Log
## [v1.2.1](https://github.com/containous/traefik/tree/v1.2.1) (2017-03-27)
[Full Changelog](https://github.com/containous/traefik/compare/v1.2.0...v1.2.1)
**Merged pull requests:**
- bump lego 0e2937900 [\#1347](https://github.com/containous/traefik/pull/1347) ([emilevauge](https://github.com/emilevauge))
- k8s: Do not log service fields when GetService is failing. [\#1331](https://github.com/containous/traefik/pull/1331) ([timoreimann](https://github.com/timoreimann))
## [v1.2.0](https://github.com/containous/traefik/tree/v1.2.0) (2017-03-20)
[Full Changelog](https://github.com/containous/traefik/compare/v1.1.2...v1.2.0)
**Merged pull requests:**
- Docker: Added warning if network could not be found [\#1310](https://github.com/containous/traefik/pull/1310) ([zweizeichen](https://github.com/zweizeichen))
- Add filter on task status in addition to desired status \(Docker Provider - swarm\) [\#1304](https://github.com/containous/traefik/pull/1304) ([Yshayy](https://github.com/Yshayy))
- Abort Kubernetes Ingress update if Kubernetes API call fails [\#1295](https://github.com/containous/traefik/pull/1295) ([Regner](https://github.com/Regner))
- Small fixes [\#1291](https://github.com/containous/traefik/pull/1291) ([emilevauge](https://github.com/emilevauge))
- Rename health check URL parameter to path. [\#1285](https://github.com/containous/traefik/pull/1285) ([timoreimann](https://github.com/timoreimann))
- Update Oxy, fix for \#1199 [\#1278](https://github.com/containous/traefik/pull/1278) ([akanto](https://github.com/akanto))
- Fix metrics registering [\#1258](https://github.com/containous/traefik/pull/1258) ([matevzmihalic](https://github.com/matevzmihalic))
- Update DefaultMaxIdleConnsPerHost default in docs. [\#1239](https://github.com/containous/traefik/pull/1239) ([timoreimann](https://github.com/timoreimann))
- Update WSS/WS Proto \[Fixes \#670\] [\#1225](https://github.com/containous/traefik/pull/1225) ([dtomcej](https://github.com/dtomcej))
- Bump go-rancher version [\#1219](https://github.com/containous/traefik/pull/1219) ([SantoDE](https://github.com/SantoDE))
- Chunk taskArns into groups of 100 [\#1209](https://github.com/containous/traefik/pull/1209) ([owen](https://github.com/owen))
- Prepare release v1.2.0 rc2 [\#1204](https://github.com/containous/traefik/pull/1204) ([emilevauge](https://github.com/emilevauge))
- Revert "Ensure that we don't add balancees with no health check runs … [\#1198](https://github.com/containous/traefik/pull/1198) ([jangie](https://github.com/jangie))
- Small fixes and improvments [\#1173](https://github.com/containous/traefik/pull/1173) ([SantoDE](https://github.com/SantoDE))
- Fix docker issues with global and dead tasks [\#1167](https://github.com/containous/traefik/pull/1167) ([christopherobin](https://github.com/christopherobin))
- Better ECS error checking [\#1143](https://github.com/containous/traefik/pull/1143) ([lpetre](https://github.com/lpetre))
- Fix stats race condition [\#1141](https://github.com/containous/traefik/pull/1141) ([emilevauge](https://github.com/emilevauge))
- ECS: Docs - info about cred. resolution and required access policies [\#1137](https://github.com/containous/traefik/pull/1137) ([rickard-von-essen](https://github.com/rickard-von-essen))
- Healthcheck tests and doc [\#1132](https://github.com/containous/traefik/pull/1132) ([Juliens](https://github.com/Juliens))
- Fix travis deploy [\#1128](https://github.com/containous/traefik/pull/1128) ([emilevauge](https://github.com/emilevauge))
- Prepare release v1.2.0 rc1 [\#1126](https://github.com/containous/traefik/pull/1126) ([emilevauge](https://github.com/emilevauge))
- Fix checkout initial before calling rmpr [\#1124](https://github.com/containous/traefik/pull/1124) ([emilevauge](https://github.com/emilevauge))
- Feature rancher integration [\#1120](https://github.com/containous/traefik/pull/1120) ([SantoDE](https://github.com/SantoDE))
- Fix glide go units [\#1119](https://github.com/containous/traefik/pull/1119) ([emilevauge](https://github.com/emilevauge))
- Carry \#818 — Add systemd watchdog feature [\#1116](https://github.com/containous/traefik/pull/1116) ([vdemeester](https://github.com/vdemeester))
- Skip file permission check on Windows [\#1115](https://github.com/containous/traefik/pull/1115) ([StefanScherer](https://github.com/StefanScherer))
- Fix Docker API version for Windows [\#1113](https://github.com/containous/traefik/pull/1113) ([StefanScherer](https://github.com/StefanScherer))
- Fix git rpr [\#1109](https://github.com/containous/traefik/pull/1109) ([emilevauge](https://github.com/emilevauge))
- Fix docker version specifier [\#1108](https://github.com/containous/traefik/pull/1108) ([timoreimann](https://github.com/timoreimann))
- Merge v1.1.2 master [\#1105](https://github.com/containous/traefik/pull/1105) ([emilevauge](https://github.com/emilevauge))
- add sh before script in deploy... [\#1103](https://github.com/containous/traefik/pull/1103) ([emilevauge](https://github.com/emilevauge))
- \[doc\] typo fixes for kubernetes user guide [\#1102](https://github.com/containous/traefik/pull/1102) ([bamarni](https://github.com/bamarni))
- add skip\_cleanup in deploy [\#1101](https://github.com/containous/traefik/pull/1101) ([emilevauge](https://github.com/emilevauge))
- Fix k8s example UI port. [\#1098](https://github.com/containous/traefik/pull/1098) ([ddunkin](https://github.com/ddunkin))
- Fix marathon provider [\#1090](https://github.com/containous/traefik/pull/1090) ([diegooliveira](https://github.com/diegooliveira))
- Add an ECS provider [\#1088](https://github.com/containous/traefik/pull/1088) ([lpetre](https://github.com/lpetre))
- Update comment to reflect the code [\#1087](https://github.com/containous/traefik/pull/1087) ([np](https://github.com/np))
- update NYTimes/gziphandler fixes \#1059 [\#1084](https://github.com/containous/traefik/pull/1084) ([JamesKyburz](https://github.com/JamesKyburz))
- Ensure that we don't add balancees with no health check runs if there is a health check defined on it [\#1080](https://github.com/containous/traefik/pull/1080) ([jangie](https://github.com/jangie))
- Add FreeBSD & OpenBSD to crossbinary [\#1078](https://github.com/containous/traefik/pull/1078) ([geoffgarside](https://github.com/geoffgarside))
- Fix metrics for multiple entry points [\#1071](https://github.com/containous/traefik/pull/1071) ([matevzmihalic](https://github.com/matevzmihalic))
- Allow setting load balancer method and sticky using service annotations [\#1068](https://github.com/containous/traefik/pull/1068) ([bakins](https://github.com/bakins))
- Fix travis script [\#1067](https://github.com/containous/traefik/pull/1067) ([emilevauge](https://github.com/emilevauge))
- Add missing fmt verb specifier in k8s provider. [\#1066](https://github.com/containous/traefik/pull/1066) ([timoreimann](https://github.com/timoreimann))
- Add git rpr command [\#1063](https://github.com/containous/traefik/pull/1063) ([emilevauge](https://github.com/emilevauge))
- Fix k8s example [\#1062](https://github.com/containous/traefik/pull/1062) ([emilevauge](https://github.com/emilevauge))
- Replace underscores to dash in autogenerated urls \(docker provider\) [\#1061](https://github.com/containous/traefik/pull/1061) ([WTFKr0](https://github.com/WTFKr0))
- Don't run go test on .glide cache folder [\#1057](https://github.com/containous/traefik/pull/1057) ([vdemeester](https://github.com/vdemeester))
- Allow setting circuitbreaker expression via Kubernetes annotation [\#1056](https://github.com/containous/traefik/pull/1056) ([bakins](https://github.com/bakins))
- Improving instrumentation. [\#1042](https://github.com/containous/traefik/pull/1042) ([enxebre](https://github.com/enxebre))
- Update user guide for upcoming `docker stack deploy` [\#1041](https://github.com/containous/traefik/pull/1041) ([twelvelabs](https://github.com/twelvelabs))
- Support sticky sessions under SWARM Mode. \#1024 [\#1033](https://github.com/containous/traefik/pull/1033) ([foleymic](https://github.com/foleymic))
- Allow for wildcards in k8s ingress host, fixes \#792 [\#1029](https://github.com/containous/traefik/pull/1029) ([sheerun](https://github.com/sheerun))
- Don't fetch ACME certificates for frontends using non-TLS entrypoints \(\#989\) [\#1023](https://github.com/containous/traefik/pull/1023) ([syfonseq](https://github.com/syfonseq))
- Return Proper Non-ACME certificate - Fixes Issue 672 [\#1018](https://github.com/containous/traefik/pull/1018) ([dtomcej](https://github.com/dtomcej))
- Fix docs build and add missing benchmarks page [\#1017](https://github.com/containous/traefik/pull/1017) ([csabapalfi](https://github.com/csabapalfi))
- Set a NopCloser request body with retry middleware [\#1016](https://github.com/containous/traefik/pull/1016) ([bamarni](https://github.com/bamarni))
- instruct to flatten dependencies with glide [\#1010](https://github.com/containous/traefik/pull/1010) ([bamarni](https://github.com/bamarni))
- check permissions on acme.json during startup [\#1009](https://github.com/containous/traefik/pull/1009) ([bamarni](https://github.com/bamarni))
- \[doc\] few tweaks on the basics page [\#1005](https://github.com/containous/traefik/pull/1005) ([bamarni](https://github.com/bamarni))
- Import order as goimports does [\#1004](https://github.com/containous/traefik/pull/1004) ([vdemeester](https://github.com/vdemeester))
- See the right go report badge [\#991](https://github.com/containous/traefik/pull/991) ([guilhem](https://github.com/guilhem))
- Add multiple values for one rule to docs [\#978](https://github.com/containous/traefik/pull/978) ([j0hnsmith](https://github.com/j0hnsmith))
- Add ACME/Lets Encrypt integration tests [\#975](https://github.com/containous/traefik/pull/975) ([trecloux](https://github.com/trecloux))
- deploy.sh: upload release source tarball [\#969](https://github.com/containous/traefik/pull/969) ([Mic92](https://github.com/Mic92))
- toml zookeeper doc fix [\#948](https://github.com/containous/traefik/pull/948) ([brdude](https://github.com/brdude))
- Add Rule AddPrefix [\#931](https://github.com/containous/traefik/pull/931) ([Juliens](https://github.com/Juliens))
- Add bug command [\#921](https://github.com/containous/traefik/pull/921) ([emilevauge](https://github.com/emilevauge))
- \(WIP\) feat: HealthCheck [\#918](https://github.com/containous/traefik/pull/918) ([Juliens](https://github.com/Juliens))
- Add ability to set authenticated user in request header [\#889](https://github.com/containous/traefik/pull/889) ([ViViDboarder](https://github.com/ViViDboarder))
- IP-per-task: [\#841](https://github.com/containous/traefik/pull/841) ([diegooliveira](https://github.com/diegooliveira))
## [v1.2.0-rc2](https://github.com/containous/traefik/tree/v1.2.0-rc2) (2017-03-01) ## [v1.2.0-rc2](https://github.com/containous/traefik/tree/v1.2.0-rc2) (2017-03-01)
[Full Changelog](https://github.com/containous/traefik/compare/v1.2.0-rc1...v1.2.0-rc2) [Full Changelog](https://github.com/containous/traefik/compare/v1.2.0-rc1...v1.2.0-rc2)

View file

@ -8,7 +8,7 @@ TRAEFIK_ENVS := \
-e VERSION \ -e VERSION \
-e CODENAME -e CODENAME
SRCS = $(shell git ls-files '*.go' | grep -v '^external/') SRCS = $(shell git ls-files '*.go' | grep -v '^vendor/' | grep -v '^integration/vendor/')
BIND_DIR := "dist" BIND_DIR := "dist"
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/containous/traefik/$(BIND_DIR)" TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/containous/traefik/$(BIND_DIR)"

View file

@ -408,6 +408,9 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
defaultMesos.Endpoint = "http://127.0.0.1:5050" defaultMesos.Endpoint = "http://127.0.0.1:5050"
defaultMesos.ExposedByDefault = true defaultMesos.ExposedByDefault = true
defaultMesos.Constraints = types.Constraints{} defaultMesos.Constraints = types.Constraints{}
defaultMesos.RefreshSeconds = 30
defaultMesos.ZkDetectionTimeout = 30
defaultMesos.StateTimeoutSecond = 30
//default ECS //default ECS
var defaultECS provider.ECS var defaultECS provider.ECS

View file

@ -240,16 +240,22 @@ For example:
sticky = true sticky = true
``` ```
Healthcheck URL can be configured with a relative URL for `healthcheck.URL`. A health check can be configured in order to remove a backend from LB rotation
Interval between healthcheck can be configured by using `healthcheck.interval` as long as it keeps returning HTTP status codes other than 200 OK to HTTP GET
(default: 30s) requests periodically carried out by Traefik. The check is defined by a path
appended to the backend URL and an interval (given in a format understood by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration)) specifying how
often the health check should be executed (the default being 30 seconds). Each
backend must respond to the health check within 5 seconds.
A recovering backend returning 200 OK responses again is being returned to the
LB rotation pool.
For example: For example:
```toml ```toml
[backends] [backends]
[backends.backend1] [backends.backend1]
[backends.backend1.healthcheck] [backends.backend1.healthcheck]
URL = "/health" path = "/health"
interval = "10s" interval = "10s"
``` ```

View file

@ -77,11 +77,13 @@
# #
# IdleTimeout = "360s" # IdleTimeout = "360s"
# If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used. # Controls the maximum idle (keep-alive) connections to keep per-host. If zero, DefaultMaxIdleConnsPerHost
# If you encounter 'too many open files' errors, you can either change this value, or change `ulimit` value. # from the Go standard library net/http module is used.
# If you encounter 'too many open files' errors, you can either increase this
# value or change the `ulimit`.
# #
# Optional # Optional
# Default: http.DefaultMaxIdleConnsPerHost # Default: 200
# #
# MaxIdleConnsPerHost = 200 # MaxIdleConnsPerHost = 200
@ -842,7 +844,7 @@ Labels can be used on containers to override default behaviour:
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend. - `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.priority=10`: override default frontend priority - `traefik.frontend.priority=10`: override default frontend priority
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. - `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
- `traefik.docker.network`: Set the docker network to use for connections to this container - `traefik.docker.network`: Set the docker network to use for connections to this container. If a container is linked to several networks, be sure to set the proper network name (you can check with docker inspect <container_id>) otherwise it will randomly pick one (depending on how docker is returning them). For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name.
If several ports need to be exposed from a container, the services labels can be used If several ports need to be exposed from a container, the services labels can be used
- `traefik.<service-name>.port=443`: create a service binding with frontend/backend using this port. Overrides `traefik.port`. - `traefik.<service-name>.port=443`: create a service binding with frontend/backend using this port. Overrides `traefik.port`.
@ -1048,12 +1050,14 @@ domain = "mesos.localhost"
# Zookeeper timeout (in seconds) # Zookeeper timeout (in seconds)
# #
# Optional # Optional
# Default: 30
# #
# ZkDetectionTimeout = 30 # ZkDetectionTimeout = 30
# Polling interval (in seconds) # Polling interval (in seconds)
# #
# Optional # Optional
# Default: 30
# #
# RefreshSeconds = 30 # RefreshSeconds = 30
@ -1066,8 +1070,9 @@ domain = "mesos.localhost"
# HTTP Timeout (in seconds) # HTTP Timeout (in seconds)
# #
# Optional # Optional
# Default: 30
# #
# StateTimeoutSecond = "host" # StateTimeoutSecond = "30"
``` ```
## Kubernetes Ingress backend ## Kubernetes Ingress backend

131
glide.lock generated
View file

@ -1,21 +1,19 @@
hash: b1cbcbd938a47a246b0d4d634037b76e63486c63e5867b339f92bcbd7453b75c hash: 2abed980e61ff7659b181849419aadcb8d9d63594a5ac3215d09df0ea12a1700
updated: 2017-04-07T11:34:46.101385591+01:00 updated: 2017-04-11T17:02:21.540487905+02:00
imports: imports:
- name: bitbucket.org/ww/goautoneg
version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675
- name: cloud.google.com/go - name: cloud.google.com/go
version: 2e6a95edb1071d750f6d7db777bf66cd2997af6c version: 2e6a95edb1071d750f6d7db777bf66cd2997af6c
subpackages: subpackages:
- compute/metadata - compute/metadata
- internal - internal
- name: github.com/abbot/go-http-auth - name: github.com/abbot/go-http-auth
version: cb4372376e1e00e9f6ab9ec142e029302c9e7140 version: d45c47bedec736d172957bd394786b76626fa8ac
- name: github.com/ArthurHlt/go-eureka-client - name: github.com/ArthurHlt/go-eureka-client
version: ba361cd0f9f571b4e871421423d2f02f5689c3d2 version: 9d0a49cbd39aa3634ae1977e9f519a262b10adaf
subpackages: subpackages:
- eureka - eureka
- name: github.com/ArthurHlt/gominlog - name: github.com/ArthurHlt/gominlog
version: 068c01ce147ad68fca25ef3fa29ae5395ae273ab version: 72eebf980f467d3ab3a8b4ddf660f664911ce519
- name: github.com/aws/aws-sdk-go - name: github.com/aws/aws-sdk-go
version: 3f8f870ec9939e32b3372abf74d24e468bcd285d version: 3f8f870ec9939e32b3372abf74d24e468bcd285d
subpackages: subpackages:
@ -65,27 +63,27 @@ imports:
- autorest/date - autorest/date
- autorest/to - autorest/to
- name: github.com/beorn7/perks - name: github.com/beorn7/perks
version: b965b613227fddccbfffe13eae360ed3fa822f8d version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
subpackages: subpackages:
- quantile - quantile
- name: github.com/blang/semver - name: github.com/blang/semver
version: 31b736133b98f26d5e078ec9eb591666edfd091f version: 31b736133b98f26d5e078ec9eb591666edfd091f
- name: github.com/boltdb/bolt - name: github.com/boltdb/bolt
version: 5cc10bbbc5c141029940133bb33c9e969512a698 version: e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd
- name: github.com/BurntSushi/toml - name: github.com/BurntSushi/toml
version: 99064174e013895bbd9b025c31100bd1d9b590ca version: b26d9c308763d68093482582cea63d69be07a0f0
- name: github.com/BurntSushi/ty - name: github.com/BurntSushi/ty
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74 version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
subpackages: subpackages:
- fun - fun
- name: github.com/cenk/backoff - name: github.com/cenk/backoff
version: 8edc80b07f38c27352fb186d971c628a6c32552b version: 5d150e7eec023ce7a124856b37c68e54b4050ac7
- name: github.com/codahale/hdrhistogram - name: github.com/codahale/hdrhistogram
version: 9208b142303c12d8899bae836fd524ac9338b4fd version: 9208b142303c12d8899bae836fd524ac9338b4fd
- name: github.com/codegangsta/cli - name: github.com/codegangsta/cli
version: bf4a526f48af7badd25d2cb02d587e1b01be3b50 version: bf4a526f48af7badd25d2cb02d587e1b01be3b50
- name: github.com/codegangsta/negroni - name: github.com/codegangsta/negroni
version: dc6b9d037e8dab60cbfc09c61d6932537829be8b version: c0db5feaa33826cd5117930c8f4ee5c0f565eec6
- name: github.com/containous/flaeg - name: github.com/containous/flaeg
version: b5d2dc5878df07c2d74413348186982e7b865871 version: b5d2dc5878df07c2d74413348186982e7b865871
- name: github.com/containous/mux - name: github.com/containous/mux
@ -95,11 +93,13 @@ imports:
- name: github.com/coreos/etcd - name: github.com/coreos/etcd
version: c400d05d0aa73e21e431c16145e558d624098018 version: c400d05d0aa73e21e431c16145e558d624098018
subpackages: subpackages:
- Godeps/_workspace/src/github.com/ugorji/go/codec - Godeps/_workspace/src/github.com/coreos/go-systemd/journal
- Godeps/_workspace/src/golang.org/x/net/context - Godeps/_workspace/src/github.com/coreos/pkg/capnslog
- client - client
- pkg/fileutil
- pkg/pathutil - pkg/pathutil
- pkg/types - pkg/types
- version
- name: github.com/coreos/go-oidc - name: github.com/coreos/go-oidc
version: 5644a2f50e2d2d5ba0b474bc5bc55fea1925936d version: 5644a2f50e2d2d5ba0b474bc5bc55fea1925936d
subpackages: subpackages:
@ -119,15 +119,13 @@ imports:
- httputil - httputil
- timeutil - timeutil
- name: github.com/davecgh/go-spew - name: github.com/davecgh/go-spew
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9
subpackages: subpackages:
- spew - spew
- name: github.com/daviddengcn/go-colortext
version: 3b18c8575a432453d41fdafb340099fff5bba2f7
- name: github.com/decker502/dnspod-go - name: github.com/decker502/dnspod-go
version: f6b1d56f1c048bd94d7e42ac36efb4d57b069b6f version: 68650ee11e182e30773781d391c66a0c80ccf9f2
- name: github.com/dgrijalva/jwt-go - name: github.com/dgrijalva/jwt-go
version: 9ed569b5d1ac936e6494082958d63a6aa4fff99a version: d2709f9f1f31ebcda9651b03077758c1f3a0018c
- name: github.com/dnsimple/dnsimple-go - name: github.com/dnsimple/dnsimple-go
version: 5a5b427618a76f9eed5ede0f3e6306fbd9311d2e version: 5a5b427618a76f9eed5ede0f3e6306fbd9311d2e
subpackages: subpackages:
@ -168,9 +166,9 @@ imports:
- name: github.com/docker/go-units - name: github.com/docker/go-units
version: 0dadbb0345b35ec7ef35e228dabb8de89a65bf52 version: 0dadbb0345b35ec7ef35e228dabb8de89a65bf52
- name: github.com/docker/leadership - name: github.com/docker/leadership
version: bfc7753dd48af19513b29deec23c364bf0f274eb version: 0a913e2d71a12fd14a028452435cb71ac8d82cb6
- name: github.com/docker/libkv - name: github.com/docker/libkv
version: 35d3e2084c650109e7bcc7282655b1bc8ba924ff version: 1d8431073ae03cdaedb198a89722f3aab6d418ef
subpackages: subpackages:
- store - store
- store/boltdb - store/boltdb
@ -192,18 +190,20 @@ imports:
- tokens - tokens
- zones - zones
- name: github.com/elazarl/go-bindata-assetfs - name: github.com/elazarl/go-bindata-assetfs
version: 57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2 version: 30f82fa23fd844bd5bb1e5f216db87fd77b5eb43
- name: github.com/emicklei/go-restful - name: github.com/emicklei/go-restful
version: 892402ba11a2e2fd5e1295dd633481f27365f14d version: 892402ba11a2e2fd5e1295dd633481f27365f14d
subpackages: subpackages:
- log - log
- swagger - swagger
- name: github.com/fatih/color
version: 9131ab34cf20d2f6d83fdc67168a5430d1c7dc23
- name: github.com/gambol99/go-marathon - name: github.com/gambol99/go-marathon
version: 6b00a5b651b1beb2c6821863f7c60df490bd46c8 version: 6b00a5b651b1beb2c6821863f7c60df490bd46c8
- name: github.com/ghodss/yaml - name: github.com/ghodss/yaml
version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee
- name: github.com/go-ini/ini - name: github.com/go-ini/ini
version: 6f66b0e091edb3c7b380f7c4f0f884274d550b67 version: e7fea39b01aea8d5671f6858f0532f56e8bff3a5
- name: github.com/go-kit/kit - name: github.com/go-kit/kit
version: f66b0e13579bfc5a48b9e2a94b1209c107ea1f41 version: f66b0e13579bfc5a48b9e2a94b1209c107ea1f41
subpackages: subpackages:
@ -226,15 +226,15 @@ imports:
- name: github.com/golang/glog - name: github.com/golang/glog
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
- name: github.com/golang/protobuf - name: github.com/golang/protobuf
version: 5677a0e3d5e89854c9974e1256839ee23f8233ca version: 2bba0603135d7d7f5cb73b2125beeda19c09f4ef
subpackages: subpackages:
- proto - proto
- name: github.com/google/go-github - name: github.com/google/go-github
version: c8ebe3a4d7f0791a6315b7410353d4084c58805d version: 6896997c7c9fe603fb9d2e8e92303bb18481e60a
subpackages: subpackages:
- github - github
- name: github.com/google/go-querystring - name: github.com/google/go-querystring
version: 9235644dd9e52eeae6fa48efd539fdc351a0af53 version: 53e6ce116135b80d037921a7fdd5138cf32d7a8a
subpackages: subpackages:
- query - query
- name: github.com/google/gofuzz - name: github.com/google/gofuzz
@ -242,24 +242,23 @@ imports:
- name: github.com/googleapis/gax-go - name: github.com/googleapis/gax-go
version: 9af46dd5a1713e8b5cd71106287eba3cefdde50b version: 9af46dd5a1713e8b5cd71106287eba3cefdde50b
- name: github.com/gorilla/context - name: github.com/gorilla/context
version: 1ea25387ff6f684839d82767c1733ff4d4d15d0a version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
- name: github.com/gorilla/websocket - name: github.com/gorilla/websocket
version: 4873052237e4eeda85cf50c071ef33836fe8e139 version: a91eba7f97777409bc2c443f5534d41dd20c5720
- name: github.com/hashicorp/consul - name: github.com/hashicorp/consul
version: fce7d75609a04eeb9d4bf41c8dc592aac18fc97d version: 3f92cc70e8163df866873c16c6d89889b5c95fc4
subpackages: subpackages:
- api - api
- name: github.com/hashicorp/go-cleanhttp - name: github.com/hashicorp/go-cleanhttp
version: 875fb671b3ddc66f8e2f0acc33829c8cb989a38d version: 3573b8b52aa7b37b9358d966a898feb387f62437
- name: github.com/hashicorp/go-version - name: github.com/hashicorp/go-version
version: e96d3840402619007766590ecea8dd7af1292276 version: 03c5bf6be031b6dd45afec16b1cf94fc8938bc77
- name: github.com/hashicorp/serf - name: github.com/hashicorp/serf
version: 6c4672d66fc6312ddde18399262943e21175d831 version: 19f2c401e122352c047a84d6584dd51e2fb8fcc4
subpackages: subpackages:
- coordinate - coordinate
- serf
- name: github.com/JamesClonk/vultr - name: github.com/JamesClonk/vultr
version: 9ec0427d51411407c0402b093a1771cb75af9679 version: 0f156dd232bc4ebf8a32ba83fec57c0e4c9db69f
subpackages: subpackages:
- lib - lib
- name: github.com/jmespath/go-jmespath - name: github.com/jmespath/go-jmespath
@ -276,10 +275,16 @@ imports:
- buffer - buffer
- jlexer - jlexer
- jwriter - jwriter
- name: github.com/mattn/go-colorable
version: 5411d3eea5978e6cdc258b30de592b60df6aba96
repo: https://github.com/mattn/go-colorable
- name: github.com/mattn/go-isatty
version: 57fdcb988a5c543893cc61bce354a6e24ab70022
repo: https://github.com/mattn/go-isatty
- name: github.com/mattn/go-shellwords - name: github.com/mattn/go-shellwords
version: 525bedee691b5a8df547cb5cf9f86b7fb1883e24 version: 02e3cf038dcea8290e44424da473dd12be796a8a
- name: github.com/matttproud/golang_protobuf_extensions - name: github.com/matttproud/golang_protobuf_extensions
version: fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a version: c12348ce28de40eed0136aa2b644d0ee0650e56c
subpackages: subpackages:
- pbutil - pbutil
- name: github.com/mesos/mesos-go - name: github.com/mesos/mesos-go
@ -305,79 +310,82 @@ imports:
- records/state - records/state
- util - util
- name: github.com/Microsoft/go-winio - name: github.com/Microsoft/go-winio
version: ce2922f643c8fd76b46cadc7f404a06282678b34 version: fff283ad5116362ca252298cfc9b95828956d85d
- name: github.com/miekg/dns - name: github.com/miekg/dns
version: 8060d9f51305bbe024b99679454e62f552cd0b0b version: 8060d9f51305bbe024b99679454e62f552cd0b0b
- name: github.com/mitchellh/mapstructure - name: github.com/mitchellh/mapstructure
version: f3009df150dadf309fdee4a54ed65c124afad715 version: 53818660ed4955e899c0bcafa97299a388bd7c8e
- name: github.com/mvdan/xurls - name: github.com/mvdan/xurls
version: fa08908f19eca8c491d68c6bd8b4b44faea6daf8 version: db96455566f05ffe42bd6ac671f05eeb1152b45d
- name: github.com/NYTimes/gziphandler - name: github.com/NYTimes/gziphandler
version: 6710af535839f57c687b62c4c23d649f9545d885 version: 22d4470af89e09998fc16b35029df973932df4ae
- name: github.com/ogier/pflag - name: github.com/ogier/pflag
version: 45c278ab3607870051a2ea9040bb85fcb8557481 version: 45c278ab3607870051a2ea9040bb85fcb8557481
- name: github.com/opencontainers/runc - name: github.com/opencontainers/runc
version: 1a81e9ab1f138c091fe5c86d0883f87716088527 version: 50401b5b4c2e01e4f1372b73a021742deeaf4e2d
subpackages: subpackages:
- libcontainer/user - libcontainer/user
- name: github.com/ovh/go-ovh - name: github.com/ovh/go-ovh
version: a8a4c0bc40e56322142649bda7b2b4bb15145b6e version: d2207178e10e4527e8f222fd8707982df8c3af17
subpackages: subpackages:
- ovh - ovh
- name: github.com/pborman/uuid - name: github.com/pborman/uuid
version: ca53cad383cad2479bbba7f7a1a05797ec1386e4 version: ca53cad383cad2479bbba7f7a1a05797ec1386e4
- name: github.com/pkg/errors - name: github.com/pkg/errors
version: bfd5150e4e41705ded2129ec33379de1cb90b513 version: ff09b135c25aae272398c51a07235b90a75aa4f0
- name: github.com/pmezard/go-difflib - name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages: subpackages:
- difflib - difflib
- name: github.com/prometheus/client_golang - name: github.com/prometheus/client_golang
version: c5b7fccd204277076155f10851dad72b76a49317 version: 08fd2e12372a66e68e30523c7642e0cbc3e4fbde
subpackages: subpackages:
- prometheus - prometheus
- prometheus/promhttp - prometheus/promhttp
- name: github.com/prometheus/client_model - name: github.com/prometheus/client_model
version: fa8ad6fec33561be4280a8f0514318c79d7f6cb6 version: 6f3806018612930941127f2a7c6c453ba2c527d2
subpackages: subpackages:
- go - go
- name: github.com/prometheus/common - name: github.com/prometheus/common
version: ffe929a3f4c4faeaa10f2b9535c2b1be3ad15650 version: 49fee292b27bfff7f354ee0f64e1bc4850462edf
subpackages: subpackages:
- expfmt - expfmt
- internal/bitbucket.org/ww/goautoneg
- model - model
- name: github.com/prometheus/procfs - name: github.com/prometheus/procfs
version: 454a56f35412459b5e684fd5ec0f9211b94f002a version: a1dba9ce8baed984a2495b658c82687f8157b98f
subpackages:
- xfs
- name: github.com/PuerkitoBio/purell - name: github.com/PuerkitoBio/purell
version: 8a290539e2e8629dbc4e6bad948158f790ec31f4 version: 8a290539e2e8629dbc4e6bad948158f790ec31f4
- name: github.com/PuerkitoBio/urlesc - name: github.com/PuerkitoBio/urlesc
version: 5bd2802263f21d8788851d5305584c82a5c75d7e version: 5bd2802263f21d8788851d5305584c82a5c75d7e
- name: github.com/pyr/egoscale - name: github.com/pyr/egoscale
version: ab4b0d7ff424c462da486aef27f354cdeb29a319 version: 987e683a7552f34ee586217d1cc8507d52e80ab9
subpackages: subpackages:
- src/egoscale - src/egoscale
- name: github.com/rancher/go-rancher - name: github.com/rancher/go-rancher
version: 2c43ff300f3eafcbd7d0b89b10427fc630efdc1e version: 5b8f6cc26b355ba03d7611fce3844155b7baf05b
subpackages: subpackages:
- client - client
- name: github.com/ryanuber/go-glob - name: github.com/ryanuber/go-glob
version: 572520ed46dbddaed19ea3d9541bdd0494163693 version: 256dc444b735e061061cf46c809487313d5b0065
- name: github.com/samuel/go-zookeeper - name: github.com/samuel/go-zookeeper
version: e64db453f3512cade908163702045e0f31137843 version: 1d7be4effb13d2d908342d349d71a284a7542693
subpackages: subpackages:
- zk - zk
- name: github.com/satori/go.uuid - name: github.com/satori/go.uuid
version: 879c5887cd475cd7864858769793b2ceb0d44feb version: 879c5887cd475cd7864858769793b2ceb0d44feb
- name: github.com/Sirupsen/logrus - name: github.com/Sirupsen/logrus
version: a283a10442df8dc09befd873fab202bf8a253d6a version: 10f801ebc38b33738c9d17d50860f484a0988ff5
- name: github.com/spf13/pflag - name: github.com/spf13/pflag
version: 5ccb023bc27df288a957c5e994cd44fd19619465 version: 5ccb023bc27df288a957c5e994cd44fd19619465
- name: github.com/streamrail/concurrent-map - name: github.com/streamrail/concurrent-map
version: 65a174a3a4188c0b7099acbc6cfa0c53628d3287 version: 8bf1e9bacbf65b10c81d0f4314cf2b1ebef728b5
- name: github.com/stretchr/objx - name: github.com/stretchr/objx
version: cbeaeb16a013161a98496fad62933b1d21786672 version: cbeaeb16a013161a98496fad62933b1d21786672
- name: github.com/stretchr/testify - name: github.com/stretchr/testify
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 version: 4d4bfba8f1d1027c4fdbe371823030df51419987
subpackages: subpackages:
- assert - assert
- mock - mock
@ -394,11 +402,11 @@ imports:
subpackages: subpackages:
- codec - codec
- name: github.com/unrolled/render - name: github.com/unrolled/render
version: 198ad4d8b8a4612176b804ca10555b222a086b40 version: 50716a0a853771bb36bfce61a45cdefdb98c2e6e
- name: github.com/vdemeester/docker-events - name: github.com/vdemeester/docker-events
version: be74d4929ec1ad118df54349fda4b0cba60f849b version: be74d4929ec1ad118df54349fda4b0cba60f849b
- name: github.com/vulcand/oxy - name: github.com/vulcand/oxy
version: fcc76b52eb8568540a020b7a99e854d9d752b364 version: f88530866c561d24a6b5aac49f76d6351b788b9f
repo: https://github.com/containous/oxy.git repo: https://github.com/containous/oxy.git
vcs: git vcs: git
subpackages: subpackages:
@ -523,17 +531,18 @@ imports:
- tap - tap
- transport - transport
- name: gopkg.in/fsnotify.v1 - name: gopkg.in/fsnotify.v1
version: a8a77c9133d2d6fd8334f3260d06f60e8d80a5fb version: 629574ca2a5df945712d3079857300b5e4da0236
- name: gopkg.in/inf.v0 - name: gopkg.in/inf.v0
version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
- name: gopkg.in/ini.v1 - name: gopkg.in/ini.v1
version: 6f66b0e091edb3c7b380f7c4f0f884274d550b67 version: e7fea39b01aea8d5671f6858f0532f56e8bff3a5
- name: gopkg.in/mgo.v2 - name: gopkg.in/mgo.v2
version: 29cc868a5ca65f401ff318143f9408d02f4799cc version: 3f83fa5005286a7fe593b055f0d7771a7dce4655
subpackages: subpackages:
- bson - bson
- internal/json
- name: gopkg.in/ns1/ns1-go.v2 - name: gopkg.in/ns1/ns1-go.v2
version: d8d10b7f448291ddbdce48d4594fb1b667014c8b version: 2abc76c60bf88ba33b15d1d87a13f624d8dff956
subpackages: subpackages:
- rest - rest
- rest/model/account - rest/model/account
@ -542,7 +551,7 @@ imports:
- rest/model/filter - rest/model/filter
- rest/model/monitor - rest/model/monitor
- name: gopkg.in/square/go-jose.v1 - name: gopkg.in/square/go-jose.v1
version: e3f973b66b91445ec816dd7411ad1b6495a5a2fc version: aa2e30fdd1fe9dd3394119af66451ae790d50e0d
subpackages: subpackages:
- cipher - cipher
- json - json

View file

@ -8,7 +8,7 @@ import:
- package: github.com/cenk/backoff - package: github.com/cenk/backoff
- package: github.com/containous/flaeg - package: github.com/containous/flaeg
- package: github.com/vulcand/oxy - package: github.com/vulcand/oxy
version: fcc76b52eb8568540a020b7a99e854d9d752b364 version: f88530866c561d24a6b5aac49f76d6351b788b9f
repo: https://github.com/containous/oxy.git repo: https://github.com/containous/oxy.git
vcs: git vcs: git
subpackages: subpackages:
@ -139,7 +139,7 @@ import:
subpackages: subpackages:
- proto - proto
- package: github.com/rancher/go-rancher - package: github.com/rancher/go-rancher
version: 2c43ff300f3eafcbd7d0b89b10427fc630efdc1e version: 5b8f6cc26b355ba03d7611fce3844155b7baf05b
- package: golang.org/x/oauth2/google - package: golang.org/x/oauth2/google
version: 7fdf09982454086d5570c7db3e11f360194830ca version: 7fdf09982454086d5570c7db3e11f360194830ca
- package: github.com/googleapis/gax-go - package: github.com/googleapis/gax-go

View file

@ -25,7 +25,7 @@ func GetHealthCheck() *HealthCheck {
// BackendHealthCheck HealthCheck configuration for a backend // BackendHealthCheck HealthCheck configuration for a backend
type BackendHealthCheck struct { type BackendHealthCheck struct {
URL string Path string
Interval time.Duration Interval time.Duration
DisabledURLs []*url.URL DisabledURLs []*url.URL
requestTimeout time.Duration requestTimeout time.Duration
@ -53,9 +53,9 @@ func newHealthCheck() *HealthCheck {
} }
// NewBackendHealthCheck Instantiate a new BackendHealthCheck // NewBackendHealthCheck Instantiate a new BackendHealthCheck
func NewBackendHealthCheck(URL string, interval time.Duration, lb loadBalancer) *BackendHealthCheck { func NewBackendHealthCheck(Path string, interval time.Duration, lb loadBalancer) *BackendHealthCheck {
return &BackendHealthCheck{ return &BackendHealthCheck{
URL: URL, Path: Path,
Interval: interval, Interval: interval,
requestTimeout: 5 * time.Second, requestTimeout: 5 * time.Second,
lb: lb, lb: lb,
@ -124,7 +124,7 @@ func checkHealth(serverURL *url.URL, backend *BackendHealthCheck) bool {
client := http.Client{ client := http.Client{
Timeout: backend.requestTimeout, Timeout: backend.requestTimeout,
} }
resp, err := client.Get(serverURL.String() + backend.URL) resp, err := client.Get(serverURL.String() + backend.Path)
if err == nil { if err == nil {
defer resp.Body.Close() defer resp.Body.Close()
} }

View file

@ -26,7 +26,7 @@ func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
output := b.Bytes() output := b.Bytes()
c.Assert(string(output), checker.Contains, "Near line 0 (last key parsed ''): Bare keys cannot contain '{'") c.Assert(string(output), checker.Contains, "Near line 0 (last key parsed ''): bare keys cannot contain '{'")
} }
func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) { func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
@ -70,7 +70,7 @@ func (s *SimpleSuite) TestDefaultEntryPoints(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
output := b.Bytes() output := b.Bytes()
c.Assert(string(output), checker.Contains, "\\\"DefaultEntryPoints\\\":[\\\"http\\\"]") c.Assert(string(output), checker.Contains, "\"DefaultEntryPoints\":[\"http\"]")
} }
func (s *SimpleSuite) TestPrintHelp(c *check.C) { func (s *SimpleSuite) TestPrintHelp(c *check.C) {

View file

@ -32,7 +32,8 @@ func (p *Prometheus) getLatencyHistogram() metrics.Histogram {
// NewPrometheus returns a new prometheus Metrics implementation. // NewPrometheus returns a new prometheus Metrics implementation.
func NewPrometheus(name string, config *types.Prometheus) *Prometheus { func NewPrometheus(name string, config *types.Prometheus) *Prometheus {
var m Prometheus var m Prometheus
m.reqsCounter = prometheus.NewCounterFrom(
cv := stdprometheus.NewCounterVec(
stdprometheus.CounterOpts{ stdprometheus.CounterOpts{
Name: reqsName, Name: reqsName,
Help: "How many HTTP requests processed, partitioned by status code and method.", Help: "How many HTTP requests processed, partitioned by status code and method.",
@ -41,6 +42,17 @@ func NewPrometheus(name string, config *types.Prometheus) *Prometheus {
[]string{"code", "method"}, []string{"code", "method"},
) )
err := stdprometheus.Register(cv)
if err != nil {
e, ok := err.(stdprometheus.AlreadyRegisteredError)
if !ok {
panic(err)
}
m.reqsCounter = prometheus.NewCounter(e.ExistingCollector.(*stdprometheus.CounterVec))
} else {
m.reqsCounter = prometheus.NewCounter(cv)
}
var buckets []float64 var buckets []float64
if config.Buckets != nil { if config.Buckets != nil {
buckets = config.Buckets buckets = config.Buckets
@ -48,7 +60,7 @@ func NewPrometheus(name string, config *types.Prometheus) *Prometheus {
buckets = []float64{0.1, 0.3, 1.2, 5} buckets = []float64{0.1, 0.3, 1.2, 5}
} }
m.latencyHistogram = prometheus.NewHistogramFrom( hv := stdprometheus.NewHistogramVec(
stdprometheus.HistogramOpts{ stdprometheus.HistogramOpts{
Name: latencyName, Name: latencyName,
Help: "How long it took to process the request.", Help: "How long it took to process the request.",
@ -57,6 +69,18 @@ func NewPrometheus(name string, config *types.Prometheus) *Prometheus {
}, },
[]string{}, []string{},
) )
err = stdprometheus.Register(hv)
if err != nil {
e, ok := err.(stdprometheus.AlreadyRegisteredError)
if !ok {
panic(err)
}
m.latencyHistogram = prometheus.NewHistogram(e.ExistingCollector.(*stdprometheus.HistogramVec))
} else {
m.latencyHistogram = prometheus.NewHistogram(hv)
}
return &m return &m
} }

View file

@ -9,10 +9,19 @@ import (
"github.com/codegangsta/negroni" "github.com/codegangsta/negroni"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
dto "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/assert"
) )
func TestPrometheus(t *testing.T) { func TestPrometheus(t *testing.T) {
metricsFamily, err := prometheus.DefaultGatherer.Gather()
if err != nil {
t.Fatalf("could not gather metrics family: %s", err)
}
initialMetricsFamilyCount := len(metricsFamily)
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
n := negroni.New() n := negroni.New()
@ -42,6 +51,80 @@ func TestPrometheus(t *testing.T) {
t.Errorf("body does not contain request total entry '%s'", reqsName) t.Errorf("body does not contain request total entry '%s'", reqsName)
} }
if !strings.Contains(body, latencyName) { if !strings.Contains(body, latencyName) {
t.Errorf("body does not contain request duration entry '%s'", reqsName) t.Errorf("body does not contain request duration entry '%s'", latencyName)
}
// Register the same metrics again
metricsMiddlewareBackend = NewMetricsWrapper(NewPrometheus("test", &types.Prometheus{}))
n = negroni.New()
n.Use(metricsMiddlewareBackend)
n.UseHandler(r)
n.ServeHTTP(recorder, req2)
metricsFamily, err = prometheus.DefaultGatherer.Gather()
if err != nil {
t.Fatalf("could not gather metrics family: %s", err)
}
tests := []struct {
name string
labels map[string]string
assert func(*dto.MetricFamily)
}{
{
name: reqsName,
labels: map[string]string{
"code": "200",
"method": "GET",
"service": "test",
},
assert: func(family *dto.MetricFamily) {
cv := uint(family.Metric[0].Counter.GetValue())
if cv != 3 {
t.Errorf("gathered metrics do not contain correct value for total requests, got %d", cv)
}
},
},
{
name: latencyName,
labels: map[string]string{
"service": "test",
},
assert: func(family *dto.MetricFamily) {
sc := family.Metric[0].Histogram.GetSampleCount()
if sc != 3 {
t.Errorf("gathered metrics do not contain correct sample count for request duration, got %d", sc)
}
},
},
}
assert.Equal(t, len(tests), len(metricsFamily)-initialMetricsFamilyCount, "gathered traefic metrics count does not match tests count")
for _, test := range tests {
family := findMetricFamily(test.name, metricsFamily)
if family == nil {
t.Errorf("gathered metrics do not contain '%s'", test.name)
continue
}
for _, label := range family.Metric[0].Label {
val, ok := test.labels[*label.Name]
if !ok {
t.Errorf("'%s' metric contains unexpected label '%s'", test.name, label)
} else if val != *label.Value {
t.Errorf("label '%s' in metric '%s' has wrong value '%s'", label, test.name, *label.Value)
}
}
test.assert(family)
} }
} }
func findMetricFamily(name string, families []*dto.MetricFamily) *dto.MetricFamily {
for _, family := range families {
if family.GetName() == name {
return family
}
}
return nil
}

View file

@ -550,6 +550,8 @@ func (provider *Docker) getIPAddress(container dockerData) string {
if network != nil { if network != nil {
return network.Addr return network.Addr
} }
log.Warnf("Could not find network named '%s' for container '%s'! Maybe you're missing the project's prefix in the label? Defaulting to first available network.", label, container.Name)
} }
} }
@ -826,6 +828,9 @@ func listTasks(ctx context.Context, dockerClient client.APIClient, serviceID str
var dockerDataList []dockerData var dockerDataList []dockerData
for _, task := range taskList { for _, task := range taskList {
if task.Status.State != swarm.TaskStateRunning {
continue
}
dockerData := parseTasks(task, serviceDockerData, networkMap, isGlobalSvc) dockerData := parseTasks(task, serviceDockerData, networkMap, isGlobalSvc)
dockerDataList = append(dockerDataList, dockerData) dockerDataList = append(dockerDataList, dockerData)
} }

View file

@ -6,11 +6,16 @@ import (
"testing" "testing"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/davecgh/go-spew/spew"
dockerclient "github.com/docker/engine-api/client"
docker "github.com/docker/engine-api/types" docker "github.com/docker/engine-api/types"
dockertypes "github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container" "github.com/docker/engine-api/types/container"
"github.com/docker/engine-api/types/network" "github.com/docker/engine-api/types/network"
"github.com/docker/engine-api/types/swarm" "github.com/docker/engine-api/types/swarm"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
"golang.org/x/net/context"
"strconv"
) )
func TestDockerGetFrontendName(t *testing.T) { func TestDockerGetFrontendName(t *testing.T) {
@ -99,12 +104,16 @@ func TestDockerGetFrontendName(t *testing.T) {
}, },
} }
for _, e := range containers { for containerID, e := range containers {
dockerData := parseContainer(e.container) e := e
actual := provider.getFrontendName(dockerData) t.Run(strconv.Itoa(containerID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseContainer(e.container)
} actual := provider.getFrontendName(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
@ -176,12 +185,16 @@ func TestDockerGetFrontendRule(t *testing.T) {
}, },
} }
for _, e := range containers { for containerID, e := range containers {
dockerData := parseContainer(e.container) e := e
actual := provider.getFrontendRule(dockerData) t.Run(strconv.Itoa(containerID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseContainer(e.container)
} actual := provider.getFrontendRule(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
@ -239,12 +252,16 @@ func TestDockerGetBackend(t *testing.T) {
}, },
} }
for _, e := range containers { for containerID, e := range containers {
dockerData := parseContainer(e.container) e := e
actual := provider.getBackend(dockerData) t.Run(strconv.Itoa(containerID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseContainer(e.container)
} actual := provider.getBackend(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
@ -340,12 +357,16 @@ func TestDockerGetIPAddress(t *testing.T) { // TODO
}, },
} }
for _, e := range containers { for containerID, e := range containers {
dockerData := parseContainer(e.container) e := e
actual := provider.getIPAddress(dockerData) t.Run(strconv.Itoa(containerID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseContainer(e.container)
} actual := provider.getIPAddress(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
@ -453,12 +474,16 @@ func TestDockerGetPort(t *testing.T) {
}, },
} }
for _, e := range containers { for containerID, e := range containers {
dockerData := parseContainer(e.container) e := e
actual := provider.getPort(dockerData) t.Run(strconv.Itoa(containerID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseContainer(e.container)
} actual := provider.getPort(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
@ -493,12 +518,16 @@ func TestDockerGetWeight(t *testing.T) {
}, },
} }
for _, e := range containers { for containerID, e := range containers {
dockerData := parseContainer(e.container) e := e
actual := provider.getWeight(dockerData) t.Run(strconv.Itoa(containerID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseContainer(e.container)
} actual := provider.getWeight(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
@ -535,12 +564,16 @@ func TestDockerGetDomain(t *testing.T) {
}, },
} }
for _, e := range containers { for containerID, e := range containers {
dockerData := parseContainer(e.container) e := e
actual := provider.getDomain(dockerData) t.Run(strconv.Itoa(containerID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseContainer(e.container)
} actual := provider.getDomain(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
@ -575,12 +608,16 @@ func TestDockerGetProtocol(t *testing.T) {
}, },
} }
for _, e := range containers { for containerID, e := range containers {
dockerData := parseContainer(e.container) e := e
actual := provider.getProtocol(dockerData) t.Run(strconv.Itoa(containerID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseContainer(e.container)
} actual := provider.getProtocol(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
@ -614,12 +651,16 @@ func TestDockerGetPassHostHeader(t *testing.T) {
}, },
} }
for _, e := range containers { for containerID, e := range containers {
dockerData := parseContainer(e.container) e := e
actual := provider.getPassHostHeader(dockerData) t.Run(strconv.Itoa(containerID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseContainer(e.container)
} actual := provider.getPassHostHeader(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
@ -652,18 +693,22 @@ func TestDockerGetLabel(t *testing.T) {
}, },
} }
for _, e := range containers { for containerID, e := range containers {
dockerData := parseContainer(e.container) e := e
label, err := getLabel(dockerData, "foo") t.Run(strconv.Itoa(containerID), func(t *testing.T) {
if e.expected != "" { t.Parallel()
if err == nil || !strings.Contains(err.Error(), e.expected) { dockerData := parseContainer(e.container)
t.Fatalf("expected an error with %q, got %v", e.expected, err) label, err := getLabel(dockerData, "foo")
if e.expected != "" {
if err == nil || !strings.Contains(err.Error(), e.expected) {
t.Errorf("expected an error with %q, got %v", e.expected, err)
}
} else {
if label != "bar" {
t.Errorf("expected label 'bar', got %s", label)
}
} }
} else { })
if label != "bar" {
t.Fatalf("expected label 'bar', got %s", label)
}
}
} }
} }
@ -719,17 +764,21 @@ func TestDockerGetLabels(t *testing.T) {
}, },
} }
for _, e := range containers { for containerID, e := range containers {
dockerData := parseContainer(e.container) e := e
labels, err := getLabels(dockerData, []string{"foo", "bar"}) t.Run(strconv.Itoa(containerID), func(t *testing.T) {
if !reflect.DeepEqual(labels, e.expectedLabels) { t.Parallel()
t.Fatalf("expect %v, got %v", e.expectedLabels, labels) dockerData := parseContainer(e.container)
} labels, err := getLabels(dockerData, []string{"foo", "bar"})
if e.expectedError != "" { if !reflect.DeepEqual(labels, e.expectedLabels) {
if err == nil || !strings.Contains(err.Error(), e.expectedError) { t.Errorf("expect %v, got %v", e.expectedLabels, labels)
t.Fatalf("expected an error with %q, got %v", e.expectedError, err)
} }
} if e.expectedError != "" {
if err == nil || !strings.Contains(err.Error(), e.expectedError) {
t.Errorf("expected an error with %q, got %v", e.expectedError, err)
}
}
})
} }
} }
@ -953,13 +1002,17 @@ func TestDockerTraefikFilter(t *testing.T) {
}, },
} }
for _, e := range containers { for containerID, e := range containers {
provider.ExposedByDefault = e.exposedByDefault e := e
dockerData := parseContainer(e.container) t.Run(strconv.Itoa(containerID), func(t *testing.T) {
actual := provider.containerFilter(dockerData) t.Parallel()
if actual != e.expected { provider.ExposedByDefault = e.exposedByDefault
t.Fatalf("expected %v for %+v, got %+v", e.expected, e, actual) dockerData := parseContainer(e.container)
} actual := provider.containerFilter(dockerData)
if actual != e.expected {
t.Errorf("expected %v for %+v, got %+v", e.expected, e, actual)
}
})
} }
} }
@ -1175,21 +1228,25 @@ func TestDockerLoadDockerConfig(t *testing.T) {
ExposedByDefault: true, ExposedByDefault: true,
} }
for _, c := range cases { for caseID, c := range cases {
var dockerDataList []dockerData c := c
for _, container := range c.containers { t.Run(strconv.Itoa(caseID), func(t *testing.T) {
dockerData := parseContainer(container) t.Parallel()
dockerDataList = append(dockerDataList, dockerData) var dockerDataList []dockerData
} for _, container := range c.containers {
dockerData := parseContainer(container)
dockerDataList = append(dockerDataList, dockerData)
}
actualConfig := provider.loadDockerConfig(dockerDataList) actualConfig := provider.loadDockerConfig(dockerDataList)
// Compare backends // Compare backends
if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) { if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) {
t.Fatalf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends) t.Errorf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends)
} }
if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) { if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) {
t.Fatalf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends) t.Errorf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends)
} }
})
} }
} }
@ -1273,12 +1330,16 @@ func TestSwarmGetFrontendName(t *testing.T) {
}, },
} }
for _, e := range services { for serviceID, e := range services {
dockerData := parseService(e.service, e.networks) e := e
actual := provider.getFrontendName(dockerData) t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseService(e.service, e.networks)
} actual := provider.getFrontendName(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
@ -1345,12 +1406,16 @@ func TestSwarmGetFrontendRule(t *testing.T) {
}, },
} }
for _, e := range services { for serviceID, e := range services {
dockerData := parseService(e.service, e.networks) e := e
actual := provider.getFrontendRule(dockerData) t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseService(e.service, e.networks)
} actual := provider.getFrontendRule(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
@ -1402,12 +1467,16 @@ func TestSwarmGetBackend(t *testing.T) {
}, },
} }
for _, e := range services { for serviceID, e := range services {
dockerData := parseService(e.service, e.networks) e := e
actual := provider.getBackend(dockerData) t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseService(e.service, e.networks)
} actual := provider.getBackend(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
@ -1499,12 +1568,16 @@ func TestSwarmGetIPAddress(t *testing.T) {
}, },
} }
for _, e := range services { for serviceID, e := range services {
dockerData := parseService(e.service, e.networks) e := e
actual := provider.getIPAddress(dockerData) t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseService(e.service, e.networks)
} actual := provider.getIPAddress(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
@ -1537,12 +1610,16 @@ func TestSwarmGetPort(t *testing.T) {
}, },
} }
for _, e := range services { for serviceID, e := range services {
dockerData := parseService(e.service, e.networks) e := e
actual := provider.getPort(dockerData) t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseService(e.service, e.networks)
} actual := provider.getPort(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
@ -1589,12 +1666,16 @@ func TestSwarmGetWeight(t *testing.T) {
}, },
} }
for _, e := range services { for serviceID, e := range services {
dockerData := parseService(e.service, e.networks) e := e
actual := provider.getWeight(dockerData) t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseService(e.service, e.networks)
} actual := provider.getWeight(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
@ -1642,12 +1723,16 @@ func TestSwarmGetDomain(t *testing.T) {
}, },
} }
for _, e := range services { for serviceID, e := range services {
dockerData := parseService(e.service, e.networks) e := e
actual := provider.getDomain(dockerData) t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseService(e.service, e.networks)
} actual := provider.getDomain(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
@ -1694,12 +1779,16 @@ func TestSwarmGetProtocol(t *testing.T) {
}, },
} }
for _, e := range services { for serviceID, e := range services {
dockerData := parseService(e.service, e.networks) e := e
actual := provider.getProtocol(dockerData) t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseService(e.service, e.networks)
} actual := provider.getProtocol(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
@ -1746,17 +1835,20 @@ func TestSwarmGetPassHostHeader(t *testing.T) {
}, },
} }
for _, e := range services { for serviceID, e := range services {
dockerData := parseService(e.service, e.networks) e := e
actual := provider.getPassHostHeader(dockerData) t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
if actual != e.expected { t.Parallel()
t.Fatalf("expected %q, got %q", e.expected, actual) dockerData := parseService(e.service, e.networks)
} actual := provider.getPassHostHeader(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
} }
} }
func TestSwarmGetLabel(t *testing.T) { func TestSwarmGetLabel(t *testing.T) {
services := []struct { services := []struct {
service swarm.Service service swarm.Service
expected string expected string
@ -1795,18 +1887,22 @@ func TestSwarmGetLabel(t *testing.T) {
}, },
} }
for _, e := range services { for serviceID, e := range services {
dockerData := parseService(e.service, e.networks) e := e
label, err := getLabel(dockerData, "foo") t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
if e.expected != "" { t.Parallel()
if err == nil || !strings.Contains(err.Error(), e.expected) { dockerData := parseService(e.service, e.networks)
t.Fatalf("expected an error with %q, got %v", e.expected, err) label, err := getLabel(dockerData, "foo")
if e.expected != "" {
if err == nil || !strings.Contains(err.Error(), e.expected) {
t.Errorf("expected an error with %q, got %v", e.expected, err)
}
} else {
if label != "bar" {
t.Errorf("expected label 'bar', got %s", label)
}
} }
} else { })
if label != "bar" {
t.Fatalf("expected label 'bar', got %s", label)
}
}
} }
} }
@ -1867,17 +1963,21 @@ func TestSwarmGetLabels(t *testing.T) {
}, },
} }
for _, e := range services { for serviceID, e := range services {
dockerData := parseService(e.service, e.networks) e := e
labels, err := getLabels(dockerData, []string{"foo", "bar"}) t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
if !reflect.DeepEqual(labels, e.expectedLabels) { t.Parallel()
t.Fatalf("expect %v, got %v", e.expectedLabels, labels) dockerData := parseService(e.service, e.networks)
} labels, err := getLabels(dockerData, []string{"foo", "bar"})
if e.expectedError != "" { if !reflect.DeepEqual(labels, e.expectedLabels) {
if err == nil || !strings.Contains(err.Error(), e.expectedError) { t.Errorf("expect %v, got %v", e.expectedLabels, labels)
t.Fatalf("expected an error with %q, got %v", e.expectedError, err)
} }
} if e.expectedError != "" {
if err == nil || !strings.Contains(err.Error(), e.expectedError) {
t.Errorf("expected an error with %q, got %v", e.expectedError, err)
}
}
})
} }
} }
@ -2031,13 +2131,17 @@ func TestSwarmTraefikFilter(t *testing.T) {
}, },
} }
for _, e := range services { for serviceID, e := range services {
dockerData := parseService(e.service, e.networks) e := e
provider.ExposedByDefault = e.exposedByDefault t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
actual := provider.containerFilter(dockerData) t.Parallel()
if actual != e.expected { dockerData := parseService(e.service, e.networks)
t.Fatalf("expected %v for %+v, got %+v", e.expected, e, actual) provider.ExposedByDefault = e.exposedByDefault
} actual := provider.containerFilter(dockerData)
if actual != e.expected {
t.Errorf("expected %v for %+v, got %+v", e.expected, e, actual)
}
})
} }
} }
@ -2208,21 +2312,25 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
SwarmMode: true, SwarmMode: true,
} }
for _, c := range cases { for caseID, c := range cases {
var dockerDataList []dockerData c := c
for _, service := range c.services { t.Run(strconv.Itoa(caseID), func(t *testing.T) {
dockerData := parseService(service, c.networks) t.Parallel()
dockerDataList = append(dockerDataList, dockerData) var dockerDataList []dockerData
} for _, service := range c.services {
dockerData := parseService(service, c.networks)
dockerDataList = append(dockerDataList, dockerData)
}
actualConfig := provider.loadDockerConfig(dockerDataList) actualConfig := provider.loadDockerConfig(dockerDataList)
// Compare backends // Compare backends
if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) { if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) {
t.Fatalf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends) t.Errorf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends)
} }
if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) { if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) {
t.Fatalf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends) t.Errorf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends)
} }
})
} }
} }
@ -2230,7 +2338,7 @@ func TestSwarmTaskParsing(t *testing.T) {
cases := []struct { cases := []struct {
service swarm.Service service swarm.Service
tasks []swarm.Task tasks []swarm.Task
isGlobalSvc bool isGlobalSVC bool
expectedNames map[string]string expectedNames map[string]string
networks map[string]*docker.NetworkResource networks map[string]*docker.NetworkResource
}{ }{
@ -2256,7 +2364,7 @@ func TestSwarmTaskParsing(t *testing.T) {
Slot: 3, Slot: 3,
}, },
}, },
isGlobalSvc: false, isGlobalSVC: false,
expectedNames: map[string]string{ expectedNames: map[string]string{
"id1": "container.1", "id1": "container.1",
"id2": "container.2", "id2": "container.2",
@ -2287,7 +2395,7 @@ func TestSwarmTaskParsing(t *testing.T) {
ID: "id3", ID: "id3",
}, },
}, },
isGlobalSvc: true, isGlobalSVC: true,
expectedNames: map[string]string{ expectedNames: map[string]string{
"id1": "container.id1", "id1": "container.id1",
"id2": "container.id2", "id2": "container.id2",
@ -2301,15 +2409,113 @@ func TestSwarmTaskParsing(t *testing.T) {
}, },
} }
for _, e := range cases { for caseID, e := range cases {
dockerData := parseService(e.service, e.networks) e := e
t.Run(strconv.Itoa(caseID), func(t *testing.T) {
t.Parallel()
dockerData := parseService(e.service, e.networks)
for _, task := range e.tasks { for _, task := range e.tasks {
taskDockerData := parseTasks(task, dockerData, map[string]*docker.NetworkResource{}, e.isGlobalSvc) taskDockerData := parseTasks(task, dockerData, map[string]*docker.NetworkResource{}, e.isGlobalSVC)
if !reflect.DeepEqual(taskDockerData.Name, e.expectedNames[task.ID]) { if !reflect.DeepEqual(taskDockerData.Name, e.expectedNames[task.ID]) {
t.Fatalf("expect %v, got %v", e.expectedNames[task.ID], taskDockerData.Name) t.Errorf("expect %v, got %v", e.expectedNames[task.ID], taskDockerData.Name)
}
} }
} })
}
}
type fakeTasksClient struct {
dockerclient.APIClient
tasks []swarm.Task
err error
}
func (c *fakeTasksClient) TaskList(ctx context.Context, options dockertypes.TaskListOptions) ([]swarm.Task, error) {
return c.tasks, c.err
}
func TestListTasks(t *testing.T) {
cases := []struct {
service swarm.Service
tasks []swarm.Task
isGlobalSVC bool
expectedTasks []string
networks map[string]*docker.NetworkResource
}{
{
service: swarm.Service{
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{
Name: "container",
},
},
},
tasks: []swarm.Task{
{
ID: "id1",
Slot: 1,
Status: swarm.TaskStatus{
State: swarm.TaskStateRunning,
},
},
{
ID: "id2",
Slot: 2,
Status: swarm.TaskStatus{
State: swarm.TaskStatePending,
},
},
{
ID: "id3",
Slot: 3,
},
{
ID: "id4",
Slot: 4,
Status: swarm.TaskStatus{
State: swarm.TaskStateRunning,
},
},
{
ID: "id5",
Slot: 5,
Status: swarm.TaskStatus{
State: swarm.TaskStateFailed,
},
},
},
isGlobalSVC: false,
expectedTasks: []string{
"container.1",
"container.4",
},
networks: map[string]*docker.NetworkResource{
"1": {
Name: "foo",
},
},
},
}
for caseID, e := range cases {
e := e
t.Run(strconv.Itoa(caseID), func(t *testing.T) {
t.Parallel()
dockerData := parseService(e.service, e.networks)
dockerClient := &fakeTasksClient{tasks: e.tasks}
taskDockerData, _ := listTasks(context.Background(), dockerClient, e.service.ID, dockerData, map[string]*docker.NetworkResource{}, e.isGlobalSVC)
if len(e.expectedTasks) != len(taskDockerData) {
t.Errorf("expected tasks %v, got %v", spew.Sdump(e.expectedTasks), spew.Sdump(taskDockerData))
}
for i, taskID := range e.expectedTasks {
if taskDockerData[i].Name != taskID {
t.Errorf("expect task id %v, got %v", taskID, taskDockerData[i].Name)
}
}
})
} }
} }

View file

@ -19,7 +19,7 @@ type mockDynamoDBCLient struct {
var backend = &types.Backend{ var backend = &types.Backend{
HealthCheck: &types.HealthCheck{ HealthCheck: &types.HealthCheck{
URL: "/build", Path: "/build",
}, },
Servers: map[string]types.Server{ Servers: map[string]types.Server{
"server1": { "server1": {

View file

@ -206,13 +206,28 @@ func (provider *ECS) listInstances(ctx context.Context, client *awsClient) ([]ec
taskArns = append(taskArns, req.Data.(*ecs.ListTasksOutput).TaskArns...) taskArns = append(taskArns, req.Data.(*ecs.ListTasksOutput).TaskArns...)
} }
req, taskResp := client.ecs.DescribeTasksRequest(&ecs.DescribeTasksInput{ // Early return: if we can't list tasks we have nothing to
Tasks: taskArns, // describe below - likely empty cluster/permissions are bad. This
Cluster: &provider.Cluster, // stops the AWS API from returning a 401 when you DescribeTasks
}) // with no input.
if len(taskArns) == 0 {
return []ecsInstance{}, nil
}
chunkedTaskArns := provider.chunkedTaskArns(taskArns)
var tasks []*ecs.Task
for _, arns := range chunkedTaskArns {
req, taskResp := client.ecs.DescribeTasksRequest(&ecs.DescribeTasksInput{
Tasks: arns,
Cluster: &provider.Cluster,
})
if err := wrapAws(ctx, req); err != nil {
return nil, err
}
tasks = append(tasks, taskResp.Tasks...)
if err := wrapAws(ctx, req); err != nil {
return nil, err
} }
containerInstanceArns := make([]*string, 0) containerInstanceArns := make([]*string, 0)
@ -221,7 +236,7 @@ func (provider *ECS) listInstances(ctx context.Context, client *awsClient) ([]ec
taskDefinitionArns := make([]*string, 0) taskDefinitionArns := make([]*string, 0)
byTaskDefinition := make(map[string]int) byTaskDefinition := make(map[string]int)
for _, task := range taskResp.Tasks { for _, task := range tasks {
if _, found := byContainerInstance[*task.ContainerInstanceArn]; !found { if _, found := byContainerInstance[*task.ContainerInstanceArn]; !found {
byContainerInstance[*task.ContainerInstanceArn] = len(containerInstanceArns) byContainerInstance[*task.ContainerInstanceArn] = len(containerInstanceArns)
containerInstanceArns = append(containerInstanceArns, task.ContainerInstanceArn) containerInstanceArns = append(containerInstanceArns, task.ContainerInstanceArn)
@ -243,7 +258,7 @@ func (provider *ECS) listInstances(ctx context.Context, client *awsClient) ([]ec
} }
var instances []ecsInstance var instances []ecsInstance
for _, task := range taskResp.Tasks { for _, task := range tasks {
machineIdx := byContainerInstance[*task.ContainerInstanceArn] machineIdx := byContainerInstance[*task.ContainerInstanceArn]
taskDefIdx := byTaskDefinition[*task.TaskDefinitionArn] taskDefIdx := byTaskDefinition[*task.TaskDefinitionArn]
@ -398,6 +413,22 @@ func (provider *ECS) getFrontendRule(i ecsInstance) string {
return "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + provider.Domain return "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + provider.Domain
} }
// ECS expects no more than 100 parameters be passed to a DescribeTask call; thus, pack
// each string into an array capped at 100 elements
func (provider *ECS) chunkedTaskArns(tasks []*string) [][]*string {
var chunkedTasks [][]*string
for i := 0; i < len(tasks); i += 100 {
sliceEnd := -1
if i+100 < len(tasks) {
sliceEnd = i + 100
} else {
sliceEnd = len(tasks)
}
chunkedTasks = append(chunkedTasks, tasks[i:sliceEnd])
}
return chunkedTasks
}
func (i ecsInstance) Protocol() string { func (i ecsInstance) Protocol() string {
if label := i.label("traefik.protocol"); label != "" { if label := i.label("traefik.protocol"); label != "" {
return label return label

View file

@ -308,3 +308,42 @@ func TestFilterInstance(t *testing.T) {
} }
} }
} }
func TestTaskChunking(t *testing.T) {
provider := &ECS{}
testval := "a"
cases := []struct {
count int
expectedLengths []int
}{
{0, []int(nil)},
{1, []int{1}},
{99, []int{99}},
{100, []int{100}},
{101, []int{100, 1}},
{199, []int{100, 99}},
{200, []int{100, 100}},
{201, []int{100, 100, 1}},
{555, []int{100, 100, 100, 100, 100, 55}},
{1001, []int{100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 1}},
}
for _, c := range cases {
var tasks []*string
for v := 0; v < c.count; v++ {
tasks = append(tasks, &testval)
}
out := provider.chunkedTaskArns(tasks)
var outCount []int
for _, el := range out {
outCount = append(outCount, len(el))
}
if !reflect.DeepEqual(outCount, c.expectedLengths) {
t.Errorf("Chunking %d elements, expected %#v, got %#v", c.count, c.expectedLengths, outCount)
}
}
}

View file

@ -198,8 +198,13 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
} }
service, exists, err := k8sClient.GetService(i.ObjectMeta.Namespace, pa.Backend.ServiceName) service, exists, err := k8sClient.GetService(i.ObjectMeta.Namespace, pa.Backend.ServiceName)
if err != nil || !exists { if err != nil {
log.Warnf("Error retrieving service %s/%s: %v", i.ObjectMeta.Namespace, pa.Backend.ServiceName, err) log.Errorf("Error while retrieving service information from k8s API %s/%s: %v", i.ObjectMeta.Namespace, pa.Backend.ServiceName, err)
return nil, err
}
if !exists {
log.Errorf("Service not found for %s/%s", i.ObjectMeta.Namespace, pa.Backend.ServiceName)
delete(templateObjects.Frontends, r.Host+pa.Path) delete(templateObjects.Frontends, r.Host+pa.Path)
continue continue
} }
@ -233,17 +238,17 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
} else { } else {
endpoints, exists, err := k8sClient.GetEndpoints(service.ObjectMeta.Namespace, service.ObjectMeta.Name) endpoints, exists, err := k8sClient.GetEndpoints(service.ObjectMeta.Namespace, service.ObjectMeta.Name)
if err != nil { if err != nil {
log.Errorf("Error while retrieving endpoints from k8s API %s/%s: %v", service.ObjectMeta.Namespace, service.ObjectMeta.Name, err) log.Errorf("Error retrieving endpoints %s/%s: %v", service.ObjectMeta.Namespace, service.ObjectMeta.Name, err)
continue return nil, err
} }
if !exists { if !exists {
log.Errorf("Service not found for %s/%s", service.ObjectMeta.Namespace, service.ObjectMeta.Name) log.Errorf("Endpoints not found for %s/%s", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
continue continue
} }
if len(endpoints.Subsets) == 0 { if len(endpoints.Subsets) == 0 {
log.Warnf("Endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name) log.Warnf("Service endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{ templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(int(port.Port)), URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(int(port.Port)),
Weight: 1, Weight: 1,

View file

@ -2,6 +2,7 @@ package provider
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"reflect" "reflect"
"strings" "strings"
@ -9,6 +10,7 @@ import (
"github.com/containous/traefik/provider/k8s" "github.com/containous/traefik/provider/k8s"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/davecgh/go-spew/spew"
"k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/pkg/apis/extensions/v1beta1" "k8s.io/client-go/pkg/apis/extensions/v1beta1"
"k8s.io/client-go/pkg/util/intstr" "k8s.io/client-go/pkg/util/intstr"
@ -1814,11 +1816,286 @@ func TestGetRuleTypeFromAnnotation(t *testing.T) {
} }
} }
func TestKubeAPIErrors(t *testing.T) {
ingresses := []*v1beta1.Ingress{{
ObjectMeta: v1.ObjectMeta{
Namespace: "testing",
},
Spec: v1beta1.IngressSpec{
Rules: []v1beta1.IngressRule{
{
Host: "foo",
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Path: "/bar",
Backend: v1beta1.IngressBackend{
ServiceName: "service1",
ServicePort: intstr.FromInt(80),
},
},
},
},
},
},
},
},
}}
services := []*v1.Service{{
ObjectMeta: v1.ObjectMeta{
Name: "service1",
UID: "1",
Namespace: "testing",
},
Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.1",
Ports: []v1.ServicePort{
{
Port: 80,
},
},
},
}}
endpoints := []*v1.Endpoints{}
watchChan := make(chan interface{})
apiErr := errors.New("failed kube api call")
testCases := []struct {
desc string
apiServiceErr error
apiEndpointsErr error
}{
{
desc: "failed service call",
apiServiceErr: apiErr,
},
{
desc: "failed endpoints call",
apiEndpointsErr: apiErr,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.desc, func(t *testing.T) {
t.Parallel()
client := clientMock{
ingresses: ingresses,
services: services,
endpoints: endpoints,
watchChan: watchChan,
apiServiceError: tc.apiServiceErr,
apiEndpointsError: tc.apiEndpointsErr,
}
provider := Kubernetes{}
if _, err := provider.loadIngresses(client); err != apiErr {
t.Errorf("Got error %v, wanted error %v", err, apiErr)
}
})
}
}
func TestMissingResources(t *testing.T) {
ingresses := []*v1beta1.Ingress{{
ObjectMeta: v1.ObjectMeta{
Namespace: "testing",
},
Spec: v1beta1.IngressSpec{
Rules: []v1beta1.IngressRule{
{
Host: "fully_working",
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Backend: v1beta1.IngressBackend{
ServiceName: "fully_working_service",
ServicePort: intstr.FromInt(80),
},
},
},
},
},
},
{
Host: "missing_service",
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Backend: v1beta1.IngressBackend{
ServiceName: "missing_service_service",
ServicePort: intstr.FromInt(80),
},
},
},
},
},
},
{
Host: "missing_endpoints",
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Backend: v1beta1.IngressBackend{
ServiceName: "missing_endpoints_service",
ServicePort: intstr.FromInt(80),
},
},
},
},
},
},
},
},
}}
services := []*v1.Service{
{
ObjectMeta: v1.ObjectMeta{
Name: "fully_working_service",
UID: "1",
Namespace: "testing",
},
Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.1",
Ports: []v1.ServicePort{
{
Port: 80,
},
},
},
},
{
ObjectMeta: v1.ObjectMeta{
Name: "missing_endpoints_service",
UID: "3",
Namespace: "testing",
},
Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.3",
Ports: []v1.ServicePort{
{
Port: 80,
},
},
},
},
}
endpoints := []*v1.Endpoints{
{
ObjectMeta: v1.ObjectMeta{
Name: "fully_working_service",
UID: "1",
Namespace: "testing",
},
Subsets: []v1.EndpointSubset{
{
Addresses: []v1.EndpointAddress{
{
IP: "10.10.0.1",
},
},
Ports: []v1.EndpointPort{
{
Port: 8080,
},
},
},
},
},
}
watchChan := make(chan interface{})
client := clientMock{
ingresses: ingresses,
services: services,
endpoints: endpoints,
watchChan: watchChan,
// TODO: Update all tests to cope with "properExists == true" correctly and remove flag.
// See https://github.com/containous/traefik/issues/1307
properExists: true,
}
provider := Kubernetes{}
actual, err := provider.loadIngresses(client)
if err != nil {
t.Fatalf("error %+v", err)
}
expected := &types.Configuration{
Backends: map[string]*types.Backend{
"fully_working": {
Servers: map[string]types.Server{
"http://10.10.0.1:8080": {
URL: "http://10.10.0.1:8080",
Weight: 1,
},
},
CircuitBreaker: nil,
LoadBalancer: &types.LoadBalancer{
Method: "wrr",
Sticky: false,
},
},
"missing_service": {
Servers: map[string]types.Server{},
LoadBalancer: &types.LoadBalancer{
Method: "wrr",
Sticky: false,
},
},
"missing_endpoints": {
Servers: map[string]types.Server{},
CircuitBreaker: nil,
LoadBalancer: &types.LoadBalancer{
Method: "wrr",
Sticky: false,
},
},
},
Frontends: map[string]*types.Frontend{
"fully_working": {
Backend: "fully_working",
PassHostHeader: true,
Routes: map[string]types.Route{
"fully_working": {
Rule: "Host:fully_working",
},
},
},
"missing_endpoints": {
Backend: "missing_endpoints",
PassHostHeader: true,
Routes: map[string]types.Route{
"missing_endpoints": {
Rule: "Host:missing_endpoints",
},
},
},
},
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("expected\n%v\ngot\n\n%v", spew.Sdump(expected), spew.Sdump(actual))
}
}
type clientMock struct { type clientMock struct {
ingresses []*v1beta1.Ingress ingresses []*v1beta1.Ingress
services []*v1.Service services []*v1.Service
endpoints []*v1.Endpoints endpoints []*v1.Endpoints
watchChan chan interface{} watchChan chan interface{}
apiServiceError error
apiEndpointsError error
properExists bool
} }
func (c clientMock) GetIngresses(namespaces k8s.Namespaces) []*v1beta1.Ingress { func (c clientMock) GetIngresses(namespaces k8s.Namespaces) []*v1beta1.Ingress {
@ -1833,20 +2110,33 @@ func (c clientMock) GetIngresses(namespaces k8s.Namespaces) []*v1beta1.Ingress {
} }
func (c clientMock) GetService(namespace, name string) (*v1.Service, bool, error) { func (c clientMock) GetService(namespace, name string) (*v1.Service, bool, error) {
if c.apiServiceError != nil {
return nil, false, c.apiServiceError
}
for _, service := range c.services { for _, service := range c.services {
if service.Namespace == namespace && service.Name == name { if service.Namespace == namespace && service.Name == name {
return service, true, nil return service, true, nil
} }
} }
return &v1.Service{}, true, nil return nil, false, nil
} }
func (c clientMock) GetEndpoints(namespace, name string) (*v1.Endpoints, bool, error) { func (c clientMock) GetEndpoints(namespace, name string) (*v1.Endpoints, bool, error) {
if c.apiEndpointsError != nil {
return nil, false, c.apiEndpointsError
}
for _, endpoints := range c.endpoints { for _, endpoints := range c.endpoints {
if endpoints.Namespace == namespace && endpoints.Name == name { if endpoints.Namespace == namespace && endpoints.Name == name {
return endpoints, true, nil return endpoints, true, nil
} }
} }
if c.properExists {
return nil, false, nil
}
return &v1.Endpoints{}, true, nil return &v1.Endpoints{}, true, nil
} }

View file

@ -80,7 +80,7 @@ func TestConfigurationErrors(t *testing.T) {
}, },
nil, nil,
}, },
expectedError: "Near line 1 (last key parsed 'Hello'): Expected key separator '=', but got '<' instead", expectedError: "Near line 1 (last key parsed 'Hello'): expected key separator '=', but got '<' instead",
funcMap: template.FuncMap{ funcMap: template.FuncMap{
"Foo": func() string { "Foo": func() string {
return "bar" return "bar"

View file

@ -667,7 +667,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
interval = time.Second * 30 interval = time.Second * 30
} }
} }
backendsHealthcheck[frontend.Backend] = healthcheck.NewBackendHealthCheck(configuration.Backends[frontend.Backend].HealthCheck.URL, interval, rebalancer) backendsHealthcheck[frontend.Backend] = healthcheck.NewBackendHealthCheck(configuration.Backends[frontend.Backend].HealthCheck.Path, interval, rebalancer)
} }
} }
case types.Wrr: case types.Wrr:
@ -701,7 +701,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
interval = time.Second * 30 interval = time.Second * 30
} }
} }
backendsHealthcheck[frontend.Backend] = healthcheck.NewBackendHealthCheck(configuration.Backends[frontend.Backend].HealthCheck.URL, interval, rr) backendsHealthcheck[frontend.Backend] = healthcheck.NewBackendHealthCheck(configuration.Backends[frontend.Backend].HealthCheck.Path, interval, rr)
} }
} }
maxConns := configuration.Backends[frontend.Backend].MaxConn maxConns := configuration.Backends[frontend.Backend].MaxConn

View file

@ -261,7 +261,6 @@ func run(traefikConfiguration *TraefikConfiguration) {
} }
}(t) }(t)
} }
log.Info(t.String())
server.Wait() server.Wait()
log.Info("Shutting down") log.Info("Shutting down")
} }

View file

@ -58,11 +58,13 @@
# #
# ProvidersThrottleDuration = "5s" # ProvidersThrottleDuration = "5s"
# If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used. # Controls the maximum idle (keep-alive) connections to keep per-host. If zero, DefaultMaxIdleConnsPerHost
# If you encounter 'too many open files' errors, you can either change this value, or change `ulimit` value. # from the Go standard library net/http module is used.
# If you encounter 'too many open files' errors, you can either increase this
# value or change the `ulimit`.
# #
# Optional # Optional
# Default: http.DefaultMaxIdleConnsPerHost # Default: 200
# #
# MaxIdleConnsPerHost = 200 # MaxIdleConnsPerHost = 200
@ -652,12 +654,14 @@
# Zookeeper timeout (in seconds) # Zookeeper timeout (in seconds)
# #
# Optional # Optional
# Default: 30
# #
# ZkDetectionTimeout = 30 # ZkDetectionTimeout = 30
# Polling interval (in seconds) # Polling interval (in seconds)
# #
# Optional # Optional
# Default: 30
# #
# RefreshSeconds = 30 # RefreshSeconds = 30
@ -670,8 +674,9 @@
# HTTP Timeout (in seconds) # HTTP Timeout (in seconds)
# #
# Optional # Optional
# Default: 30
# #
# StateTimeoutSecond = "host" # StateTimeoutSecond = "30"
################################################################ ################################################################
# Kubernetes Ingress configuration backend # Kubernetes Ingress configuration backend

View file

@ -39,7 +39,7 @@ type CircuitBreaker struct {
// HealthCheck holds HealthCheck configuration // HealthCheck holds HealthCheck configuration
type HealthCheck struct { type HealthCheck struct {
URL string `json:"url,omitempty"` Path string `json:"path,omitempty"`
Interval string `json:"interval,omitempty"` Interval string `json:"interval,omitempty"`
} }

21
vendor/github.com/ArthurHlt/go-eureka-client/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Arthur Halet
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.

View file

@ -1,11 +1,12 @@
package eureka package eureka
import ( import (
"encoding/xml" "encoding/xml"
"strings" "strings"
) )
func (c *Client) GetApplications() (*Applications, error) { func (c *Client) GetApplications() (*Applications, error) {
response, err := c.Get("apps"); response, err := c.Get("apps")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -17,7 +18,7 @@ func (c *Client) GetApplications() (*Applications, error) {
func (c *Client) GetApplication(appId string) (*Application, error) { func (c *Client) GetApplication(appId string) (*Application, error) {
values := []string{"apps", appId} values := []string{"apps", appId}
path := strings.Join(values, "/") path := strings.Join(values, "/")
response, err := c.Get(path); response, err := c.Get(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -25,10 +26,11 @@ func (c *Client) GetApplication(appId string) (*Application, error) {
err = xml.Unmarshal(response.Body, application) err = xml.Unmarshal(response.Body, application)
return application, err return application, err
} }
func (c *Client) GetInstance(appId, instanceId string) (*InstanceInfo, error) { func (c *Client) GetInstance(appId, instanceId string) (*InstanceInfo, error) {
values := []string{"apps", appId, instanceId} values := []string{"apps", appId, instanceId}
path := strings.Join(values, "/") path := strings.Join(values, "/")
response, err := c.Get(path); response, err := c.Get(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -36,3 +38,27 @@ func (c *Client) GetInstance(appId, instanceId string) (*InstanceInfo, error) {
err = xml.Unmarshal(response.Body, instance) err = xml.Unmarshal(response.Body, instance)
return instance, err return instance, err
} }
func (c *Client) GetVIP(vipId string) (*Applications, error) {
values := []string{"vips", vipId}
path := strings.Join(values, "/")
response, err := c.Get(path)
if err != nil {
return nil, err
}
var applications *Applications = new(Applications)
err = xml.Unmarshal(response.Body, applications)
return applications, err
}
func (c *Client) GetSVIP(svipId string) (*Applications, error) {
values := []string{"svips", svipId}
path := strings.Join(values, "/")
response, err := c.Get(path)
if err != nil {
return nil, err
}
var applications *Applications = new(Applications)
err = xml.Unmarshal(response.Body, applications)
return applications, err
}

View file

@ -67,7 +67,7 @@ type InstanceInfo struct {
type DataCenterInfo struct { type DataCenterInfo struct {
Name string `xml:"name" json:"name"` Name string `xml:"name" json:"name"`
Class string `xml:"class,attr" json:"@class"` Class string `xml:"class,attr" json:"@class"`
Metadata DataCenterMetadata `xml:"metadata,omitempty" json:"metadata,omitempty"` Metadata *DataCenterMetadata `xml:"metadata,omitempty" json:"metadata,omitempty"`
} }
type DataCenterMetadata struct { type DataCenterMetadata struct {
@ -106,6 +106,8 @@ func NewRawRequest(method, relativePath string, body []byte, cancel <-chan bool)
func NewInstanceInfo(hostName, app, ip string, port int, ttl uint, isSsl bool) *InstanceInfo { func NewInstanceInfo(hostName, app, ip string, port int, ttl uint, isSsl bool) *InstanceInfo {
dataCenterInfo := &DataCenterInfo{ dataCenterInfo := &DataCenterInfo{
Name: "MyOwn", Name: "MyOwn",
Class: "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
Metadata: nil,
} }
leaseInfo := &LeaseInfo{ leaseInfo := &LeaseInfo{
EvictionDurationInSecs: ttl, EvictionDurationInSecs: ttl,

View file

@ -1,26 +1,26 @@
package gominlog package gominlog
import ( import (
"fmt"
"github.com/daviddengcn/go-colortext"
"io"
"log" "log"
"os" "os"
"regexp" "fmt"
"runtime" "runtime"
"github.com/fatih/color"
"regexp"
"strings" "strings"
"io"
) )
type Level int type Level int
const ( const (
Loff = Level(^uint(0) >> 1) Loff = Level(^uint(0) >> 1)
Lsevere = Level(1000) Lsevere = Level(1000)
Lerror = Level(900) Lerror = Level(900)
Lwarning = Level(800) Lwarning = Level(800)
Linfo = Level(700) Linfo = Level(700)
Ldebug = Level(600) Ldebug = Level(600)
Lall = Level(-Loff - 1) Lall = Level(-Loff - 1)
) )
type MinLog struct { type MinLog struct {
@ -54,6 +54,14 @@ func NewMinLog(appName string, level Level, withColor bool, flag int) *MinLog {
minLog.level = level minLog.level = level
return minLog return minLog
} }
func NewMinLogWithWriter(appName string, level Level, withColor bool, flag int, logWriter io.Writer) *MinLog {
minLog := &MinLog{}
minLog.log = log.New(logWriter, "", flag)
minLog.isColorized = withColor
minLog.packageName = appName
minLog.level = level
return minLog
}
func NewMinLogWithLogger(packageName string, level Level, withColor bool, logger *log.Logger) *MinLog { func NewMinLogWithLogger(packageName string, level Level, withColor bool, logger *log.Logger) *MinLog {
minLog := &MinLog{} minLog := &MinLog{}
minLog.log = logger minLog.log = logger
@ -89,10 +97,11 @@ func (this *MinLog) IsColorized() bool {
return this.isColorized return this.isColorized
} }
func (this *MinLog) GetLogger() *log.Logger { func (this *MinLog) GetLogger() *log.Logger {
return this.log return this.log
} }
func (this *MinLog) logMessage(typeLog string, colorFg ct.Color, colorBg ct.Color, args ...interface{}) { func (this *MinLog) logMessage(typeLog string, colorFg color.Attribute, colorBg color.Attribute, args ...interface{}) {
var text string var text string
msg := "" msg := ""
flags := this.log.Flags() flags := this.log.Flags()
@ -100,7 +109,7 @@ func (this *MinLog) logMessage(typeLog string, colorFg ct.Color, colorBg ct.Colo
msg += this.trace() msg += this.trace()
this.log.SetFlags(flags - log.Lshortfile) this.log.SetFlags(flags - log.Lshortfile)
} }
text, ok := args[0].(string) text, ok := args[0].(string);
if !ok { if !ok {
panic("Firt argument should be a string") panic("Firt argument should be a string")
} }
@ -113,51 +122,47 @@ func (this *MinLog) logMessage(typeLog string, colorFg ct.Color, colorBg ct.Colo
this.writeMsgInLogger(msg, colorFg, colorBg) this.writeMsgInLogger(msg, colorFg, colorBg)
this.log.SetFlags(flags) this.log.SetFlags(flags)
} }
func (this *MinLog) writeMsgInLogger(msg string, colorFg ct.Color, colorBg ct.Color) { func (this *MinLog) writeMsgInLogger(msg string, colorFg color.Attribute, colorBg color.Attribute) {
if this.isColorized && colorFg > 0 { if this.isColorized && int(colorBg) == 0 {
ct.Foreground(colorFg, false) msg = color.New(colorFg).Sprint(msg)
} } else if this.isColorized {
if this.isColorized && colorBg > 0 { msg = color.New(colorFg, colorBg).Sprint(msg)
ct.ChangeColor(colorFg, false, colorBg, false)
} }
this.log.Print(msg) this.log.Print(msg)
if this.isColorized {
ct.ResetColor()
}
} }
func (this *MinLog) Error(args ...interface{}) { func (this *MinLog) Error(args ...interface{}) {
if this.level > Lerror { if this.level > Lerror {
return return
} }
this.logMessage("ERROR", ct.Red, 0, args...) this.logMessage("ERROR", color.FgRed, 0, args...)
} }
func (this *MinLog) Severe(args ...interface{}) { func (this *MinLog) Severe(args ...interface{}) {
if this.level > Lsevere { if this.level > Lsevere {
return return
} }
this.logMessage("SEVERE", ct.Red, ct.Yellow, args...) this.logMessage("SEVERE", color.FgRed, color.BgYellow, args...)
} }
func (this *MinLog) Debug(args ...interface{}) { func (this *MinLog) Debug(args ...interface{}) {
if this.level > Ldebug { if this.level > Ldebug {
return return
} }
this.logMessage("DEBUG", ct.Blue, 0, args...) this.logMessage("DEBUG", color.FgBlue, 0, args...)
} }
func (this *MinLog) Info(args ...interface{}) { func (this *MinLog) Info(args ...interface{}) {
if this.level > Linfo { if this.level > Linfo {
return return
} }
this.logMessage("INFO", ct.Cyan, 0, args...) this.logMessage("INFO", color.FgCyan, 0, args...)
} }
func (this *MinLog) Warning(args ...interface{}) { func (this *MinLog) Warning(args ...interface{}) {
if this.level > Lwarning { if this.level > Lwarning {
return return
} }
this.logMessage("WARNING", ct.Yellow, 0, args...) this.logMessage("WARNING", color.FgYellow, 0, args...)
} }
func (this *MinLog) trace() string { func (this *MinLog) trace() string {
var shortFile string var shortFile string
@ -167,7 +172,7 @@ func (this *MinLog) trace() string {
file, line := f.FileLine(pc[2]) file, line := f.FileLine(pc[2])
if this.packageName == "" { if this.packageName == "" {
execFileSplit := strings.Split(os.Args[0], "/") execFileSplit := strings.Split(os.Args[0], "/")
this.packageName = execFileSplit[len(execFileSplit)-1] this.packageName = execFileSplit[len(execFileSplit) - 1]
} }
regex, err := regexp.Compile(regexp.QuoteMeta(this.packageName) + "/(.*)") regex, err := regexp.Compile(regexp.QuoteMeta(this.packageName) + "/(.*)")
if err != nil { if err != nil {
@ -176,7 +181,7 @@ func (this *MinLog) trace() string {
subMatch := regex.FindStringSubmatch(file) subMatch := regex.FindStringSubmatch(file)
if len(subMatch) < 2 { if len(subMatch) < 2 {
fileSplit := strings.Split(file, "/") fileSplit := strings.Split(file, "/")
shortFile = fileSplit[len(fileSplit)-1] shortFile = fileSplit[len(fileSplit) - 1]
} else { } else {
shortFile = subMatch[1] shortFile = subMatch[1]
} }

View file

@ -4,7 +4,7 @@ files via reflection. There is also support for delaying decoding with
the Primitive type, and querying the set of keys in a TOML document with the the Primitive type, and querying the set of keys in a TOML document with the
MetaData type. MetaData type.
The specification implemented: https://github.com/mojombo/toml The specification implemented: https://github.com/toml-lang/toml
The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify
whether a file is a valid TOML document. It can also be used to print the whether a file is a valid TOML document. It can also be used to print the

View file

@ -241,7 +241,7 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
func (enc *Encoder) eTable(key Key, rv reflect.Value) { func (enc *Encoder) eTable(key Key, rv reflect.Value) {
panicIfInvalidKey(key) panicIfInvalidKey(key)
if len(key) == 1 { if len(key) == 1 {
// Output an extra new line between top-level tables. // Output an extra newline between top-level tables.
// (The newline isn't written if nothing else has been written though.) // (The newline isn't written if nothing else has been written though.)
enc.newline() enc.newline()
} }

View file

@ -30,24 +30,28 @@ const (
itemArrayTableEnd itemArrayTableEnd
itemKeyStart itemKeyStart
itemCommentStart itemCommentStart
itemInlineTableStart
itemInlineTableEnd
) )
const ( const (
eof = 0 eof = 0
tableStart = '[' comma = ','
tableEnd = ']' tableStart = '['
arrayTableStart = '[' tableEnd = ']'
arrayTableEnd = ']' arrayTableStart = '['
tableSep = '.' arrayTableEnd = ']'
keySep = '=' tableSep = '.'
arrayStart = '[' keySep = '='
arrayEnd = ']' arrayStart = '['
arrayValTerm = ',' arrayEnd = ']'
commentStart = '#' commentStart = '#'
stringStart = '"' stringStart = '"'
stringEnd = '"' stringEnd = '"'
rawStringStart = '\'' rawStringStart = '\''
rawStringEnd = '\'' rawStringEnd = '\''
inlineTableStart = '{'
inlineTableEnd = '}'
) )
type stateFn func(lx *lexer) stateFn type stateFn func(lx *lexer) stateFn
@ -56,11 +60,18 @@ type lexer struct {
input string input string
start int start int
pos int pos int
width int
line int line int
state stateFn state stateFn
items chan item items chan item
// Allow for backing up up to three runes.
// This is necessary because TOML contains 3-rune tokens (""" and ''').
prevWidths [3]int
nprev int // how many of prevWidths are in use
// If we emit an eof, we can still back up, but it is not OK to call
// next again.
atEOF bool
// A stack of state functions used to maintain context. // A stack of state functions used to maintain context.
// The idea is to reuse parts of the state machine in various places. // The idea is to reuse parts of the state machine in various places.
// For example, values can appear at the top level or within arbitrarily // For example, values can appear at the top level or within arbitrarily
@ -88,7 +99,7 @@ func (lx *lexer) nextItem() item {
func lex(input string) *lexer { func lex(input string) *lexer {
lx := &lexer{ lx := &lexer{
input: input + "\n", input: input,
state: lexTop, state: lexTop,
line: 1, line: 1,
items: make(chan item, 10), items: make(chan item, 10),
@ -103,7 +114,7 @@ func (lx *lexer) push(state stateFn) {
func (lx *lexer) pop() stateFn { func (lx *lexer) pop() stateFn {
if len(lx.stack) == 0 { if len(lx.stack) == 0 {
return lx.errorf("BUG in lexer: no states to pop.") return lx.errorf("BUG in lexer: no states to pop")
} }
last := lx.stack[len(lx.stack)-1] last := lx.stack[len(lx.stack)-1]
lx.stack = lx.stack[0 : len(lx.stack)-1] lx.stack = lx.stack[0 : len(lx.stack)-1]
@ -125,16 +136,25 @@ func (lx *lexer) emitTrim(typ itemType) {
} }
func (lx *lexer) next() (r rune) { func (lx *lexer) next() (r rune) {
if lx.atEOF {
panic("next called after EOF")
}
if lx.pos >= len(lx.input) { if lx.pos >= len(lx.input) {
lx.width = 0 lx.atEOF = true
return eof return eof
} }
if lx.input[lx.pos] == '\n' { if lx.input[lx.pos] == '\n' {
lx.line++ lx.line++
} }
r, lx.width = utf8.DecodeRuneInString(lx.input[lx.pos:]) lx.prevWidths[2] = lx.prevWidths[1]
lx.pos += lx.width lx.prevWidths[1] = lx.prevWidths[0]
if lx.nprev < 3 {
lx.nprev++
}
r, w := utf8.DecodeRuneInString(lx.input[lx.pos:])
lx.prevWidths[0] = w
lx.pos += w
return r return r
} }
@ -143,9 +163,20 @@ func (lx *lexer) ignore() {
lx.start = lx.pos lx.start = lx.pos
} }
// backup steps back one rune. Can be called only once per call of next. // backup steps back one rune. Can be called only twice between calls to next.
func (lx *lexer) backup() { func (lx *lexer) backup() {
lx.pos -= lx.width if lx.atEOF {
lx.atEOF = false
return
}
if lx.nprev < 1 {
panic("backed up too far")
}
w := lx.prevWidths[0]
lx.prevWidths[0] = lx.prevWidths[1]
lx.prevWidths[1] = lx.prevWidths[2]
lx.nprev--
lx.pos -= w
if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' { if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' {
lx.line-- lx.line--
} }
@ -182,7 +213,7 @@ func (lx *lexer) skip(pred func(rune) bool) {
// errorf stops all lexing by emitting an error and returning `nil`. // errorf stops all lexing by emitting an error and returning `nil`.
// Note that any value that is a character is escaped if it's a special // Note that any value that is a character is escaped if it's a special
// character (new lines, tabs, etc.). // character (newlines, tabs, etc.).
func (lx *lexer) errorf(format string, values ...interface{}) stateFn { func (lx *lexer) errorf(format string, values ...interface{}) stateFn {
lx.items <- item{ lx.items <- item{
itemError, itemError,
@ -198,7 +229,6 @@ func lexTop(lx *lexer) stateFn {
if isWhitespace(r) || isNL(r) { if isWhitespace(r) || isNL(r) {
return lexSkip(lx, lexTop) return lexSkip(lx, lexTop)
} }
switch r { switch r {
case commentStart: case commentStart:
lx.push(lexTop) lx.push(lexTop)
@ -207,7 +237,7 @@ func lexTop(lx *lexer) stateFn {
return lexTableStart return lexTableStart
case eof: case eof:
if lx.pos > lx.start { if lx.pos > lx.start {
return lx.errorf("Unexpected EOF.") return lx.errorf("unexpected EOF")
} }
lx.emit(itemEOF) lx.emit(itemEOF)
return nil return nil
@ -222,12 +252,12 @@ func lexTop(lx *lexer) stateFn {
// lexTopEnd is entered whenever a top-level item has been consumed. (A value // lexTopEnd is entered whenever a top-level item has been consumed. (A value
// or a table.) It must see only whitespace, and will turn back to lexTop // or a table.) It must see only whitespace, and will turn back to lexTop
// upon a new line. If it sees EOF, it will quit the lexer successfully. // upon a newline. If it sees EOF, it will quit the lexer successfully.
func lexTopEnd(lx *lexer) stateFn { func lexTopEnd(lx *lexer) stateFn {
r := lx.next() r := lx.next()
switch { switch {
case r == commentStart: case r == commentStart:
// a comment will read to a new line for us. // a comment will read to a newline for us.
lx.push(lexTop) lx.push(lexTop)
return lexCommentStart return lexCommentStart
case isWhitespace(r): case isWhitespace(r):
@ -236,11 +266,11 @@ func lexTopEnd(lx *lexer) stateFn {
lx.ignore() lx.ignore()
return lexTop return lexTop
case r == eof: case r == eof:
lx.ignore() lx.emit(itemEOF)
return lexTop return nil
} }
return lx.errorf("Expected a top-level item to end with a new line, "+ return lx.errorf("expected a top-level item to end with a newline, "+
"comment or EOF, but got %q instead.", r) "comment, or EOF, but got %q instead", r)
} }
// lexTable lexes the beginning of a table. Namely, it makes sure that // lexTable lexes the beginning of a table. Namely, it makes sure that
@ -267,8 +297,8 @@ func lexTableEnd(lx *lexer) stateFn {
func lexArrayTableEnd(lx *lexer) stateFn { func lexArrayTableEnd(lx *lexer) stateFn {
if r := lx.next(); r != arrayTableEnd { if r := lx.next(); r != arrayTableEnd {
return lx.errorf("Expected end of table array name delimiter %q, "+ return lx.errorf("expected end of table array name delimiter %q, "+
"but got %q instead.", arrayTableEnd, r) "but got %q instead", arrayTableEnd, r)
} }
lx.emit(itemArrayTableEnd) lx.emit(itemArrayTableEnd)
return lexTopEnd return lexTopEnd
@ -278,11 +308,11 @@ func lexTableNameStart(lx *lexer) stateFn {
lx.skip(isWhitespace) lx.skip(isWhitespace)
switch r := lx.peek(); { switch r := lx.peek(); {
case r == tableEnd || r == eof: case r == tableEnd || r == eof:
return lx.errorf("Unexpected end of table name. (Table names cannot " + return lx.errorf("unexpected end of table name " +
"be empty.)") "(table names cannot be empty)")
case r == tableSep: case r == tableSep:
return lx.errorf("Unexpected table separator. (Table names cannot " + return lx.errorf("unexpected table separator " +
"be empty.)") "(table names cannot be empty)")
case r == stringStart || r == rawStringStart: case r == stringStart || r == rawStringStart:
lx.ignore() lx.ignore()
lx.push(lexTableNameEnd) lx.push(lexTableNameEnd)
@ -317,8 +347,8 @@ func lexTableNameEnd(lx *lexer) stateFn {
case r == tableEnd: case r == tableEnd:
return lx.pop() return lx.pop()
default: default:
return lx.errorf("Expected '.' or ']' to end table name, but got %q "+ return lx.errorf("expected '.' or ']' to end table name, "+
"instead.", r) "but got %q instead", r)
} }
} }
@ -328,7 +358,7 @@ func lexKeyStart(lx *lexer) stateFn {
r := lx.peek() r := lx.peek()
switch { switch {
case r == keySep: case r == keySep:
return lx.errorf("Unexpected key separator %q.", keySep) return lx.errorf("unexpected key separator %q", keySep)
case isWhitespace(r) || isNL(r): case isWhitespace(r) || isNL(r):
lx.next() lx.next()
return lexSkip(lx, lexKeyStart) return lexSkip(lx, lexKeyStart)
@ -359,7 +389,7 @@ func lexBareKey(lx *lexer) stateFn {
lx.emit(itemText) lx.emit(itemText)
return lexKeyEnd return lexKeyEnd
default: default:
return lx.errorf("Bare keys cannot contain %q.", r) return lx.errorf("bare keys cannot contain %q", r)
} }
} }
@ -372,7 +402,7 @@ func lexKeyEnd(lx *lexer) stateFn {
case isWhitespace(r): case isWhitespace(r):
return lexSkip(lx, lexKeyEnd) return lexSkip(lx, lexKeyEnd)
default: default:
return lx.errorf("Expected key separator %q, but got %q instead.", return lx.errorf("expected key separator %q, but got %q instead",
keySep, r) keySep, r)
} }
} }
@ -381,9 +411,8 @@ func lexKeyEnd(lx *lexer) stateFn {
// lexValue will ignore whitespace. // lexValue will ignore whitespace.
// After a value is lexed, the last state on the next is popped and returned. // After a value is lexed, the last state on the next is popped and returned.
func lexValue(lx *lexer) stateFn { func lexValue(lx *lexer) stateFn {
// We allow whitespace to precede a value, but NOT new lines. // We allow whitespace to precede a value, but NOT newlines.
// In array syntax, the array states are responsible for ignoring new // In array syntax, the array states are responsible for ignoring newlines.
// lines.
r := lx.next() r := lx.next()
switch { switch {
case isWhitespace(r): case isWhitespace(r):
@ -397,6 +426,10 @@ func lexValue(lx *lexer) stateFn {
lx.ignore() lx.ignore()
lx.emit(itemArray) lx.emit(itemArray)
return lexArrayValue return lexArrayValue
case inlineTableStart:
lx.ignore()
lx.emit(itemInlineTableStart)
return lexInlineTableValue
case stringStart: case stringStart:
if lx.accept(stringStart) { if lx.accept(stringStart) {
if lx.accept(stringStart) { if lx.accept(stringStart) {
@ -420,7 +453,7 @@ func lexValue(lx *lexer) stateFn {
case '+', '-': case '+', '-':
return lexNumberStart return lexNumberStart
case '.': // special error case, be kind to users case '.': // special error case, be kind to users
return lx.errorf("Floats must start with a digit, not '.'.") return lx.errorf("floats must start with a digit, not '.'")
} }
if unicode.IsLetter(r) { if unicode.IsLetter(r) {
// Be permissive here; lexBool will give a nice error if the // Be permissive here; lexBool will give a nice error if the
@ -430,11 +463,11 @@ func lexValue(lx *lexer) stateFn {
lx.backup() lx.backup()
return lexBool return lexBool
} }
return lx.errorf("Expected value but found %q instead.", r) return lx.errorf("expected value but found %q instead", r)
} }
// lexArrayValue consumes one value in an array. It assumes that '[' or ',' // lexArrayValue consumes one value in an array. It assumes that '[' or ','
// have already been consumed. All whitespace and new lines are ignored. // have already been consumed. All whitespace and newlines are ignored.
func lexArrayValue(lx *lexer) stateFn { func lexArrayValue(lx *lexer) stateFn {
r := lx.next() r := lx.next()
switch { switch {
@ -443,10 +476,11 @@ func lexArrayValue(lx *lexer) stateFn {
case r == commentStart: case r == commentStart:
lx.push(lexArrayValue) lx.push(lexArrayValue)
return lexCommentStart return lexCommentStart
case r == arrayValTerm: case r == comma:
return lx.errorf("Unexpected array value terminator %q.", return lx.errorf("unexpected comma")
arrayValTerm)
case r == arrayEnd: case r == arrayEnd:
// NOTE(caleb): The spec isn't clear about whether you can have
// a trailing comma or not, so we'll allow it.
return lexArrayEnd return lexArrayEnd
} }
@ -455,8 +489,9 @@ func lexArrayValue(lx *lexer) stateFn {
return lexValue return lexValue
} }
// lexArrayValueEnd consumes the cruft between values of an array. Namely, // lexArrayValueEnd consumes everything between the end of an array value and
// it ignores whitespace and expects either a ',' or a ']'. // the next value (or the end of the array): it ignores whitespace and newlines
// and expects either a ',' or a ']'.
func lexArrayValueEnd(lx *lexer) stateFn { func lexArrayValueEnd(lx *lexer) stateFn {
r := lx.next() r := lx.next()
switch { switch {
@ -465,31 +500,88 @@ func lexArrayValueEnd(lx *lexer) stateFn {
case r == commentStart: case r == commentStart:
lx.push(lexArrayValueEnd) lx.push(lexArrayValueEnd)
return lexCommentStart return lexCommentStart
case r == arrayValTerm: case r == comma:
lx.ignore() lx.ignore()
return lexArrayValue // move on to the next value return lexArrayValue // move on to the next value
case r == arrayEnd: case r == arrayEnd:
return lexArrayEnd return lexArrayEnd
} }
return lx.errorf("Expected an array value terminator %q or an array "+ return lx.errorf(
"terminator %q, but got %q instead.", arrayValTerm, arrayEnd, r) "expected a comma or array terminator %q, but got %q instead",
arrayEnd, r,
)
} }
// lexArrayEnd finishes the lexing of an array. It assumes that a ']' has // lexArrayEnd finishes the lexing of an array.
// just been consumed. // It assumes that a ']' has just been consumed.
func lexArrayEnd(lx *lexer) stateFn { func lexArrayEnd(lx *lexer) stateFn {
lx.ignore() lx.ignore()
lx.emit(itemArrayEnd) lx.emit(itemArrayEnd)
return lx.pop() return lx.pop()
} }
// lexInlineTableValue consumes one key/value pair in an inline table.
// It assumes that '{' or ',' have already been consumed. Whitespace is ignored.
func lexInlineTableValue(lx *lexer) stateFn {
r := lx.next()
switch {
case isWhitespace(r):
return lexSkip(lx, lexInlineTableValue)
case isNL(r):
return lx.errorf("newlines not allowed within inline tables")
case r == commentStart:
lx.push(lexInlineTableValue)
return lexCommentStart
case r == comma:
return lx.errorf("unexpected comma")
case r == inlineTableEnd:
return lexInlineTableEnd
}
lx.backup()
lx.push(lexInlineTableValueEnd)
return lexKeyStart
}
// lexInlineTableValueEnd consumes everything between the end of an inline table
// key/value pair and the next pair (or the end of the table):
// it ignores whitespace and expects either a ',' or a '}'.
func lexInlineTableValueEnd(lx *lexer) stateFn {
r := lx.next()
switch {
case isWhitespace(r):
return lexSkip(lx, lexInlineTableValueEnd)
case isNL(r):
return lx.errorf("newlines not allowed within inline tables")
case r == commentStart:
lx.push(lexInlineTableValueEnd)
return lexCommentStart
case r == comma:
lx.ignore()
return lexInlineTableValue
case r == inlineTableEnd:
return lexInlineTableEnd
}
return lx.errorf("expected a comma or an inline table terminator %q, "+
"but got %q instead", inlineTableEnd, r)
}
// lexInlineTableEnd finishes the lexing of an inline table.
// It assumes that a '}' has just been consumed.
func lexInlineTableEnd(lx *lexer) stateFn {
lx.ignore()
lx.emit(itemInlineTableEnd)
return lx.pop()
}
// lexString consumes the inner contents of a string. It assumes that the // lexString consumes the inner contents of a string. It assumes that the
// beginning '"' has already been consumed and ignored. // beginning '"' has already been consumed and ignored.
func lexString(lx *lexer) stateFn { func lexString(lx *lexer) stateFn {
r := lx.next() r := lx.next()
switch { switch {
case r == eof:
return lx.errorf("unexpected EOF")
case isNL(r): case isNL(r):
return lx.errorf("Strings cannot contain new lines.") return lx.errorf("strings cannot contain newlines")
case r == '\\': case r == '\\':
lx.push(lexString) lx.push(lexString)
return lexStringEscape return lexStringEscape
@ -506,11 +598,12 @@ func lexString(lx *lexer) stateFn {
// lexMultilineString consumes the inner contents of a string. It assumes that // lexMultilineString consumes the inner contents of a string. It assumes that
// the beginning '"""' has already been consumed and ignored. // the beginning '"""' has already been consumed and ignored.
func lexMultilineString(lx *lexer) stateFn { func lexMultilineString(lx *lexer) stateFn {
r := lx.next() switch lx.next() {
switch { case eof:
case r == '\\': return lx.errorf("unexpected EOF")
case '\\':
return lexMultilineStringEscape return lexMultilineStringEscape
case r == stringEnd: case stringEnd:
if lx.accept(stringEnd) { if lx.accept(stringEnd) {
if lx.accept(stringEnd) { if lx.accept(stringEnd) {
lx.backup() lx.backup()
@ -534,8 +627,10 @@ func lexMultilineString(lx *lexer) stateFn {
func lexRawString(lx *lexer) stateFn { func lexRawString(lx *lexer) stateFn {
r := lx.next() r := lx.next()
switch { switch {
case r == eof:
return lx.errorf("unexpected EOF")
case isNL(r): case isNL(r):
return lx.errorf("Strings cannot contain new lines.") return lx.errorf("strings cannot contain newlines")
case r == rawStringEnd: case r == rawStringEnd:
lx.backup() lx.backup()
lx.emit(itemRawString) lx.emit(itemRawString)
@ -547,12 +642,13 @@ func lexRawString(lx *lexer) stateFn {
} }
// lexMultilineRawString consumes a raw string. Nothing can be escaped in such // lexMultilineRawString consumes a raw string. Nothing can be escaped in such
// a string. It assumes that the beginning "'" has already been consumed and // a string. It assumes that the beginning "'''" has already been consumed and
// ignored. // ignored.
func lexMultilineRawString(lx *lexer) stateFn { func lexMultilineRawString(lx *lexer) stateFn {
r := lx.next() switch lx.next() {
switch { case eof:
case r == rawStringEnd: return lx.errorf("unexpected EOF")
case rawStringEnd:
if lx.accept(rawStringEnd) { if lx.accept(rawStringEnd) {
if lx.accept(rawStringEnd) { if lx.accept(rawStringEnd) {
lx.backup() lx.backup()
@ -605,10 +701,9 @@ func lexStringEscape(lx *lexer) stateFn {
case 'U': case 'U':
return lexLongUnicodeEscape return lexLongUnicodeEscape
} }
return lx.errorf("Invalid escape character %q. Only the following "+ return lx.errorf("invalid escape character %q; only the following "+
"escape characters are allowed: "+ "escape characters are allowed: "+
"\\b, \\t, \\n, \\f, \\r, \\\", \\/, \\\\, "+ `\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r)
"\\uXXXX and \\UXXXXXXXX.", r)
} }
func lexShortUnicodeEscape(lx *lexer) stateFn { func lexShortUnicodeEscape(lx *lexer) stateFn {
@ -616,8 +711,8 @@ func lexShortUnicodeEscape(lx *lexer) stateFn {
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
r = lx.next() r = lx.next()
if !isHexadecimal(r) { if !isHexadecimal(r) {
return lx.errorf("Expected four hexadecimal digits after '\\u', "+ return lx.errorf(`expected four hexadecimal digits after '\u', `+
"but got '%s' instead.", lx.current()) "but got %q instead", lx.current())
} }
} }
return lx.pop() return lx.pop()
@ -628,8 +723,8 @@ func lexLongUnicodeEscape(lx *lexer) stateFn {
for i := 0; i < 8; i++ { for i := 0; i < 8; i++ {
r = lx.next() r = lx.next()
if !isHexadecimal(r) { if !isHexadecimal(r) {
return lx.errorf("Expected eight hexadecimal digits after '\\U', "+ return lx.errorf(`expected eight hexadecimal digits after '\U', `+
"but got '%s' instead.", lx.current()) "but got %q instead", lx.current())
} }
} }
return lx.pop() return lx.pop()
@ -647,9 +742,9 @@ func lexNumberOrDateStart(lx *lexer) stateFn {
case 'e', 'E': case 'e', 'E':
return lexFloat return lexFloat
case '.': case '.':
return lx.errorf("Floats must start with a digit, not '.'.") return lx.errorf("floats must start with a digit, not '.'")
} }
return lx.errorf("Expected a digit but got %q.", r) return lx.errorf("expected a digit but got %q", r)
} }
// lexNumberOrDate consumes either an integer, float or datetime. // lexNumberOrDate consumes either an integer, float or datetime.
@ -697,9 +792,9 @@ func lexNumberStart(lx *lexer) stateFn {
r := lx.next() r := lx.next()
if !isDigit(r) { if !isDigit(r) {
if r == '.' { if r == '.' {
return lx.errorf("Floats must start with a digit, not '.'.") return lx.errorf("floats must start with a digit, not '.'")
} }
return lx.errorf("Expected a digit but got %q.", r) return lx.errorf("expected a digit but got %q", r)
} }
return lexNumber return lexNumber
} }
@ -745,7 +840,7 @@ func lexBool(lx *lexer) stateFn {
var rs []rune var rs []rune
for { for {
r := lx.next() r := lx.next()
if r == eof || isWhitespace(r) || isNL(r) { if !unicode.IsLetter(r) {
lx.backup() lx.backup()
break break
} }
@ -757,7 +852,7 @@ func lexBool(lx *lexer) stateFn {
lx.emit(itemBool) lx.emit(itemBool)
return lx.pop() return lx.pop()
} }
return lx.errorf("Expected value but found %q instead.", s) return lx.errorf("expected value but found %q instead", s)
} }
// lexCommentStart begins the lexing of a comment. It will emit // lexCommentStart begins the lexing of a comment. It will emit
@ -769,7 +864,7 @@ func lexCommentStart(lx *lexer) stateFn {
} }
// lexComment lexes an entire comment. It assumes that '#' has been consumed. // lexComment lexes an entire comment. It assumes that '#' has been consumed.
// It will consume *up to* the first new line character, and pass control // It will consume *up to* the first newline character, and pass control
// back to the last state on the stack. // back to the last state on the stack.
func lexComment(lx *lexer) stateFn { func lexComment(lx *lexer) stateFn {
r := lx.peek() r := lx.peek()

View file

@ -269,6 +269,41 @@ func (p *parser) value(it item) (interface{}, tomlType) {
types = append(types, typ) types = append(types, typ)
} }
return array, p.typeOfArray(types) return array, p.typeOfArray(types)
case itemInlineTableStart:
var (
hash = make(map[string]interface{})
outerContext = p.context
outerKey = p.currentKey
)
p.context = append(p.context, p.currentKey)
p.currentKey = ""
for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
if it.typ != itemKeyStart {
p.bug("Expected key start but instead found %q, around line %d",
it.val, p.approxLine)
}
if it.typ == itemCommentStart {
p.expect(itemText)
continue
}
// retrieve key
k := p.next()
p.approxLine = k.line
kname := p.keyString(k)
// retrieve value
p.currentKey = kname
val, typ := p.value(p.next())
// make sure we keep metadata up to date
p.setType(kname, typ)
p.ordered = append(p.ordered, p.context.add(p.currentKey))
hash[kname] = val
}
p.context = outerContext
p.currentKey = outerKey
return hash, tomlHash
} }
p.bug("Unexpected value type: %s", it.typ) p.bug("Unexpected value type: %s", it.typ)
panic("unreachable") panic("unreachable")

38
vendor/github.com/JamesClonk/vultr/lib/applications.go generated vendored Normal file
View file

@ -0,0 +1,38 @@
package lib
import (
"sort"
"strings"
)
// Application on Vultr
type Application struct {
ID string `json:"APPID"`
Name string `json:"name"`
ShortName string `json:"short_name"`
DeployName string `json:"deploy_name"`
Surcharge float64 `json:"surcharge"`
}
type applications []Application
func (s applications) Len() int { return len(s) }
func (s applications) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s applications) Less(i, j int) bool {
return strings.ToLower(s[i].Name) < strings.ToLower(s[j].Name)
}
// GetApplications returns a list of all available applications on Vultr
func (c *Client) GetApplications() ([]Application, error) {
var appMap map[string]Application
if err := c.get(`app/list`, &appMap); err != nil {
return nil, err
}
var appList []Application
for _, app := range appMap {
appList = append(appList, app)
}
sort.Sort(applications(appList))
return appList, nil
}

View file

@ -4,7 +4,9 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/url" "net/url"
"sort"
"strconv" "strconv"
"strings"
) )
// BlockStorage on Vultr account // BlockStorage on Vultr account
@ -19,6 +21,25 @@ type BlockStorage struct {
AttachedTo string `json:"attached_to_SUBID"` AttachedTo string `json:"attached_to_SUBID"`
} }
type blockstorages []BlockStorage
func (b blockstorages) Len() int { return len(b) }
func (b blockstorages) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b blockstorages) Less(i, j int) bool {
// sort order: name, size, status
if strings.ToLower(b[i].Name) < strings.ToLower(b[j].Name) {
return true
} else if strings.ToLower(b[i].Name) > strings.ToLower(b[j].Name) {
return false
}
if b[i].SizeGB < b[j].SizeGB {
return true
} else if b[i].SizeGB > b[j].SizeGB {
return false
}
return b[i].Status < b[j].Status
}
// UnmarshalJSON implements json.Unmarshaller on BlockStorage. // UnmarshalJSON implements json.Unmarshaller on BlockStorage.
// This is needed because the Vultr API is inconsistent in it's JSON responses. // This is needed because the Vultr API is inconsistent in it's JSON responses.
// Some fields can change type, from JSON number to JSON string and vice-versa. // Some fields can change type, from JSON number to JSON string and vice-versa.
@ -87,6 +108,7 @@ func (c *Client) GetBlockStorages() (storages []BlockStorage, err error) {
if err := c.get(`block/list`, &storages); err != nil { if err := c.get(`block/list`, &storages); err != nil {
return nil, err return nil, err
} }
sort.Sort(blockstorages(storages))
return storages, nil return storages, nil
} }

View file

@ -18,7 +18,7 @@ import (
const ( const (
// Version of this libary // Version of this libary
Version = "1.12.0" Version = "1.13.0"
// APIVersion of Vultr // APIVersion of Vultr
APIVersion = "v1" APIVersion = "v1"

View file

@ -3,6 +3,8 @@ package lib
import ( import (
"fmt" "fmt"
"net/url" "net/url"
"sort"
"strings"
) )
// DNSDomain represents a DNS domain on Vultr // DNSDomain represents a DNS domain on Vultr
@ -11,6 +13,14 @@ type DNSDomain struct {
Created string `json:"date_created"` Created string `json:"date_created"`
} }
type dnsdomains []DNSDomain
func (d dnsdomains) Len() int { return len(d) }
func (d dnsdomains) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
func (d dnsdomains) Less(i, j int) bool {
return strings.ToLower(d[i].Domain) < strings.ToLower(d[j].Domain)
}
// DNSRecord represents a DNS record on Vultr // DNSRecord represents a DNS record on Vultr
type DNSRecord struct { type DNSRecord struct {
RecordID int `json:"RECORDID"` RecordID int `json:"RECORDID"`
@ -21,20 +31,41 @@ type DNSRecord struct {
TTL int `json:"ttl"` TTL int `json:"ttl"`
} }
type dnsrecords []DNSRecord
func (d dnsrecords) Len() int { return len(d) }
func (d dnsrecords) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
func (d dnsrecords) Less(i, j int) bool {
// sort order: type, data, name
if d[i].Type < d[j].Type {
return true
} else if d[i].Type > d[j].Type {
return false
}
if d[i].Data < d[j].Data {
return true
} else if d[i].Data > d[j].Data {
return false
}
return strings.ToLower(d[i].Name) < strings.ToLower(d[j].Name)
}
// GetDNSDomains returns a list of available domains on Vultr account // GetDNSDomains returns a list of available domains on Vultr account
func (c *Client) GetDNSDomains() (dnsdomains []DNSDomain, err error) { func (c *Client) GetDNSDomains() (domains []DNSDomain, err error) {
if err := c.get(`dns/list`, &dnsdomains); err != nil { if err := c.get(`dns/list`, &domains); err != nil {
return nil, err return nil, err
} }
return dnsdomains, nil sort.Sort(dnsdomains(domains))
return domains, nil
} }
// GetDNSRecords returns a list of all DNS records of a particular domain // GetDNSRecords returns a list of all DNS records of a particular domain
func (c *Client) GetDNSRecords(domain string) (dnsrecords []DNSRecord, err error) { func (c *Client) GetDNSRecords(domain string) (records []DNSRecord, err error) {
if err := c.get(`dns/records?domain=`+domain, &dnsrecords); err != nil { if err := c.get(`dns/records?domain=`+domain, &records); err != nil {
return nil, err return nil, err
} }
return dnsrecords, nil sort.Sort(dnsrecords(records))
return records, nil
} }
// CreateDNSDomain creates a new DNS domain name on Vultr // CreateDNSDomain creates a new DNS domain name on Vultr

View file

@ -1,6 +1,9 @@
package lib package lib
import "net/url" import (
"net/url"
"sort"
)
// IPv4 information of a virtual machine // IPv4 information of a virtual machine
type IPv4 struct { type IPv4 struct {
@ -11,6 +14,20 @@ type IPv4 struct {
ReverseDNS string `json:"reverse"` ReverseDNS string `json:"reverse"`
} }
type ipv4s []IPv4
func (s ipv4s) Len() int { return len(s) }
func (s ipv4s) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s ipv4s) Less(i, j int) bool {
// sort order: type, ip
if s[i].Type < s[j].Type {
return true
} else if s[i].Type > s[j].Type {
return false
}
return s[i].IP < s[j].IP
}
// IPv6 information of a virtual machine // IPv6 information of a virtual machine
type IPv6 struct { type IPv6 struct {
IP string `json:"ip"` IP string `json:"ip"`
@ -19,12 +36,32 @@ type IPv6 struct {
Type string `json:"type"` Type string `json:"type"`
} }
type ipv6s []IPv6
func (s ipv6s) Len() int { return len(s) }
func (s ipv6s) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s ipv6s) Less(i, j int) bool {
// sort order: type, ip
if s[i].Type < s[j].Type {
return true
} else if s[i].Type > s[j].Type {
return false
}
return s[i].IP < s[j].IP
}
// ReverseDNSIPv6 information of a virtual machine // ReverseDNSIPv6 information of a virtual machine
type ReverseDNSIPv6 struct { type ReverseDNSIPv6 struct {
IP string `json:"ip"` IP string `json:"ip"`
ReverseDNS string `json:"reverse"` ReverseDNS string `json:"reverse"`
} }
type reverseDNSIPv6s []ReverseDNSIPv6
func (s reverseDNSIPv6s) Len() int { return len(s) }
func (s reverseDNSIPv6s) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s reverseDNSIPv6s) Less(i, j int) bool { return s[i].IP < s[j].IP }
// ListIPv4 lists the IPv4 information of a virtual machine // ListIPv4 lists the IPv4 information of a virtual machine
func (c *Client) ListIPv4(id string) (list []IPv4, err error) { func (c *Client) ListIPv4(id string) (list []IPv4, err error) {
var ipMap map[string][]IPv4 var ipMap map[string][]IPv4
@ -37,6 +74,7 @@ func (c *Client) ListIPv4(id string) (list []IPv4, err error) {
list = append(list, ip) list = append(list, ip)
} }
} }
sort.Sort(ipv4s(list))
return list, nil return list, nil
} }
@ -52,6 +90,7 @@ func (c *Client) ListIPv6(id string) (list []IPv6, err error) {
list = append(list, ip) list = append(list, ip)
} }
} }
sort.Sort(ipv6s(list))
return list, nil return list, nil
} }
@ -67,6 +106,7 @@ func (c *Client) ListIPv6ReverseDNS(id string) (list []ReverseDNSIPv6, err error
list = append(list, ip) list = append(list, ip)
} }
} }
sort.Sort(reverseDNSIPv6s(list))
return list, nil return list, nil
} }

View file

@ -1,5 +1,10 @@
package lib package lib
import (
"sort"
"strings"
)
// ISO image on Vultr // ISO image on Vultr
type ISO struct { type ISO struct {
ID int `json:"ISOID"` ID int `json:"ISOID"`
@ -9,6 +14,20 @@ type ISO struct {
MD5sum string `json:"md5sum"` MD5sum string `json:"md5sum"`
} }
type isos []ISO
func (s isos) Len() int { return len(s) }
func (s isos) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s isos) Less(i, j int) bool {
// sort order: filename, created
if strings.ToLower(s[i].Filename) < strings.ToLower(s[j].Filename) {
return true
} else if strings.ToLower(s[i].Filename) > strings.ToLower(s[j].Filename) {
return false
}
return s[i].Created < s[j].Created
}
// GetISO returns a list of all ISO images on Vultr account // GetISO returns a list of all ISO images on Vultr account
func (c *Client) GetISO() ([]ISO, error) { func (c *Client) GetISO() ([]ISO, error) {
var isoMap map[string]ISO var isoMap map[string]ISO
@ -20,5 +39,6 @@ func (c *Client) GetISO() ([]ISO, error) {
for _, iso := range isoMap { for _, iso := range isoMap {
isoList = append(isoList, iso) isoList = append(isoList, iso)
} }
sort.Sort(isos(isoList))
return isoList, nil return isoList, nil
} }

View file

@ -1,5 +1,10 @@
package lib package lib
import (
"sort"
"strings"
)
// OS image on Vultr // OS image on Vultr
type OS struct { type OS struct {
ID int `json:"OSID"` ID int `json:"OSID"`
@ -10,6 +15,12 @@ type OS struct {
Surcharge string `json:"surcharge"` Surcharge string `json:"surcharge"`
} }
type oses []OS
func (s oses) Len() int { return len(s) }
func (s oses) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s oses) Less(i, j int) bool { return strings.ToLower(s[i].Name) < strings.ToLower(s[j].Name) }
// GetOS returns a list of all available operating systems on Vultr // GetOS returns a list of all available operating systems on Vultr
func (c *Client) GetOS() ([]OS, error) { func (c *Client) GetOS() ([]OS, error) {
var osMap map[string]OS var osMap map[string]OS
@ -21,5 +32,6 @@ func (c *Client) GetOS() ([]OS, error) {
for _, os := range osMap { for _, os := range osMap {
osList = append(osList, os) osList = append(osList, os)
} }
sort.Sort(oses(osList))
return osList, nil return osList, nil
} }

View file

@ -1,6 +1,11 @@
package lib package lib
import "fmt" import (
"fmt"
"sort"
"strconv"
"strings"
)
// Plan on Vultr // Plan on Vultr
type Plan struct { type Plan struct {
@ -14,6 +19,40 @@ type Plan struct {
Regions []int `json:"available_locations"` Regions []int `json:"available_locations"`
} }
type plans []Plan
func (p plans) Len() int { return len(p) }
func (p plans) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p plans) Less(i, j int) bool {
pa, _ := strconv.ParseFloat(strings.TrimSpace(p[i].Price), 64)
pb, _ := strconv.ParseFloat(strings.TrimSpace(p[j].Price), 64)
ra, _ := strconv.ParseInt(strings.TrimSpace(p[i].RAM), 10, 64)
rb, _ := strconv.ParseInt(strings.TrimSpace(p[j].RAM), 10, 64)
da, _ := strconv.ParseInt(strings.TrimSpace(p[i].Disk), 10, 64)
db, _ := strconv.ParseInt(strings.TrimSpace(p[j].Disk), 10, 64)
// sort order: price, vcpu, ram, disk
if pa < pb {
return true
} else if pa > pb {
return false
}
if p[i].VCpus < p[j].VCpus {
return true
} else if p[i].VCpus > p[j].VCpus {
return false
}
if ra < rb {
return true
} else if ra > rb {
return false
}
return da < db
}
// GetPlans returns a list of all available plans on Vultr account // GetPlans returns a list of all available plans on Vultr account
func (c *Client) GetPlans() ([]Plan, error) { func (c *Client) GetPlans() ([]Plan, error) {
var planMap map[string]Plan var planMap map[string]Plan
@ -21,11 +60,13 @@ func (c *Client) GetPlans() ([]Plan, error) {
return nil, err return nil, err
} }
var planList []Plan var p plans
for _, plan := range planMap { for _, plan := range planMap {
planList = append(planList, plan) p = append(p, plan)
} }
return planList, nil
sort.Sort(plans(p))
return p, nil
} }
// GetAvailablePlansForRegion returns available plans for specified region // GetAvailablePlansForRegion returns available plans for specified region

View file

@ -1,5 +1,7 @@
package lib package lib
import "sort"
// Region on Vultr // Region on Vultr
type Region struct { type Region struct {
ID int `json:"DCID,string"` ID int `json:"DCID,string"`
@ -12,6 +14,20 @@ type Region struct {
Code string `json:"regioncode"` Code string `json:"regioncode"`
} }
type regions []Region
func (s regions) Len() int { return len(s) }
func (s regions) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s regions) Less(i, j int) bool {
// sort order: continent, name
if s[i].Continent < s[j].Continent {
return true
} else if s[i].Continent > s[j].Continent {
return false
}
return s[i].Name < s[j].Name
}
// GetRegions returns a list of all available Vultr regions // GetRegions returns a list of all available Vultr regions
func (c *Client) GetRegions() ([]Region, error) { func (c *Client) GetRegions() ([]Region, error) {
var regionMap map[string]Region var regionMap map[string]Region
@ -23,5 +39,6 @@ func (c *Client) GetRegions() ([]Region, error) {
for _, os := range regionMap { for _, os := range regionMap {
regionList = append(regionList, os) regionList = append(regionList, os)
} }
sort.Sort(regions(regionList))
return regionList, nil return regionList, nil
} }

View file

@ -4,7 +4,9 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/url" "net/url"
"sort"
"strconv" "strconv"
"strings"
) )
// IP on Vultr // IP on Vultr
@ -18,6 +20,25 @@ type IP struct {
AttachedTo string `json:"attached_SUBID,string"` AttachedTo string `json:"attached_SUBID,string"`
} }
type ips []IP
func (s ips) Len() int { return len(s) }
func (s ips) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s ips) Less(i, j int) bool {
// sort order: label, iptype, subnet
if strings.ToLower(s[i].Label) < strings.ToLower(s[j].Label) {
return true
} else if strings.ToLower(s[i].Label) > strings.ToLower(s[j].Label) {
return false
}
if s[i].IPType < s[j].IPType {
return true
} else if s[i].IPType > s[j].IPType {
return false
}
return s[i].Subnet < s[j].Subnet
}
// UnmarshalJSON implements json.Unmarshaller on IP. // UnmarshalJSON implements json.Unmarshaller on IP.
// This is needed because the Vultr API is inconsistent in it's JSON responses. // This is needed because the Vultr API is inconsistent in it's JSON responses.
// Some fields can change type, from JSON number to JSON string and vice-versa. // Some fields can change type, from JSON number to JSON string and vice-versa.
@ -89,11 +110,12 @@ func (c *Client) ListReservedIP() ([]IP, error) {
return nil, err return nil, err
} }
ips := make([]IP, 0) ipList := make([]IP, 0)
for _, ip := range ipMap { for _, ip := range ipMap {
ips = append(ips, ip) ipList = append(ipList, ip)
} }
return ips, nil sort.Sort(ips(ipList))
return ipList, nil
} }
// GetReservedIP returns reserved IP with given ID // GetReservedIP returns reserved IP with given ID

View file

@ -4,6 +4,8 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/url" "net/url"
"sort"
"strings"
) )
// StartupScript on Vultr account // StartupScript on Vultr account
@ -14,6 +16,14 @@ type StartupScript struct {
Content string `json:"script"` Content string `json:"script"`
} }
type startupscripts []StartupScript
func (s startupscripts) Len() int { return len(s) }
func (s startupscripts) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s startupscripts) Less(i, j int) bool {
return strings.ToLower(s[i].Name) < strings.ToLower(s[j].Name)
}
// UnmarshalJSON implements json.Unmarshaller on StartupScript. // UnmarshalJSON implements json.Unmarshaller on StartupScript.
// Necessary because the SCRIPTID field has inconsistent types. // Necessary because the SCRIPTID field has inconsistent types.
func (s *StartupScript) UnmarshalJSON(data []byte) (err error) { func (s *StartupScript) UnmarshalJSON(data []byte) (err error) {
@ -47,6 +57,7 @@ func (c *Client) GetStartupScripts() (scripts []StartupScript, err error) {
} }
scripts = append(scripts, script) scripts = append(scripts, script)
} }
sort.Sort(startupscripts(scripts))
return scripts, nil return scripts, nil
} }

View file

@ -5,7 +5,9 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/url" "net/url"
"sort"
"strconv" "strconv"
"strings"
) )
// Server (virtual machine) on Vultr account // Server (virtual machine) on Vultr account
@ -36,6 +38,8 @@ type Server struct {
KVMUrl string `json:"kvm_url"` KVMUrl string `json:"kvm_url"`
AutoBackups string `json:"auto_backups"` AutoBackups string `json:"auto_backups"`
Tag string `json:"tag"` Tag string `json:"tag"`
OSID string `json:"OSID"`
AppID string `json:"APPID"`
} }
// ServerOptions are optional parameters to be used during server creation // ServerOptions are optional parameters to be used during server creation
@ -52,6 +56,21 @@ type ServerOptions struct {
DontNotifyOnActivate bool DontNotifyOnActivate bool
Hostname string Hostname string
Tag string Tag string
AppID string
}
type servers []Server
func (s servers) Len() int { return len(s) }
func (s servers) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s servers) Less(i, j int) bool {
// sort order: name, ip
if strings.ToLower(s[i].Name) < strings.ToLower(s[j].Name) {
return true
} else if strings.ToLower(s[i].Name) > strings.ToLower(s[j].Name) {
return false
}
return s[i].MainIP < s[j].MainIP
} }
// V6Network represents a IPv6 network of a Vultr server // V6Network represents a IPv6 network of a Vultr server
@ -140,6 +159,18 @@ func (s *Server) UnmarshalJSON(data []byte) (err error) {
} }
s.AllowedBandwidth = ab s.AllowedBandwidth = ab
value = fmt.Sprintf("%v", fields["OSID"])
if value == "<nil>" {
value = ""
}
s.OSID = value
value = fmt.Sprintf("%v", fields["APPID"])
if value == "<nil>" {
value = ""
}
s.AppID = value
s.ID = fmt.Sprintf("%v", fields["SUBID"]) s.ID = fmt.Sprintf("%v", fields["SUBID"])
s.Name = fmt.Sprintf("%v", fields["label"]) s.Name = fmt.Sprintf("%v", fields["label"])
s.OS = fmt.Sprintf("%v", fields["os"]) s.OS = fmt.Sprintf("%v", fields["os"])
@ -180,29 +211,31 @@ func (s *Server) UnmarshalJSON(data []byte) (err error) {
} }
// GetServers returns a list of current virtual machines on Vultr account // GetServers returns a list of current virtual machines on Vultr account
func (c *Client) GetServers() (servers []Server, err error) { func (c *Client) GetServers() (serverList []Server, err error) {
var serverMap map[string]Server var serverMap map[string]Server
if err := c.get(`server/list`, &serverMap); err != nil { if err := c.get(`server/list`, &serverMap); err != nil {
return nil, err return nil, err
} }
for _, server := range serverMap { for _, server := range serverMap {
servers = append(servers, server) serverList = append(serverList, server)
} }
return servers, nil sort.Sort(servers(serverList))
return serverList, nil
} }
// GetServersByTag returns a list of all virtual machines matching by tag // GetServersByTag returns a list of all virtual machines matching by tag
func (c *Client) GetServersByTag(tag string) (servers []Server, err error) { func (c *Client) GetServersByTag(tag string) (serverList []Server, err error) {
var serverMap map[string]Server var serverMap map[string]Server
if err := c.get(`server/list?tag=`+tag, &serverMap); err != nil { if err := c.get(`server/list?tag=`+tag, &serverMap); err != nil {
return nil, err return nil, err
} }
for _, server := range serverMap { for _, server := range serverMap {
servers = append(servers, server) serverList = append(serverList, server)
} }
return servers, nil sort.Sort(servers(serverList))
return serverList, nil
} }
// GetServer returns the virtual machine with the given ID // GetServer returns the virtual machine with the given ID
@ -274,6 +307,10 @@ func (c *Client) CreateServer(name string, regionID, planID, osID int, options *
if options.Tag != "" { if options.Tag != "" {
values.Add("tag", options.Tag) values.Add("tag", options.Tag)
} }
if options.AppID != "" {
values.Add("APPID", options.AppID)
}
} }
var server Server var server Server
@ -371,6 +408,7 @@ func (c *Client) ListOSforServer(id string) (os []OS, err error) {
for _, o := range osMap { for _, o := range osMap {
os = append(os, o) os = append(os, o)
} }
sort.Sort(oses(os))
return os, nil return os, nil
} }
@ -446,3 +484,30 @@ func (c *Client) BandwidthOfServer(id string) (bandwidth []map[string]string, er
return bandwidth, nil return bandwidth, nil
} }
// ChangeApplicationofServer changes the virtual machine to a different application
func (c *Client) ChangeApplicationofServer(id string, appID string) error {
values := url.Values{
"SUBID": {id},
"APPID": {appID},
}
if err := c.post(`server/app_change`, values, nil); err != nil {
return err
}
return nil
}
// ListApplicationsforServer lists all available operating systems to which an existing virtual machine can be changed
func (c *Client) ListApplicationsforServer(id string) (apps []Application, err error) {
var appMap map[string]Application
if err := c.get(`server/app_change_list?SUBID=`+id, &appMap); err != nil {
return nil, err
}
for _, app := range appMap {
apps = append(apps, app)
}
sort.Sort(applications(apps))
return apps, nil
}

View file

@ -1,6 +1,10 @@
package lib package lib
import "net/url" import (
"net/url"
"sort"
"strings"
)
// Snapshot of a virtual machine on Vultr account // Snapshot of a virtual machine on Vultr account
type Snapshot struct { type Snapshot struct {
@ -11,17 +15,32 @@ type Snapshot struct {
Created string `json:"date_created"` Created string `json:"date_created"`
} }
type snapshots []Snapshot
func (s snapshots) Len() int { return len(s) }
func (s snapshots) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s snapshots) Less(i, j int) bool {
// sort order: description, created
if strings.ToLower(s[i].Description) < strings.ToLower(s[j].Description) {
return true
} else if strings.ToLower(s[i].Description) > strings.ToLower(s[j].Description) {
return false
}
return s[i].Created < s[j].Created
}
// GetSnapshots retrieves a list of all snapshots on Vultr account // GetSnapshots retrieves a list of all snapshots on Vultr account
func (c *Client) GetSnapshots() (snapshots []Snapshot, err error) { func (c *Client) GetSnapshots() (snapshotList []Snapshot, err error) {
var snapshotMap map[string]Snapshot var snapshotMap map[string]Snapshot
if err := c.get(`snapshot/list`, &snapshotMap); err != nil { if err := c.get(`snapshot/list`, &snapshotMap); err != nil {
return nil, err return nil, err
} }
for _, snapshot := range snapshotMap { for _, snapshot := range snapshotMap {
snapshots = append(snapshots, snapshot) snapshotList = append(snapshotList, snapshot)
} }
return snapshots, nil sort.Sort(snapshots(snapshotList))
return snapshotList, nil
} }
// CreateSnapshot creates a new virtual machine snapshot // CreateSnapshot creates a new virtual machine snapshot

View file

@ -1,6 +1,10 @@
package lib package lib
import "net/url" import (
"net/url"
"sort"
"strings"
)
// SSHKey on Vultr account // SSHKey on Vultr account
type SSHKey struct { type SSHKey struct {
@ -10,6 +14,12 @@ type SSHKey struct {
Created string `json:"date_created"` Created string `json:"date_created"`
} }
type sshkeys []SSHKey
func (s sshkeys) Len() int { return len(s) }
func (s sshkeys) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s sshkeys) Less(i, j int) bool { return strings.ToLower(s[i].Name) < strings.ToLower(s[j].Name) }
// GetSSHKeys returns a list of SSHKeys from Vultr account // GetSSHKeys returns a list of SSHKeys from Vultr account
func (c *Client) GetSSHKeys() (keys []SSHKey, err error) { func (c *Client) GetSSHKeys() (keys []SSHKey, err error) {
var keyMap map[string]SSHKey var keyMap map[string]SSHKey
@ -20,6 +30,7 @@ func (c *Client) GetSSHKeys() (keys []SSHKey, err error) {
for _, key := range keyMap { for _, key := range keyMap {
keys = append(keys, key) keys = append(keys, key)
} }
sort.Sort(sshkeys(keys))
return keys, nil return keys, nil
} }

View file

@ -1,3 +1,5 @@
// +build windows
package winio package winio
import ( import (

View file

@ -1,3 +1,5 @@
// +build windows
package winio package winio
import ( import (

View file

@ -1,3 +1,5 @@
// +build windows
package winio package winio
import ( import (

View file

@ -1,3 +1,5 @@
// +build windows
package winio package winio
import ( import (

View file

@ -1,3 +1,5 @@
// +build windows
package winio package winio
import ( import (
@ -83,7 +85,7 @@ func RunWithPrivileges(names []string, fn func() error) error {
return err return err
} }
defer releaseThreadToken(token) defer releaseThreadToken(token)
err = adjustPrivileges(token, privileges) err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED)
if err != nil { if err != nil {
return err return err
} }
@ -110,6 +112,15 @@ func mapPrivileges(names []string) ([]uint64, error) {
// EnableProcessPrivileges enables privileges globally for the process. // EnableProcessPrivileges enables privileges globally for the process.
func EnableProcessPrivileges(names []string) error { func EnableProcessPrivileges(names []string) error {
return enableDisableProcessPrivilege(names, SE_PRIVILEGE_ENABLED)
}
// DisableProcessPrivileges disables privileges globally for the process.
func DisableProcessPrivileges(names []string) error {
return enableDisableProcessPrivilege(names, 0)
}
func enableDisableProcessPrivilege(names []string, action uint32) error {
privileges, err := mapPrivileges(names) privileges, err := mapPrivileges(names)
if err != nil { if err != nil {
return err return err
@ -123,15 +134,15 @@ func EnableProcessPrivileges(names []string) error {
} }
defer token.Close() defer token.Close()
return adjustPrivileges(token, privileges) return adjustPrivileges(token, privileges, action)
} }
func adjustPrivileges(token windows.Token, privileges []uint64) error { func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error {
var b bytes.Buffer var b bytes.Buffer
binary.Write(&b, binary.LittleEndian, uint32(len(privileges))) binary.Write(&b, binary.LittleEndian, uint32(len(privileges)))
for _, p := range privileges { for _, p := range privileges {
binary.Write(&b, binary.LittleEndian, p) binary.Write(&b, binary.LittleEndian, p)
binary.Write(&b, binary.LittleEndian, uint32(SE_PRIVILEGE_ENABLED)) binary.Write(&b, binary.LittleEndian, action)
} }
prevState := make([]byte, b.Len()) prevState := make([]byte, b.Len())
reqSize := uint32(0) reqSize := uint32(0)

View file

@ -1,3 +1,5 @@
// +build windows
package winio package winio
import ( import (

View file

@ -1,3 +1,3 @@
package winio package winio
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go //go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go

View file

@ -12,9 +12,9 @@ import (
var _ unsafe.Pointer var _ unsafe.Pointer
var ( var (
modkernel32 = syscall.NewLazyDLL("kernel32.dll") modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
modwinmm = syscall.NewLazyDLL("winmm.dll") modwinmm = windows.NewLazySystemDLL("winmm.dll")
modadvapi32 = syscall.NewLazyDLL("advapi32.dll") modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
procCancelIoEx = modkernel32.NewProc("CancelIoEx") procCancelIoEx = modkernel32.NewProc("CancelIoEx")
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort") procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")

View file

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"compress/gzip" "compress/gzip"
"fmt" "fmt"
"io"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
@ -21,10 +22,16 @@ const (
type codings map[string]float64 type codings map[string]float64
// The default qvalue to assign to an encoding if no explicit qvalue is set. const (
// This is actually kind of ambiguous in RFC 2616, so hopefully it's correct. // DefaultQValue is the default qvalue to assign to an encoding if no explicit qvalue is set.
// The examples seem to indicate that it is. // This is actually kind of ambiguous in RFC 2616, so hopefully it's correct.
const DEFAULT_QVALUE = 1.0 // The examples seem to indicate that it is.
DefaultQValue = 1.0
// DefaultMinSize defines the minimum size to reach to enable compression.
// It's 512 bytes.
DefaultMinSize = 512
)
// gzipWriterPools stores a sync.Pool for each compression level for reuse of // gzipWriterPools stores a sync.Pool for each compression level for reuse of
// gzip.Writers. Use poolIndex to covert a compression level to an index into // gzip.Writers. Use poolIndex to covert a compression level to an index into
@ -63,35 +70,88 @@ func addLevelPool(level int) {
// GzipResponseWriter provides an http.ResponseWriter interface, which gzips // GzipResponseWriter provides an http.ResponseWriter interface, which gzips
// bytes before writing them to the underlying response. This doesn't close the // bytes before writing them to the underlying response. This doesn't close the
// writers, so don't forget to do that. // writers, so don't forget to do that.
// It can be configured to skip response smaller than minSize.
type GzipResponseWriter struct { type GzipResponseWriter struct {
http.ResponseWriter http.ResponseWriter
index int // Index for gzipWriterPools. index int // Index for gzipWriterPools.
gw *gzip.Writer gw *gzip.Writer
code int // Saves the WriteHeader value.
minSize int // Specifed the minimum response size to gzip. If the response length is bigger than this value, it is compressed.
buf []byte // Holds the first part of the write before reaching the minSize or the end of the write.
} }
// Write appends data to the gzip writer. // Write appends data to the gzip writer.
func (w *GzipResponseWriter) Write(b []byte) (int, error) { func (w *GzipResponseWriter) Write(b []byte) (int, error) {
// Lazily create the gzip.Writer, this allows empty bodies to be actually // If content type is not set.
// empty, for example in the case of status code 204 (no content).
if w.gw == nil {
w.init()
}
if _, ok := w.Header()[contentType]; !ok { if _, ok := w.Header()[contentType]; !ok {
// If content type is not set, infer it from the uncompressed body. // It infer it from the uncompressed body.
w.Header().Set(contentType, http.DetectContentType(b)) w.Header().Set(contentType, http.DetectContentType(b))
} }
return w.gw.Write(b)
// GZIP responseWriter is initialized. Use the GZIP responseWriter.
if w.gw != nil {
n, err := w.gw.Write(b)
return n, err
}
// Save the write into a buffer for later use in GZIP responseWriter (if content is long enough) or at close with regular responseWriter.
w.buf = append(w.buf, b...)
// If the global writes are bigger than the minSize, compression is enable.
if len(w.buf) >= w.minSize {
err := w.startGzip()
if err != nil {
return 0, err
}
}
return len(b), nil
} }
// WriteHeader will check if the gzip writer needs to be lazily initiated and // startGzip initialize any GZIP specific informations.
// then pass the code along to the underlying ResponseWriter. func (w *GzipResponseWriter) startGzip() error {
func (w *GzipResponseWriter) WriteHeader(code int) {
if w.gw == nil && // Set the GZIP header.
code != http.StatusNotModified && code != http.StatusNoContent { w.Header().Set(contentEncoding, "gzip")
w.init()
// if the Content-Length is already set, then calls to Write on gzip
// will fail to set the Content-Length header since its already set
// See: https://github.com/golang/go/issues/14975.
w.Header().Del(contentLength)
// Write the header to gzip response.
w.writeHeader()
// Initialize the GZIP response.
w.init()
// Flush the buffer into the gzip reponse.
n, err := w.gw.Write(w.buf)
// This should never happen (per io.Writer docs), but if the write didn't
// accept the entire buffer but returned no specific error, we have no clue
// what's going on, so abort just to be safe.
if err == nil && n < len(w.buf) {
return io.ErrShortWrite
} }
w.ResponseWriter.WriteHeader(code)
w.buf = nil
return err
}
// WriteHeader just saves the response code until close or GZIP effective writes.
func (w *GzipResponseWriter) WriteHeader(code int) {
w.code = code
}
// writeHeader uses the saved code to send it to the ResponseWriter.
func (w *GzipResponseWriter) writeHeader() {
if w.code == 0 {
w.code = http.StatusOK
}
w.ResponseWriter.WriteHeader(w.code)
} }
// init graps a new gzip writer from the gzipWriterPool and writes the correct // init graps a new gzip writer from the gzipWriterPool and writes the correct
@ -102,21 +162,29 @@ func (w *GzipResponseWriter) init() {
gzw := gzipWriterPools[w.index].Get().(*gzip.Writer) gzw := gzipWriterPools[w.index].Get().(*gzip.Writer)
gzw.Reset(w.ResponseWriter) gzw.Reset(w.ResponseWriter)
w.gw = gzw w.gw = gzw
w.ResponseWriter.Header().Set(contentEncoding, "gzip")
// if the Content-Length is already set, then calls to Write on gzip
// will fail to set the Content-Length header since its already set
// See: https://github.com/golang/go/issues/14975
w.ResponseWriter.Header().Del(contentLength)
} }
// Close will close the gzip.Writer and will put it back in the gzipWriterPool. // Close will close the gzip.Writer and will put it back in the gzipWriterPool.
func (w *GzipResponseWriter) Close() error { func (w *GzipResponseWriter) Close() error {
// Buffer not nil means the regular response must be returned.
if w.buf != nil {
w.writeHeader()
// Make the write into the regular response.
_, writeErr := w.ResponseWriter.Write(w.buf)
// Returns the error if any at write.
if writeErr != nil {
return fmt.Errorf("gziphandler: write to regular responseWriter at close gets error: %q", writeErr.Error())
}
}
// If the GZIP responseWriter is not set no needs to close it.
if w.gw == nil { if w.gw == nil {
return nil return nil
} }
err := w.gw.Close() err := w.gw.Close()
gzipWriterPools[w.index].Put(w.gw) gzipWriterPools[w.index].Put(w.gw)
w.gw = nil
return err return err
} }
@ -162,9 +230,18 @@ func MustNewGzipLevelHandler(level int) func(http.Handler) http.Handler {
// if an invalid gzip compression level is given, so if one can ensure the level // if an invalid gzip compression level is given, so if one can ensure the level
// is valid, the returned error can be safely ignored. // is valid, the returned error can be safely ignored.
func NewGzipLevelHandler(level int) (func(http.Handler) http.Handler, error) { func NewGzipLevelHandler(level int) (func(http.Handler) http.Handler, error) {
return NewGzipLevelAndMinSize(level, DefaultMinSize)
}
// NewGzipLevelAndMinSize behave as NewGzipLevelHandler except it let the caller
// specify the minimum size before compression.
func NewGzipLevelAndMinSize(level, minSize int) (func(http.Handler) http.Handler, error) {
if level != gzip.DefaultCompression && (level < gzip.BestSpeed || level > gzip.BestCompression) { if level != gzip.DefaultCompression && (level < gzip.BestSpeed || level > gzip.BestCompression) {
return nil, fmt.Errorf("invalid compression level requested: %d", level) return nil, fmt.Errorf("invalid compression level requested: %d", level)
} }
if minSize < 0 {
return nil, fmt.Errorf("minimum size must be more than zero")
}
return func(h http.Handler) http.Handler { return func(h http.Handler) http.Handler {
index := poolIndex(level) index := poolIndex(level)
@ -175,6 +252,9 @@ func NewGzipLevelHandler(level int) (func(http.Handler) http.Handler, error) {
gw := &GzipResponseWriter{ gw := &GzipResponseWriter{
ResponseWriter: w, ResponseWriter: w,
index: index, index: index,
minSize: minSize,
buf: []byte{},
} }
defer gw.Close() defer gw.Close()
@ -237,7 +317,7 @@ func parseEncodings(s string) (codings, error) {
func parseCoding(s string) (coding string, qvalue float64, err error) { func parseCoding(s string) (coding string, qvalue float64, err error) {
for n, part := range strings.Split(s, ";") { for n, part := range strings.Split(s, ";") {
part = strings.TrimSpace(part) part = strings.TrimSpace(part)
qvalue = DEFAULT_QVALUE qvalue = DefaultQValue
if n == 0 { if n == 0 {
coding = strings.ToLower(part) coding = strings.ToLower(part)

43
vendor/github.com/NYTimes/gziphandler/gzip_go18.go generated vendored Normal file
View file

@ -0,0 +1,43 @@
// +build go1.8
package gziphandler
import "net/http"
// Push initiates an HTTP/2 server push.
// Push returns ErrNotSupported if the client has disabled push or if push
// is not supported on the underlying connection.
func (w *GzipResponseWriter) Push(target string, opts *http.PushOptions) error {
pusher, ok := w.ResponseWriter.(http.Pusher)
if ok && pusher != nil {
return pusher.Push(target, setAcceptEncodingForPushOptions(opts))
}
return http.ErrNotSupported
}
// setAcceptEncodingForPushOptions sets "Accept-Encoding" : "gzip" for PushOptions without overriding existing headers.
func setAcceptEncodingForPushOptions(opts *http.PushOptions) *http.PushOptions {
if opts == nil {
opts = &http.PushOptions{
Header: http.Header{
acceptEncoding: []string{"gzip"},
},
}
return opts
}
if opts.Header == nil {
opts.Header = http.Header{
acceptEncoding: []string{"gzip"},
}
return opts
}
if encoding := opts.Header.Get(acceptEncoding); encoding == "" {
opts.Header.Add(acceptEncoding, "gzip")
return opts
}
return opts
}

View file

@ -1,7 +1,7 @@
package logrus package logrus
// The following code was sourced and modified from the // The following code was sourced and modified from the
// https://bitbucket.org/tebeka/atexit package governed by the following license: // https://github.com/tebeka/atexit package governed by the following license:
// //
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>. // Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
// //

View file

@ -3,11 +3,21 @@ package logrus
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"os" "os"
"sync"
"time" "time"
) )
var bufferPool *sync.Pool
func init() {
bufferPool = &sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
}
// Defines the key when adding errors using WithError. // Defines the key when adding errors using WithError.
var ErrorKey = "error" var ErrorKey = "error"
@ -29,6 +39,9 @@ type Entry struct {
// Message passed to Debug, Info, Warn, Error, Fatal or Panic // Message passed to Debug, Info, Warn, Error, Fatal or Panic
Message string Message string
// When formatter is called in entry.log(), an Buffer may be set to entry
Buffer *bytes.Buffer
} }
func NewEntry(logger *Logger) *Entry { func NewEntry(logger *Logger) *Entry {
@ -39,21 +52,15 @@ func NewEntry(logger *Logger) *Entry {
} }
} }
// Returns a reader for the entry, which is a proxy to the formatter.
func (entry *Entry) Reader() (*bytes.Buffer, error) {
serialized, err := entry.Logger.Formatter.Format(entry)
return bytes.NewBuffer(serialized), err
}
// Returns the string representation from the reader and ultimately the // Returns the string representation from the reader and ultimately the
// formatter. // formatter.
func (entry *Entry) String() (string, error) { func (entry *Entry) String() (string, error) {
reader, err := entry.Reader() serialized, err := entry.Logger.Formatter.Format(entry)
if err != nil { if err != nil {
return "", err return "", err
} }
str := string(serialized)
return reader.String(), err return str, nil
} }
// Add an error as single field (using the key defined in ErrorKey) to the Entry. // Add an error as single field (using the key defined in ErrorKey) to the Entry.
@ -81,6 +88,7 @@ func (entry *Entry) WithFields(fields Fields) *Entry {
// This function is not declared with a pointer value because otherwise // This function is not declared with a pointer value because otherwise
// race conditions will occur when using multiple goroutines // race conditions will occur when using multiple goroutines
func (entry Entry) log(level Level, msg string) { func (entry Entry) log(level Level, msg string) {
var buffer *bytes.Buffer
entry.Time = time.Now() entry.Time = time.Now()
entry.Level = level entry.Level = level
entry.Message = msg entry.Message = msg
@ -90,20 +98,23 @@ func (entry Entry) log(level Level, msg string) {
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
entry.Logger.mu.Unlock() entry.Logger.mu.Unlock()
} }
buffer = bufferPool.Get().(*bytes.Buffer)
reader, err := entry.Reader() buffer.Reset()
defer bufferPool.Put(buffer)
entry.Buffer = buffer
serialized, err := entry.Logger.Formatter.Format(&entry)
entry.Buffer = nil
if err != nil { if err != nil {
entry.Logger.mu.Lock() entry.Logger.mu.Lock()
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err) fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
entry.Logger.mu.Unlock() entry.Logger.mu.Unlock()
} } else {
entry.Logger.mu.Lock()
entry.Logger.mu.Lock() _, err = entry.Logger.Out.Write(serialized)
defer entry.Logger.mu.Unlock() if err != nil {
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
_, err = io.Copy(entry.Logger.Out, reader) }
if err != nil { entry.Logger.mu.Unlock()
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
} }
// To avoid Entry#log() returning a value that only would make sense for // To avoid Entry#log() returning a value that only would make sense for

View file

@ -5,9 +5,40 @@ import (
"fmt" "fmt"
) )
type fieldKey string
type FieldMap map[fieldKey]string
const (
FieldKeyMsg = "msg"
FieldKeyLevel = "level"
FieldKeyTime = "time"
)
func (f FieldMap) resolve(key fieldKey) string {
if k, ok := f[key]; ok {
return k
}
return string(key)
}
type JSONFormatter struct { type JSONFormatter struct {
// TimestampFormat sets the format used for marshaling timestamps. // TimestampFormat sets the format used for marshaling timestamps.
TimestampFormat string TimestampFormat string
// DisableTimestamp allows disabling automatic timestamps in output
DisableTimestamp bool
// FieldMap allows users to customize the names of keys for various fields.
// As an example:
// formatter := &JSONFormatter{
// FieldMap: FieldMap{
// FieldKeyTime: "@timestamp",
// FieldKeyLevel: "@level",
// FieldKeyLevel: "@message",
// },
// }
FieldMap FieldMap
} }
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
@ -29,9 +60,11 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
timestampFormat = DefaultTimestampFormat timestampFormat = DefaultTimestampFormat
} }
data["time"] = entry.Time.Format(timestampFormat) if !f.DisableTimestamp {
data["msg"] = entry.Message data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
data["level"] = entry.Level.String() }
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
serialized, err := json.Marshal(data) serialized, err := json.Marshal(data)
if err != nil { if err != nil {

View file

@ -26,8 +26,31 @@ type Logger struct {
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be // to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
// logged. `logrus.Debug` is useful in // logged. `logrus.Debug` is useful in
Level Level Level Level
// Used to sync writing to the log. // Used to sync writing to the log. Locking is enabled by Default
mu sync.Mutex mu MutexWrap
// Reusable empty entry
entryPool sync.Pool
}
type MutexWrap struct {
lock sync.Mutex
disabled bool
}
func (mw *MutexWrap) Lock() {
if !mw.disabled {
mw.lock.Lock()
}
}
func (mw *MutexWrap) Unlock() {
if !mw.disabled {
mw.lock.Unlock()
}
}
func (mw *MutexWrap) Disable() {
mw.disabled = true
} }
// Creates a new logger. Configuration should be set by changing `Formatter`, // Creates a new logger. Configuration should be set by changing `Formatter`,
@ -51,162 +74,235 @@ func New() *Logger {
} }
} }
func (logger *Logger) newEntry() *Entry {
entry, ok := logger.entryPool.Get().(*Entry)
if ok {
return entry
}
return NewEntry(logger)
}
func (logger *Logger) releaseEntry(entry *Entry) {
logger.entryPool.Put(entry)
}
// Adds a field to the log entry, note that it doesn't log until you call // Adds a field to the log entry, note that it doesn't log until you call
// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry. // Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
// If you want multiple fields, use `WithFields`. // If you want multiple fields, use `WithFields`.
func (logger *Logger) WithField(key string, value interface{}) *Entry { func (logger *Logger) WithField(key string, value interface{}) *Entry {
return NewEntry(logger).WithField(key, value) entry := logger.newEntry()
defer logger.releaseEntry(entry)
return entry.WithField(key, value)
} }
// Adds a struct of fields to the log entry. All it does is call `WithField` for // Adds a struct of fields to the log entry. All it does is call `WithField` for
// each `Field`. // each `Field`.
func (logger *Logger) WithFields(fields Fields) *Entry { func (logger *Logger) WithFields(fields Fields) *Entry {
return NewEntry(logger).WithFields(fields) entry := logger.newEntry()
defer logger.releaseEntry(entry)
return entry.WithFields(fields)
} }
// Add an error as single field to the log entry. All it does is call // Add an error as single field to the log entry. All it does is call
// `WithError` for the given `error`. // `WithError` for the given `error`.
func (logger *Logger) WithError(err error) *Entry { func (logger *Logger) WithError(err error) *Entry {
return NewEntry(logger).WithError(err) entry := logger.newEntry()
defer logger.releaseEntry(entry)
return entry.WithError(err)
} }
func (logger *Logger) Debugf(format string, args ...interface{}) { func (logger *Logger) Debugf(format string, args ...interface{}) {
if logger.Level >= DebugLevel { if logger.Level >= DebugLevel {
NewEntry(logger).Debugf(format, args...) entry := logger.newEntry()
entry.Debugf(format, args...)
logger.releaseEntry(entry)
} }
} }
func (logger *Logger) Infof(format string, args ...interface{}) { func (logger *Logger) Infof(format string, args ...interface{}) {
if logger.Level >= InfoLevel { if logger.Level >= InfoLevel {
NewEntry(logger).Infof(format, args...) entry := logger.newEntry()
entry.Infof(format, args...)
logger.releaseEntry(entry)
} }
} }
func (logger *Logger) Printf(format string, args ...interface{}) { func (logger *Logger) Printf(format string, args ...interface{}) {
NewEntry(logger).Printf(format, args...) entry := logger.newEntry()
entry.Printf(format, args...)
logger.releaseEntry(entry)
} }
func (logger *Logger) Warnf(format string, args ...interface{}) { func (logger *Logger) Warnf(format string, args ...interface{}) {
if logger.Level >= WarnLevel { if logger.Level >= WarnLevel {
NewEntry(logger).Warnf(format, args...) entry := logger.newEntry()
entry.Warnf(format, args...)
logger.releaseEntry(entry)
} }
} }
func (logger *Logger) Warningf(format string, args ...interface{}) { func (logger *Logger) Warningf(format string, args ...interface{}) {
if logger.Level >= WarnLevel { if logger.Level >= WarnLevel {
NewEntry(logger).Warnf(format, args...) entry := logger.newEntry()
entry.Warnf(format, args...)
logger.releaseEntry(entry)
} }
} }
func (logger *Logger) Errorf(format string, args ...interface{}) { func (logger *Logger) Errorf(format string, args ...interface{}) {
if logger.Level >= ErrorLevel { if logger.Level >= ErrorLevel {
NewEntry(logger).Errorf(format, args...) entry := logger.newEntry()
entry.Errorf(format, args...)
logger.releaseEntry(entry)
} }
} }
func (logger *Logger) Fatalf(format string, args ...interface{}) { func (logger *Logger) Fatalf(format string, args ...interface{}) {
if logger.Level >= FatalLevel { if logger.Level >= FatalLevel {
NewEntry(logger).Fatalf(format, args...) entry := logger.newEntry()
entry.Fatalf(format, args...)
logger.releaseEntry(entry)
} }
Exit(1) Exit(1)
} }
func (logger *Logger) Panicf(format string, args ...interface{}) { func (logger *Logger) Panicf(format string, args ...interface{}) {
if logger.Level >= PanicLevel { if logger.Level >= PanicLevel {
NewEntry(logger).Panicf(format, args...) entry := logger.newEntry()
entry.Panicf(format, args...)
logger.releaseEntry(entry)
} }
} }
func (logger *Logger) Debug(args ...interface{}) { func (logger *Logger) Debug(args ...interface{}) {
if logger.Level >= DebugLevel { if logger.Level >= DebugLevel {
NewEntry(logger).Debug(args...) entry := logger.newEntry()
entry.Debug(args...)
logger.releaseEntry(entry)
} }
} }
func (logger *Logger) Info(args ...interface{}) { func (logger *Logger) Info(args ...interface{}) {
if logger.Level >= InfoLevel { if logger.Level >= InfoLevel {
NewEntry(logger).Info(args...) entry := logger.newEntry()
entry.Info(args...)
logger.releaseEntry(entry)
} }
} }
func (logger *Logger) Print(args ...interface{}) { func (logger *Logger) Print(args ...interface{}) {
NewEntry(logger).Info(args...) entry := logger.newEntry()
entry.Info(args...)
logger.releaseEntry(entry)
} }
func (logger *Logger) Warn(args ...interface{}) { func (logger *Logger) Warn(args ...interface{}) {
if logger.Level >= WarnLevel { if logger.Level >= WarnLevel {
NewEntry(logger).Warn(args...) entry := logger.newEntry()
entry.Warn(args...)
logger.releaseEntry(entry)
} }
} }
func (logger *Logger) Warning(args ...interface{}) { func (logger *Logger) Warning(args ...interface{}) {
if logger.Level >= WarnLevel { if logger.Level >= WarnLevel {
NewEntry(logger).Warn(args...) entry := logger.newEntry()
entry.Warn(args...)
logger.releaseEntry(entry)
} }
} }
func (logger *Logger) Error(args ...interface{}) { func (logger *Logger) Error(args ...interface{}) {
if logger.Level >= ErrorLevel { if logger.Level >= ErrorLevel {
NewEntry(logger).Error(args...) entry := logger.newEntry()
entry.Error(args...)
logger.releaseEntry(entry)
} }
} }
func (logger *Logger) Fatal(args ...interface{}) { func (logger *Logger) Fatal(args ...interface{}) {
if logger.Level >= FatalLevel { if logger.Level >= FatalLevel {
NewEntry(logger).Fatal(args...) entry := logger.newEntry()
entry.Fatal(args...)
logger.releaseEntry(entry)
} }
Exit(1) Exit(1)
} }
func (logger *Logger) Panic(args ...interface{}) { func (logger *Logger) Panic(args ...interface{}) {
if logger.Level >= PanicLevel { if logger.Level >= PanicLevel {
NewEntry(logger).Panic(args...) entry := logger.newEntry()
entry.Panic(args...)
logger.releaseEntry(entry)
} }
} }
func (logger *Logger) Debugln(args ...interface{}) { func (logger *Logger) Debugln(args ...interface{}) {
if logger.Level >= DebugLevel { if logger.Level >= DebugLevel {
NewEntry(logger).Debugln(args...) entry := logger.newEntry()
entry.Debugln(args...)
logger.releaseEntry(entry)
} }
} }
func (logger *Logger) Infoln(args ...interface{}) { func (logger *Logger) Infoln(args ...interface{}) {
if logger.Level >= InfoLevel { if logger.Level >= InfoLevel {
NewEntry(logger).Infoln(args...) entry := logger.newEntry()
entry.Infoln(args...)
logger.releaseEntry(entry)
} }
} }
func (logger *Logger) Println(args ...interface{}) { func (logger *Logger) Println(args ...interface{}) {
NewEntry(logger).Println(args...) entry := logger.newEntry()
entry.Println(args...)
logger.releaseEntry(entry)
} }
func (logger *Logger) Warnln(args ...interface{}) { func (logger *Logger) Warnln(args ...interface{}) {
if logger.Level >= WarnLevel { if logger.Level >= WarnLevel {
NewEntry(logger).Warnln(args...) entry := logger.newEntry()
entry.Warnln(args...)
logger.releaseEntry(entry)
} }
} }
func (logger *Logger) Warningln(args ...interface{}) { func (logger *Logger) Warningln(args ...interface{}) {
if logger.Level >= WarnLevel { if logger.Level >= WarnLevel {
NewEntry(logger).Warnln(args...) entry := logger.newEntry()
entry.Warnln(args...)
logger.releaseEntry(entry)
} }
} }
func (logger *Logger) Errorln(args ...interface{}) { func (logger *Logger) Errorln(args ...interface{}) {
if logger.Level >= ErrorLevel { if logger.Level >= ErrorLevel {
NewEntry(logger).Errorln(args...) entry := logger.newEntry()
entry.Errorln(args...)
logger.releaseEntry(entry)
} }
} }
func (logger *Logger) Fatalln(args ...interface{}) { func (logger *Logger) Fatalln(args ...interface{}) {
if logger.Level >= FatalLevel { if logger.Level >= FatalLevel {
NewEntry(logger).Fatalln(args...) entry := logger.newEntry()
entry.Fatalln(args...)
logger.releaseEntry(entry)
} }
Exit(1) Exit(1)
} }
func (logger *Logger) Panicln(args ...interface{}) { func (logger *Logger) Panicln(args ...interface{}) {
if logger.Level >= PanicLevel { if logger.Level >= PanicLevel {
NewEntry(logger).Panicln(args...) entry := logger.newEntry()
entry.Panicln(args...)
logger.releaseEntry(entry)
} }
} }
//When file is opened with appending mode, it's safe to
//write concurrently to a file (within 4k message on Linux).
//In these cases user can choose to disable the lock.
func (logger *Logger) SetNoLock() {
logger.mu.Disable()
}

View file

@ -0,0 +1,10 @@
// +build appengine
package logrus
import "io"
// IsTerminal returns true if stderr's file descriptor is a terminal.
func IsTerminal(f io.Writer) bool {
return true
}

View file

@ -1,4 +1,5 @@
// +build darwin freebsd openbsd netbsd dragonfly // +build darwin freebsd openbsd netbsd dragonfly
// +build !appengine
package logrus package logrus

View file

@ -3,6 +3,8 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build !appengine
package logrus package logrus
import "syscall" import "syscall"

View file

@ -4,18 +4,25 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build linux darwin freebsd openbsd netbsd dragonfly // +build linux darwin freebsd openbsd netbsd dragonfly
// +build !appengine
package logrus package logrus
import ( import (
"io"
"os"
"syscall" "syscall"
"unsafe" "unsafe"
) )
// IsTerminal returns true if stderr's file descriptor is a terminal. // IsTerminal returns true if stderr's file descriptor is a terminal.
func IsTerminal() bool { func IsTerminal(f io.Writer) bool {
fd := syscall.Stderr
var termios Termios var termios Termios
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) switch v := f.(type) {
return err == 0 case *os.File:
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(v.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0
default:
return false
}
} }

View file

@ -1,15 +1,21 @@
// +build solaris // +build solaris,!appengine
package logrus package logrus
import ( import (
"io"
"os" "os"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
// IsTerminal returns true if the given file descriptor is a terminal. // IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal() bool { func IsTerminal(f io.Writer) bool {
_, err := unix.IoctlGetTermios(int(os.Stdout.Fd()), unix.TCGETA) switch v := f.(type) {
return err == nil case *os.File:
_, err := unix.IoctlGetTermios(int(v.Fd()), unix.TCGETA)
return err == nil
default:
return false
}
} }

View file

@ -3,11 +3,13 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build windows // +build windows,!appengine
package logrus package logrus
import ( import (
"io"
"os"
"syscall" "syscall"
"unsafe" "unsafe"
) )
@ -19,9 +21,13 @@ var (
) )
// IsTerminal returns true if stderr's file descriptor is a terminal. // IsTerminal returns true if stderr's file descriptor is a terminal.
func IsTerminal() bool { func IsTerminal(f io.Writer) bool {
fd := syscall.Stderr switch v := f.(type) {
var st uint32 case *os.File:
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) var st uint32
return r != 0 && e == 0 r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(v.Fd()), uintptr(unsafe.Pointer(&st)), 0)
return r != 0 && e == 0
default:
return false
}
} }

View file

@ -3,9 +3,9 @@ package logrus
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"runtime"
"sort" "sort"
"strings" "strings"
"sync"
"time" "time"
) )
@ -20,16 +20,10 @@ const (
var ( var (
baseTimestamp time.Time baseTimestamp time.Time
isTerminal bool
) )
func init() { func init() {
baseTimestamp = time.Now() baseTimestamp = time.Now()
isTerminal = IsTerminal()
}
func miniTS() int {
return int(time.Since(baseTimestamp) / time.Second)
} }
type TextFormatter struct { type TextFormatter struct {
@ -54,10 +48,32 @@ type TextFormatter struct {
// that log extremely frequently and don't use the JSON formatter this may not // that log extremely frequently and don't use the JSON formatter this may not
// be desired. // be desired.
DisableSorting bool DisableSorting bool
// QuoteEmptyFields will wrap empty fields in quotes if true
QuoteEmptyFields bool
// QuoteCharacter can be set to the override the default quoting character "
// with something else. For example: ', or `.
QuoteCharacter string
// Whether the logger's out is to a terminal
isTerminal bool
sync.Once
}
func (f *TextFormatter) init(entry *Entry) {
if len(f.QuoteCharacter) == 0 {
f.QuoteCharacter = "\""
}
if entry.Logger != nil {
f.isTerminal = IsTerminal(entry.Logger.Out)
}
} }
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
var keys []string = make([]string, 0, len(entry.Data)) var b *bytes.Buffer
keys := make([]string, 0, len(entry.Data))
for k := range entry.Data { for k := range entry.Data {
keys = append(keys, k) keys = append(keys, k)
} }
@ -65,13 +81,17 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
if !f.DisableSorting { if !f.DisableSorting {
sort.Strings(keys) sort.Strings(keys)
} }
if entry.Buffer != nil {
b := &bytes.Buffer{} b = entry.Buffer
} else {
b = &bytes.Buffer{}
}
prefixFieldClashes(entry.Data) prefixFieldClashes(entry.Data)
isColorTerminal := isTerminal && (runtime.GOOS != "windows") f.Do(func() { f.init(entry) })
isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors
isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors
timestampFormat := f.TimestampFormat timestampFormat := f.TimestampFormat
if timestampFormat == "" { if timestampFormat == "" {
@ -111,18 +131,24 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
levelText := strings.ToUpper(entry.Level.String())[0:4] levelText := strings.ToUpper(entry.Level.String())[0:4]
if !f.FullTimestamp { if f.DisableTimestamp {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message) fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s ", levelColor, levelText, entry.Message)
} else if !f.FullTimestamp {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), entry.Message)
} else { } else {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message) fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
} }
for _, k := range keys { for _, k := range keys {
v := entry.Data[k] v := entry.Data[k]
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%+v", levelColor, k, v) fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
f.appendValue(b, v)
} }
} }
func needsQuoting(text string) bool { func (f *TextFormatter) needsQuoting(text string) bool {
if f.QuoteEmptyFields && len(text) == 0 {
return true
}
for _, ch := range text { for _, ch := range text {
if !((ch >= 'a' && ch <= 'z') || if !((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') || (ch >= 'A' && ch <= 'Z') ||
@ -138,24 +164,26 @@ func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interf
b.WriteString(key) b.WriteString(key)
b.WriteByte('=') b.WriteByte('=')
f.appendValue(b, value)
b.WriteByte(' ')
}
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
switch value := value.(type) { switch value := value.(type) {
case string: case string:
if !needsQuoting(value) { if !f.needsQuoting(value) {
b.WriteString(value) b.WriteString(value)
} else { } else {
fmt.Fprintf(b, "%q", value) fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, value, f.QuoteCharacter)
} }
case error: case error:
errmsg := value.Error() errmsg := value.Error()
if !needsQuoting(errmsg) { if !f.needsQuoting(errmsg) {
b.WriteString(errmsg) b.WriteString(errmsg)
} else { } else {
fmt.Fprintf(b, "%q", value) fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, errmsg, f.QuoteCharacter)
} }
default: default:
fmt.Fprint(b, value) fmt.Fprint(b, value)
} }
b.WriteByte(' ')
} }

View file

@ -11,39 +11,48 @@ func (logger *Logger) Writer() *io.PipeWriter {
} }
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter { func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
return NewEntry(logger).WriterLevel(level)
}
func (entry *Entry) Writer() *io.PipeWriter {
return entry.WriterLevel(InfoLevel)
}
func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
reader, writer := io.Pipe() reader, writer := io.Pipe()
var printFunc func(args ...interface{}) var printFunc func(args ...interface{})
switch level { switch level {
case DebugLevel: case DebugLevel:
printFunc = logger.Debug printFunc = entry.Debug
case InfoLevel: case InfoLevel:
printFunc = logger.Info printFunc = entry.Info
case WarnLevel: case WarnLevel:
printFunc = logger.Warn printFunc = entry.Warn
case ErrorLevel: case ErrorLevel:
printFunc = logger.Error printFunc = entry.Error
case FatalLevel: case FatalLevel:
printFunc = logger.Fatal printFunc = entry.Fatal
case PanicLevel: case PanicLevel:
printFunc = logger.Panic printFunc = entry.Panic
default: default:
printFunc = logger.Print printFunc = entry.Print
} }
go logger.writerScanner(reader, printFunc) go entry.writerScanner(reader, printFunc)
runtime.SetFinalizer(writer, writerFinalizer) runtime.SetFinalizer(writer, writerFinalizer)
return writer return writer
} }
func (logger *Logger) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) { func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
scanner := bufio.NewScanner(reader) scanner := bufio.NewScanner(reader)
for scanner.Scan() { for scanner.Scan() {
printFunc(scanner.Text()) printFunc(scanner.Text())
} }
if err := scanner.Err(); err != nil { if err := scanner.Err(); err != nil {
logger.Errorf("Error while reading from Writer: %s", err) entry.Errorf("Error while reading from Writer: %s", err)
} }
reader.Close() reader.Close()
} }

View file

@ -1,7 +1,10 @@
package auth package auth
import "encoding/csv" import (
import "os" "encoding/csv"
"os"
"sync"
)
/* /*
SecretProvider is used by authenticators. Takes user name and realm SecretProvider is used by authenticators. Takes user name and realm
@ -20,6 +23,7 @@ type File struct {
Info os.FileInfo Info os.FileInfo
/* must be set in inherited types during initialization */ /* must be set in inherited types during initialization */
Reload func() Reload func()
mu sync.Mutex
} }
func (f *File) ReloadIfNeeded() { func (f *File) ReloadIfNeeded() {
@ -27,6 +31,8 @@ func (f *File) ReloadIfNeeded() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
f.mu.Lock()
defer f.mu.Unlock()
if f.Info == nil || f.Info.ModTime() != info.ModTime() { if f.Info == nil || f.Info.ModTime() != info.ModTime() {
f.Info = info f.Info = info
f.Reload() f.Reload()
@ -40,6 +46,7 @@ func (f *File) ReloadIfNeeded() {
type HtdigestFile struct { type HtdigestFile struct {
File File
Users map[string]map[string]string Users map[string]map[string]string
mu sync.RWMutex
} }
func reload_htdigest(hf *HtdigestFile) { func reload_htdigest(hf *HtdigestFile) {
@ -57,6 +64,8 @@ func reload_htdigest(hf *HtdigestFile) {
panic(err) panic(err)
} }
hf.mu.Lock()
defer hf.mu.Unlock()
hf.Users = make(map[string]map[string]string) hf.Users = make(map[string]map[string]string)
for _, record := range records { for _, record := range records {
_, exists := hf.Users[record[1]] _, exists := hf.Users[record[1]]
@ -77,6 +86,8 @@ func HtdigestFileProvider(filename string) SecretProvider {
hf.Reload = func() { reload_htdigest(hf) } hf.Reload = func() { reload_htdigest(hf) }
return func(user, realm string) string { return func(user, realm string) string {
hf.ReloadIfNeeded() hf.ReloadIfNeeded()
hf.mu.RLock()
defer hf.mu.RUnlock()
_, exists := hf.Users[realm] _, exists := hf.Users[realm]
if !exists { if !exists {
return "" return ""
@ -96,6 +107,7 @@ func HtdigestFileProvider(filename string) SecretProvider {
type HtpasswdFile struct { type HtpasswdFile struct {
File File
Users map[string]string Users map[string]string
mu sync.RWMutex
} }
func reload_htpasswd(h *HtpasswdFile) { func reload_htpasswd(h *HtpasswdFile) {
@ -113,6 +125,8 @@ func reload_htpasswd(h *HtpasswdFile) {
panic(err) panic(err)
} }
h.mu.Lock()
defer h.mu.Unlock()
h.Users = make(map[string]string) h.Users = make(map[string]string)
for _, record := range records { for _, record := range records {
h.Users[record[0]] = record[1] h.Users[record[0]] = record[1]
@ -129,7 +143,9 @@ func HtpasswdFileProvider(filename string) SecretProvider {
h.Reload = func() { reload_htpasswd(h) } h.Reload = func() { reload_htpasswd(h) }
return func(user, realm string) string { return func(user, realm string) string {
h.ReloadIfNeeded() h.ReloadIfNeeded()
h.mu.RLock()
password, exists := h.Users[user] password, exists := h.Users[user]
h.mu.RUnlock()
if !exists { if !exists {
return "" return ""
} }

20
vendor/github.com/beorn7/perks/LICENSE generated vendored Normal file
View file

@ -0,0 +1,20 @@
Copyright (C) 2013 Blake Mizerany
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.

View file

@ -133,7 +133,7 @@ func (s *Stream) Query(q float64) float64 {
if l == 0 { if l == 0 {
return 0 return 0
} }
i := int(float64(l) * q) i := int(math.Ceil(float64(l) * q))
if i > 0 { if i > 0 {
i -= 1 i -= 1
} }

View file

@ -5,3 +5,6 @@ const maxMapSize = 0x7FFFFFFF // 2GB
// maxAllocSize is the size used when creating array pointers. // maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0xFFFFFFF const maxAllocSize = 0xFFFFFFF
// Are unaligned load/stores broken on this arch?
var brokenUnaligned = false

View file

@ -5,3 +5,6 @@ const maxMapSize = 0xFFFFFFFFFFFF // 256TB
// maxAllocSize is the size used when creating array pointers. // maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0x7FFFFFFF const maxAllocSize = 0x7FFFFFFF
// Are unaligned load/stores broken on this arch?
var brokenUnaligned = false

View file

@ -1,7 +1,28 @@
package bolt package bolt
import "unsafe"
// maxMapSize represents the largest mmap size supported by Bolt. // maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0x7FFFFFFF // 2GB const maxMapSize = 0x7FFFFFFF // 2GB
// maxAllocSize is the size used when creating array pointers. // maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0xFFFFFFF const maxAllocSize = 0xFFFFFFF
// Are unaligned load/stores broken on this arch?
var brokenUnaligned bool
func init() {
// Simple check to see whether this arch handles unaligned load/stores
// correctly.
// ARM9 and older devices require load/stores to be from/to aligned
// addresses. If not, the lower 2 bits are cleared and that address is
// read in a jumbled up order.
// See http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka15414.html
raw := [6]byte{0xfe, 0xef, 0x11, 0x22, 0x22, 0x11}
val := *(*uint32)(unsafe.Pointer(uintptr(unsafe.Pointer(&raw)) + 2))
brokenUnaligned = val != 0x11222211
}

View file

@ -7,3 +7,6 @@ const maxMapSize = 0xFFFFFFFFFFFF // 256TB
// maxAllocSize is the size used when creating array pointers. // maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0x7FFFFFFF const maxAllocSize = 0x7FFFFFFF
// Are unaligned load/stores broken on this arch?
var brokenUnaligned = false

View file

@ -7,3 +7,6 @@ const maxMapSize = 0xFFFFFFFFFFFF // 256TB
// maxAllocSize is the size used when creating array pointers. // maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0x7FFFFFFF const maxAllocSize = 0x7FFFFFFF
// Are unaligned load/stores broken on this arch?
var brokenUnaligned = false

View file

@ -7,3 +7,6 @@ const maxMapSize = 0xFFFFFFFFFFFF // 256TB
// maxAllocSize is the size used when creating array pointers. // maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0x7FFFFFFF const maxAllocSize = 0x7FFFFFFF
// Are unaligned load/stores broken on this arch?
var brokenUnaligned = false

View file

@ -7,3 +7,6 @@ const maxMapSize = 0xFFFFFFFFFFFF // 256TB
// maxAllocSize is the size used when creating array pointers. // maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0x7FFFFFFF const maxAllocSize = 0x7FFFFFFF
// Are unaligned load/stores broken on this arch?
var brokenUnaligned = false

View file

@ -89,7 +89,7 @@ func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) erro
func funlock(db *DB) error { func funlock(db *DB) error {
err := unlockFileEx(syscall.Handle(db.lockfile.Fd()), 0, 1, 0, &syscall.Overlapped{}) err := unlockFileEx(syscall.Handle(db.lockfile.Fd()), 0, 1, 0, &syscall.Overlapped{})
db.lockfile.Close() db.lockfile.Close()
os.Remove(db.path+lockExt) os.Remove(db.path + lockExt)
return err return err
} }

View file

@ -130,9 +130,17 @@ func (b *Bucket) Bucket(name []byte) *Bucket {
func (b *Bucket) openBucket(value []byte) *Bucket { func (b *Bucket) openBucket(value []byte) *Bucket {
var child = newBucket(b.tx) var child = newBucket(b.tx)
// If unaligned load/stores are broken on this arch and value is
// unaligned simply clone to an aligned byte array.
unaligned := brokenUnaligned && uintptr(unsafe.Pointer(&value[0]))&3 != 0
if unaligned {
value = cloneBytes(value)
}
// If this is a writable transaction then we need to copy the bucket entry. // If this is a writable transaction then we need to copy the bucket entry.
// Read-only transactions can point directly at the mmap entry. // Read-only transactions can point directly at the mmap entry.
if b.tx.writable { if b.tx.writable && !unaligned {
child.bucket = &bucket{} child.bucket = &bucket{}
*child.bucket = *(*bucket)(unsafe.Pointer(&value[0])) *child.bucket = *(*bucket)(unsafe.Pointer(&value[0]))
} else { } else {
@ -167,9 +175,8 @@ func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) {
if bytes.Equal(key, k) { if bytes.Equal(key, k) {
if (flags & bucketLeafFlag) != 0 { if (flags & bucketLeafFlag) != 0 {
return nil, ErrBucketExists return nil, ErrBucketExists
} else {
return nil, ErrIncompatibleValue
} }
return nil, ErrIncompatibleValue
} }
// Create empty, inline bucket. // Create empty, inline bucket.
@ -329,6 +336,28 @@ func (b *Bucket) Delete(key []byte) error {
return nil return nil
} }
// Sequence returns the current integer for the bucket without incrementing it.
func (b *Bucket) Sequence() uint64 { return b.bucket.sequence }
// SetSequence updates the sequence number for the bucket.
func (b *Bucket) SetSequence(v uint64) error {
if b.tx.db == nil {
return ErrTxClosed
} else if !b.Writable() {
return ErrTxNotWritable
}
// Materialize the root node if it hasn't been already so that the
// bucket will be saved during commit.
if b.rootNode == nil {
_ = b.node(b.root, nil)
}
// Increment and return the sequence.
b.bucket.sequence = v
return nil
}
// NextSequence returns an autoincrementing integer for the bucket. // NextSequence returns an autoincrementing integer for the bucket.
func (b *Bucket) NextSequence() (uint64, error) { func (b *Bucket) NextSequence() (uint64, error) {
if b.tx.db == nil { if b.tx.db == nil {

View file

@ -552,7 +552,10 @@ func (db *DB) removeTx(tx *Tx) {
// Remove the transaction. // Remove the transaction.
for i, t := range db.txs { for i, t := range db.txs {
if t == tx { if t == tx {
db.txs = append(db.txs[:i], db.txs[i+1:]...) last := len(db.txs) - 1
db.txs[i] = db.txs[last]
db.txs[last] = nil
db.txs = db.txs[:last]
break break
} }
} }
@ -952,7 +955,7 @@ func (s *Stats) Sub(other *Stats) Stats {
diff.PendingPageN = s.PendingPageN diff.PendingPageN = s.PendingPageN
diff.FreeAlloc = s.FreeAlloc diff.FreeAlloc = s.FreeAlloc
diff.FreelistInuse = s.FreelistInuse diff.FreelistInuse = s.FreelistInuse
diff.TxN = other.TxN - s.TxN diff.TxN = s.TxN - other.TxN
diff.TxStats = s.TxStats.Sub(&other.TxStats) diff.TxStats = s.TxStats.Sub(&other.TxStats)
return diff return diff
} }

View file

@ -24,7 +24,12 @@ func newFreelist() *freelist {
// size returns the size of the page after serialization. // size returns the size of the page after serialization.
func (f *freelist) size() int { func (f *freelist) size() int {
return pageHeaderSize + (int(unsafe.Sizeof(pgid(0))) * f.count()) n := f.count()
if n >= 0xFFFF {
// The first element will be used to store the count. See freelist.write.
n++
}
return pageHeaderSize + (int(unsafe.Sizeof(pgid(0))) * n)
} }
// count returns count of pages on the freelist // count returns count of pages on the freelist
@ -46,16 +51,15 @@ func (f *freelist) pending_count() int {
return count return count
} }
// all returns a list of all free ids and all pending ids in one sorted list. // copyall copies into dst a list of all free ids and all pending ids in one sorted list.
func (f *freelist) all() []pgid { // f.count returns the minimum length required for dst.
m := make(pgids, 0) func (f *freelist) copyall(dst []pgid) {
m := make(pgids, 0, f.pending_count())
for _, list := range f.pending { for _, list := range f.pending {
m = append(m, list...) m = append(m, list...)
} }
sort.Sort(m) sort.Sort(m)
return pgids(f.ids).merge(m) mergepgids(dst, f.ids, m)
} }
// allocate returns the starting page id of a contiguous list of pages of a given size. // allocate returns the starting page id of a contiguous list of pages of a given size.
@ -166,12 +170,16 @@ func (f *freelist) read(p *page) {
} }
// Copy the list of page ids from the freelist. // Copy the list of page ids from the freelist.
ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[idx:count] if count == 0 {
f.ids = make([]pgid, len(ids)) f.ids = nil
copy(f.ids, ids) } else {
ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[idx:count]
f.ids = make([]pgid, len(ids))
copy(f.ids, ids)
// Make sure they're sorted. // Make sure they're sorted.
sort.Sort(pgids(f.ids)) sort.Sort(pgids(f.ids))
}
// Rebuild the page cache. // Rebuild the page cache.
f.reindex() f.reindex()
@ -182,20 +190,22 @@ func (f *freelist) read(p *page) {
// become free. // become free.
func (f *freelist) write(p *page) error { func (f *freelist) write(p *page) error {
// Combine the old free pgids and pgids waiting on an open transaction. // Combine the old free pgids and pgids waiting on an open transaction.
ids := f.all()
// Update the header flag. // Update the header flag.
p.flags |= freelistPageFlag p.flags |= freelistPageFlag
// The page.count can only hold up to 64k elements so if we overflow that // The page.count can only hold up to 64k elements so if we overflow that
// number then we handle it by putting the size in the first element. // number then we handle it by putting the size in the first element.
if len(ids) < 0xFFFF { lenids := f.count()
p.count = uint16(len(ids)) if lenids == 0 {
copy(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[:], ids) p.count = uint16(lenids)
} else if lenids < 0xFFFF {
p.count = uint16(lenids)
f.copyall(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[:])
} else { } else {
p.count = 0xFFFF p.count = 0xFFFF
((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0] = pgid(len(ids)) ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0] = pgid(lenids)
copy(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[1:], ids) f.copyall(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[1:])
} }
return nil return nil
@ -230,7 +240,7 @@ func (f *freelist) reload(p *page) {
// reindex rebuilds the free cache based on available and pending free lists. // reindex rebuilds the free cache based on available and pending free lists.
func (f *freelist) reindex() { func (f *freelist) reindex() {
f.cache = make(map[pgid]bool) f.cache = make(map[pgid]bool, len(f.ids))
for _, id := range f.ids { for _, id := range f.ids {
f.cache[id] = true f.cache[id] = true
} }

View file

@ -201,6 +201,11 @@ func (n *node) write(p *page) {
} }
p.count = uint16(len(n.inodes)) p.count = uint16(len(n.inodes))
// Stop here if there are no items to write.
if p.count == 0 {
return
}
// Loop over each item and write it to the page. // Loop over each item and write it to the page.
b := (*[maxAllocSize]byte)(unsafe.Pointer(&p.ptr))[n.pageElementSize()*len(n.inodes):] b := (*[maxAllocSize]byte)(unsafe.Pointer(&p.ptr))[n.pageElementSize()*len(n.inodes):]
for i, item := range n.inodes { for i, item := range n.inodes {

View file

@ -62,6 +62,9 @@ func (p *page) leafPageElement(index uint16) *leafPageElement {
// leafPageElements retrieves a list of leaf nodes. // leafPageElements retrieves a list of leaf nodes.
func (p *page) leafPageElements() []leafPageElement { func (p *page) leafPageElements() []leafPageElement {
if p.count == 0 {
return nil
}
return ((*[0x7FFFFFF]leafPageElement)(unsafe.Pointer(&p.ptr)))[:] return ((*[0x7FFFFFF]leafPageElement)(unsafe.Pointer(&p.ptr)))[:]
} }
@ -72,6 +75,9 @@ func (p *page) branchPageElement(index uint16) *branchPageElement {
// branchPageElements retrieves a list of branch nodes. // branchPageElements retrieves a list of branch nodes.
func (p *page) branchPageElements() []branchPageElement { func (p *page) branchPageElements() []branchPageElement {
if p.count == 0 {
return nil
}
return ((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[:] return ((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[:]
} }
@ -139,12 +145,33 @@ func (a pgids) merge(b pgids) pgids {
// Return the opposite slice if one is nil. // Return the opposite slice if one is nil.
if len(a) == 0 { if len(a) == 0 {
return b return b
} else if len(b) == 0 { }
if len(b) == 0 {
return a return a
} }
merged := make(pgids, len(a)+len(b))
mergepgids(merged, a, b)
return merged
}
// Create a list to hold all elements from both lists. // mergepgids copies the sorted union of a and b into dst.
merged := make(pgids, 0, len(a)+len(b)) // If dst is too small, it panics.
func mergepgids(dst, a, b pgids) {
if len(dst) < len(a)+len(b) {
panic(fmt.Errorf("mergepgids bad len %d < %d + %d", len(dst), len(a), len(b)))
}
// Copy in the opposite slice if one is nil.
if len(a) == 0 {
copy(dst, b)
return
}
if len(b) == 0 {
copy(dst, a)
return
}
// Merged will hold all elements from both lists.
merged := dst[:0]
// Assign lead to the slice with a lower starting value, follow to the higher value. // Assign lead to the slice with a lower starting value, follow to the higher value.
lead, follow := a, b lead, follow := a, b
@ -166,7 +193,5 @@ func (a pgids) merge(b pgids) pgids {
} }
// Append what's left in follow. // Append what's left in follow.
merged = append(merged, follow...) _ = append(merged, follow...)
return merged
} }

View file

@ -381,7 +381,9 @@ func (tx *Tx) Check() <-chan error {
func (tx *Tx) check(ch chan error) { func (tx *Tx) check(ch chan error) {
// Check if any pages are double freed. // Check if any pages are double freed.
freed := make(map[pgid]bool) freed := make(map[pgid]bool)
for _, id := range tx.db.freelist.all() { all := make([]pgid, tx.db.freelist.count())
tx.db.freelist.copyall(all)
for _, id := range all {
if freed[id] { if freed[id] {
ch <- fmt.Errorf("page %d: already freed", id) ch <- fmt.Errorf("page %d: already freed", id)
} }

60
vendor/github.com/cenk/backoff/context.go generated vendored Normal file
View file

@ -0,0 +1,60 @@
package backoff
import (
"time"
"golang.org/x/net/context"
)
// BackOffContext is a backoff policy that stops retrying after the context
// is canceled.
type BackOffContext interface {
BackOff
Context() context.Context
}
type backOffContext struct {
BackOff
ctx context.Context
}
// WithContext returns a BackOffContext with context ctx
//
// ctx must not be nil
func WithContext(b BackOff, ctx context.Context) BackOffContext {
if ctx == nil {
panic("nil context")
}
if b, ok := b.(*backOffContext); ok {
return &backOffContext{
BackOff: b.BackOff,
ctx: ctx,
}
}
return &backOffContext{
BackOff: b,
ctx: ctx,
}
}
func ensureContext(b BackOff) BackOffContext {
if cb, ok := b.(BackOffContext); ok {
return cb
}
return WithContext(b, context.Background())
}
func (b *backOffContext) Context() context.Context {
return b.ctx
}
func (b *backOffContext) NextBackOff() time.Duration {
select {
case <-b.Context().Done():
return Stop
default:
return b.BackOff.NextBackOff()
}
}

View file

@ -89,11 +89,6 @@ func NewExponentialBackOff() *ExponentialBackOff {
MaxElapsedTime: DefaultMaxElapsedTime, MaxElapsedTime: DefaultMaxElapsedTime,
Clock: SystemClock, Clock: SystemClock,
} }
if b.RandomizationFactor < 0 {
b.RandomizationFactor = 0
} else if b.RandomizationFactor > 1 {
b.RandomizationFactor = 1
}
b.Reset() b.Reset()
return b return b
} }

View file

@ -17,6 +17,9 @@ type Notify func(error, time.Duration)
// o is guaranteed to be run at least once. // o is guaranteed to be run at least once.
// It is the caller's responsibility to reset b after Retry returns. // It is the caller's responsibility to reset b after Retry returns.
// //
// If o returns a *PermanentError, the operation is not retried, and the
// wrapped error is returned.
//
// Retry sleeps the goroutine for the duration returned by BackOff after a // Retry sleeps the goroutine for the duration returned by BackOff after a
// failed operation returns. // failed operation returns.
func Retry(o Operation, b BackOff) error { return RetryNotify(o, b, nil) } func Retry(o Operation, b BackOff) error { return RetryNotify(o, b, nil) }
@ -27,12 +30,18 @@ func RetryNotify(operation Operation, b BackOff, notify Notify) error {
var err error var err error
var next time.Duration var next time.Duration
cb := ensureContext(b)
b.Reset() b.Reset()
for { for {
if err = operation(); err == nil { if err = operation(); err == nil {
return nil return nil
} }
if permanent, ok := err.(*PermanentError); ok {
return permanent.Err
}
if next = b.NextBackOff(); next == Stop { if next = b.NextBackOff(); next == Stop {
return err return err
} }
@ -41,6 +50,29 @@ func RetryNotify(operation Operation, b BackOff, notify Notify) error {
notify(err, next) notify(err, next)
} }
time.Sleep(next) t := time.NewTimer(next)
select {
case <-cb.Context().Done():
t.Stop()
return err
case <-t.C:
}
}
}
// PermanentError signals that the operation should not be retried.
type PermanentError struct {
Err error
}
func (e *PermanentError) Error() string {
return e.Err.Error()
}
// Permanent wraps the given err in a *PermanentError.
func Permanent(err error) *PermanentError {
return &PermanentError{
Err: err,
} }
} }

View file

@ -13,7 +13,7 @@ import (
type Ticker struct { type Ticker struct {
C <-chan time.Time C <-chan time.Time
c chan time.Time c chan time.Time
b BackOff b BackOffContext
stop chan struct{} stop chan struct{}
stopOnce sync.Once stopOnce sync.Once
} }
@ -26,7 +26,7 @@ func NewTicker(b BackOff) *Ticker {
t := &Ticker{ t := &Ticker{
C: c, C: c,
c: c, c: c,
b: b, b: ensureContext(b),
stop: make(chan struct{}), stop: make(chan struct{}),
} }
go t.run() go t.run()
@ -58,6 +58,8 @@ func (t *Ticker) run() {
case <-t.stop: case <-t.stop:
t.c = nil // Prevent future ticks from being sent to the channel. t.c = nil // Prevent future ticks from being sent to the channel.
return return
case <-t.b.Context().Done():
return
} }
} }
} }

View file

@ -1,29 +1,80 @@
package negroni package negroni
import ( import (
"bytes"
"log" "log"
"net/http" "net/http"
"os" "os"
"text/template"
"time" "time"
) )
// LoggerEntry is the structure
// passed to the template.
type LoggerEntry struct {
StartTime string
Status int
Duration time.Duration
Hostname string
Method string
Path string
}
// LoggerDefaultFormat is the format
// logged used by the default Logger instance.
var LoggerDefaultFormat = "{{.StartTime}} | {{.Status}} | \t {{.Duration}} | {{.Hostname}} | {{.Method}} {{.Path}} \n"
// LoggerDefaultDateFormat is the
// format used for date by the
// default Logger instance.
var LoggerDefaultDateFormat = time.RFC3339
// ALogger interface
type ALogger interface {
Println(v ...interface{})
Printf(format string, v ...interface{})
}
// Logger is a middleware handler that logs the request as it goes in and the response as it goes out. // Logger is a middleware handler that logs the request as it goes in and the response as it goes out.
type Logger struct { type Logger struct {
// Logger inherits from log.Logger used to log messages with the Logger middleware // ALogger implements just enough log.Logger interface to be compatible with other implementations
*log.Logger ALogger
dateFormat string
template *template.Template
} }
// NewLogger returns a new Logger instance // NewLogger returns a new Logger instance
func NewLogger() *Logger { func NewLogger() *Logger {
return &Logger{log.New(os.Stdout, "[negroni] ", 0)} logger := &Logger{ALogger: log.New(os.Stdout, "[negroni] ", 0), dateFormat: LoggerDefaultDateFormat}
logger.SetFormat(LoggerDefaultFormat)
return logger
}
func (l *Logger) SetFormat(format string) {
l.template = template.Must(template.New("negroni_parser").Parse(format))
}
func (l *Logger) SetDateFormat(format string) {
l.dateFormat = format
} }
func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
start := time.Now() start := time.Now()
l.Printf("Started %s %s", r.Method, r.URL.Path)
next(rw, r) next(rw, r)
res := rw.(ResponseWriter) res := rw.(ResponseWriter)
l.Printf("Completed %v %s in %v", res.Status(), http.StatusText(res.Status()), time.Since(start)) log := LoggerEntry{
StartTime: start.Format(l.dateFormat),
Status: res.Status(),
Duration: time.Since(start),
Hostname: r.Host,
Method: r.Method,
Path: r.URL.Path,
}
buff := &bytes.Buffer{}
l.template.Execute(buff, log)
l.Printf(buff.String())
} }

View file

@ -59,6 +59,14 @@ func New(handlers ...Handler) *Negroni {
} }
} }
// With returns a new Negroni instance that is a combination of the negroni
// receiver's handlers and the provided handlers.
func (n *Negroni) With(handlers ...Handler) *Negroni {
return New(
append(n.handlers, handlers...)...,
)
}
// Classic returns a new Negroni instance with the default middleware already // Classic returns a new Negroni instance with the default middleware already
// in the stack. // in the stack.
// //

View file

@ -11,7 +11,7 @@ import (
// Recovery is a Negroni middleware that recovers from any panics and writes a 500 if there was one. // Recovery is a Negroni middleware that recovers from any panics and writes a 500 if there was one.
type Recovery struct { type Recovery struct {
Logger *log.Logger Logger ALogger
PrintStack bool PrintStack bool
ErrorHandlerFunc func(interface{}) ErrorHandlerFunc func(interface{})
StackAll bool StackAll bool

View file

@ -29,9 +29,15 @@ type beforeFunc func(ResponseWriter)
// NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter // NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter
func NewResponseWriter(rw http.ResponseWriter) ResponseWriter { func NewResponseWriter(rw http.ResponseWriter) ResponseWriter {
return &responseWriter{ nrw := &responseWriter{
ResponseWriter: rw, ResponseWriter: rw,
} }
if _, ok := rw.(http.CloseNotifier); ok {
return &responseWriterCloseNotifer{nrw}
}
return nrw
} }
type responseWriter struct { type responseWriter struct {
@ -81,10 +87,6 @@ func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return hijacker.Hijack() return hijacker.Hijack()
} }
func (rw *responseWriter) CloseNotify() <-chan bool {
return rw.ResponseWriter.(http.CloseNotifier).CloseNotify()
}
func (rw *responseWriter) callBefore() { func (rw *responseWriter) callBefore() {
for i := len(rw.beforeFuncs) - 1; i >= 0; i-- { for i := len(rw.beforeFuncs) - 1; i >= 0; i-- {
rw.beforeFuncs[i](rw) rw.beforeFuncs[i](rw)
@ -94,6 +96,18 @@ func (rw *responseWriter) callBefore() {
func (rw *responseWriter) Flush() { func (rw *responseWriter) Flush() {
flusher, ok := rw.ResponseWriter.(http.Flusher) flusher, ok := rw.ResponseWriter.(http.Flusher)
if ok { if ok {
if !rw.Written() {
// The status will be StatusOK if WriteHeader has not been called yet
rw.WriteHeader(http.StatusOK)
}
flusher.Flush() flusher.Flush()
} }
} }
type responseWriterCloseNotifer struct {
*responseWriter
}
func (rw *responseWriterCloseNotifer) CloseNotify() <-chan bool {
return rw.ResponseWriter.(http.CloseNotifier).CloseNotify()
}

63
vendor/github.com/coreos/etcd/pkg/fileutil/fileutil.go generated vendored Normal file
View file

@ -0,0 +1,63 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package fileutil implements utility functions related to files and paths.
package fileutil
import (
"io/ioutil"
"os"
"path"
"sort"
"github.com/coreos/pkg/capnslog"
)
const (
privateFileMode = 0600
)
var (
plog = capnslog.NewPackageLogger("github.com/coreos/etcd/pkg", "fileutil")
)
// IsDirWriteable checks if dir is writable by writing and removing a file
// to dir. It returns nil if dir is writable.
func IsDirWriteable(dir string) error {
f := path.Join(dir, ".touch")
if err := ioutil.WriteFile(f, []byte(""), privateFileMode); err != nil {
return err
}
return os.Remove(f)
}
// ReadDir returns the filenames in the given directory in sorted order.
func ReadDir(dirpath string) ([]string, error) {
dir, err := os.Open(dirpath)
if err != nil {
return nil, err
}
defer dir.Close()
names, err := dir.Readdirnames(-1)
if err != nil {
return nil, err
}
sort.Strings(names)
return names, nil
}
func Exist(name string) bool {
_, err := os.Stat(name)
return err == nil
}

View file

@ -0,0 +1,90 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fileutil
import (
"errors"
"os"
"syscall"
"time"
)
var (
ErrLocked = errors.New("file already locked")
)
type Lock interface {
Name() string
TryLock() error
Lock() error
Unlock() error
Destroy() error
}
type lock struct {
fname string
file *os.File
}
func (l *lock) Name() string {
return l.fname
}
// TryLock acquires exclusivity on the lock without blocking
func (l *lock) TryLock() error {
err := os.Chmod(l.fname, syscall.DMEXCL|0600)
if err != nil {
return err
}
f, err := os.Open(l.fname)
if err != nil {
return ErrLocked
}
l.file = f
return nil
}
// Lock acquires exclusivity on the lock with blocking
func (l *lock) Lock() error {
err := os.Chmod(l.fname, syscall.DMEXCL|0600)
if err != nil {
return err
}
for {
f, err := os.Open(l.fname)
if err == nil {
l.file = f
return nil
}
time.Sleep(10 * time.Millisecond)
}
}
// Unlock unlocks the lock
func (l *lock) Unlock() error {
return l.file.Close()
}
func (l *lock) Destroy() error {
return nil
}
func NewLock(file string) (Lock, error) {
l := &lock{fname: file}
return l, nil
}

View file

@ -0,0 +1,98 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
// +build solaris
package fileutil
import (
"errors"
"os"
"syscall"
)
var (
ErrLocked = errors.New("file already locked")
)
type Lock interface {
Name() string
TryLock() error
Lock() error
Unlock() error
Destroy() error
}
type lock struct {
fd int
file *os.File
}
func (l *lock) Name() string {
return l.file.Name()
}
// TryLock acquires exclusivity on the lock without blocking
func (l *lock) TryLock() error {
var lock syscall.Flock_t
lock.Start = 0
lock.Len = 0
lock.Pid = 0
lock.Type = syscall.F_WRLCK
lock.Whence = 0
lock.Pid = 0
err := syscall.FcntlFlock(uintptr(l.fd), syscall.F_SETLK, &lock)
if err != nil && err == syscall.EAGAIN {
return ErrLocked
}
return err
}
// Lock acquires exclusivity on the lock without blocking
func (l *lock) Lock() error {
var lock syscall.Flock_t
lock.Start = 0
lock.Len = 0
lock.Type = syscall.F_WRLCK
lock.Whence = 0
lock.Pid = 0
return syscall.FcntlFlock(uintptr(l.fd), syscall.F_SETLK, &lock)
}
// Unlock unlocks the lock
func (l *lock) Unlock() error {
var lock syscall.Flock_t
lock.Start = 0
lock.Len = 0
lock.Type = syscall.F_UNLCK
lock.Whence = 0
err := syscall.FcntlFlock(uintptr(l.fd), syscall.F_SETLK, &lock)
if err != nil && err == syscall.EAGAIN {
return ErrLocked
}
return err
}
func (l *lock) Destroy() error {
return l.file.Close()
}
func NewLock(file string) (Lock, error) {
f, err := os.OpenFile(file, os.O_WRONLY, 0600)
if err != nil {
return nil, err
}
l := &lock{int(f.Fd()), f}
return l, nil
}

View file

@ -0,0 +1,76 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
// +build !windows,!plan9,!solaris
package fileutil
import (
"errors"
"os"
"syscall"
)
var (
ErrLocked = errors.New("file already locked")
)
type Lock interface {
Name() string
TryLock() error
Lock() error
Unlock() error
Destroy() error
}
type lock struct {
fd int
file *os.File
}
func (l *lock) Name() string {
return l.file.Name()
}
// TryLock acquires exclusivity on the lock without blocking
func (l *lock) TryLock() error {
err := syscall.Flock(l.fd, syscall.LOCK_EX|syscall.LOCK_NB)
if err != nil && err == syscall.EWOULDBLOCK {
return ErrLocked
}
return err
}
// Lock acquires exclusivity on the lock without blocking
func (l *lock) Lock() error {
return syscall.Flock(l.fd, syscall.LOCK_EX)
}
// Unlock unlocks the lock
func (l *lock) Unlock() error {
return syscall.Flock(l.fd, syscall.LOCK_UN)
}
func (l *lock) Destroy() error {
return l.file.Close()
}
func NewLock(file string) (Lock, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
l := &lock{int(f.Fd()), f}
return l, nil
}

View file

@ -0,0 +1,71 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
// +build windows
package fileutil
import (
"errors"
"os"
)
var (
ErrLocked = errors.New("file already locked")
)
type Lock interface {
Name() string
TryLock() error
Lock() error
Unlock() error
Destroy() error
}
type lock struct {
fd int
file *os.File
}
func (l *lock) Name() string {
return l.file.Name()
}
// TryLock acquires exclusivity on the lock without blocking
func (l *lock) TryLock() error {
return nil
}
// Lock acquires exclusivity on the lock without blocking
func (l *lock) Lock() error {
return nil
}
// Unlock unlocks the lock
func (l *lock) Unlock() error {
return nil
}
func (l *lock) Destroy() error {
return l.file.Close()
}
func NewLock(file string) (Lock, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
l := &lock{int(f.Fd()), f}
return l, nil
}

View file

@ -0,0 +1,28 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
// +build !linux
package fileutil
import "os"
// Preallocate tries to allocate the space for given
// file. This operation is only supported on linux by a
// few filesystems (btrfs, ext4, etc.).
// If the operation is unsupported, no error will be returned.
// Otherwise, the error encountered will be returned.
func Preallocate(f *os.File, sizeInBytes int) error {
return nil
}

View file

@ -0,0 +1,42 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
// +build linux
package fileutil
import (
"os"
"syscall"
)
// Preallocate tries to allocate the space for given
// file. This operation is only supported on linux by a
// few filesystems (btrfs, ext4, etc.).
// If the operation is unsupported, no error will be returned.
// Otherwise, the error encountered will be returned.
func Preallocate(f *os.File, sizeInBytes int) error {
// use mode = 1 to keep size
// see FALLOC_FL_KEEP_SIZE
err := syscall.Fallocate(int(f.Fd()), 1, 0, int64(sizeInBytes))
if err != nil {
errno, ok := err.(syscall.Errno)
// treat not support as nil error
if ok && errno == syscall.ENOTSUP {
return nil
}
return err
}
return nil
}

80
vendor/github.com/coreos/etcd/pkg/fileutil/purge.go generated vendored Normal file
View file

@ -0,0 +1,80 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fileutil
import (
"os"
"path"
"sort"
"strings"
"time"
)
func PurgeFile(dirname string, suffix string, max uint, interval time.Duration, stop <-chan struct{}) <-chan error {
errC := make(chan error, 1)
go func() {
for {
fnames, err := ReadDir(dirname)
if err != nil {
errC <- err
return
}
newfnames := make([]string, 0)
for _, fname := range fnames {
if strings.HasSuffix(fname, suffix) {
newfnames = append(newfnames, fname)
}
}
sort.Strings(newfnames)
for len(newfnames) > int(max) {
f := path.Join(dirname, newfnames[0])
l, err := NewLock(f)
if err != nil {
errC <- err
return
}
err = l.TryLock()
if err != nil {
break
}
err = os.Remove(f)
if err != nil {
errC <- err
return
}
err = l.Unlock()
if err != nil {
plog.Errorf("error unlocking %s when purging file (%v)", l.Name(), err)
errC <- err
return
}
err = l.Destroy()
if err != nil {
plog.Errorf("error destroying lock %s when purging file (%v)", l.Name(), err)
errC <- err
return
}
plog.Infof("purged file %s successfully", f)
newfnames = newfnames[1:]
}
select {
case <-time.After(interval):
case <-stop:
return
}
}
}()
return errC
}

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