mirror of
https://github.com/containous/traefik.git
synced 2025-09-26 01:44:23 +03:00
Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
74ad83f05a | ||
|
d707c8ba93 | ||
|
640eb62ca1 | ||
|
216710864e | ||
|
226f20b626 | ||
|
151be83bce | ||
|
d1a8c7fa78 | ||
|
254dc38c3d | ||
|
24d084d7e6 | ||
|
df5f530058 | ||
|
753d173965 | ||
|
ffd1f122de | ||
|
f98b57fdf4 | ||
|
2d37f08864 | ||
|
a7dbcc282c | ||
|
f4f62e7fb3 | ||
|
4cae8bcb10 | ||
|
bee370ec6b | ||
|
f1d016b893 | ||
|
4defbbe848 | ||
|
f397342f16 | ||
|
989a59cc29 | ||
|
c5b71592c8 | ||
|
9de3129a55 | ||
|
40ab1f325c | ||
|
73e0561610 | ||
|
0a89cccdc0 | ||
|
b102e6de5a | ||
|
2064a6f805 | ||
|
72e2ddff98 | ||
|
7db41967c6 | ||
|
a8680a8719 | ||
|
7e11fa1193 | ||
|
489b5a6150 | ||
|
8027e8ee23 | ||
|
d35d5bd2e8 | ||
|
8e47bdedc6 | ||
|
51419a9235 | ||
|
32fd52c698 | ||
|
468e4ebd98 | ||
|
92a57384a4 | ||
|
2067a433cd | ||
|
16f1f851cc | ||
|
8e992c7cfb |
20
.semaphoreci/golang.sh
Executable file
20
.semaphoreci/golang.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
curl -O https://dl.google.com/go/go1.12.linux-amd64.tar.gz
|
||||
|
||||
tar -xvf go1.12.linux-amd64.tar.gz
|
||||
rm -rf go1.12.linux-amd64.tar.gz
|
||||
|
||||
sudo mkdir -p /usr/local/golang/1.12/go
|
||||
sudo mv go /usr/local/golang/1.12/
|
||||
|
||||
sudo rm /usr/local/bin/go
|
||||
sudo chmod +x /usr/local/golang/1.12/go/bin/go
|
||||
sudo ln -s /usr/local/golang/1.12/go/bin/go /usr/local/bin/go
|
||||
|
||||
export GOROOT="/usr/local/golang/1.12/go"
|
||||
export GOTOOLDIR="/usr/local/golang/1.12/go/pkg/tool/linux_amd64"
|
||||
|
||||
go version
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
export DOCKER_VERSION=17.03.1
|
||||
export DOCKER_VERSION=18.09.7
|
||||
|
||||
source .semaphoreci/vars
|
||||
|
||||
|
@@ -13,6 +13,7 @@ env:
|
||||
- VERSION: $TRAVIS_TAG
|
||||
- CODENAME: maroilles
|
||||
- N_MAKE_JOBS: 2
|
||||
- DOCS_VERIFY_SKIP: true
|
||||
|
||||
script:
|
||||
- echo "Skipping tests... (Tests are executed on SemaphoreCI)"
|
||||
|
74
CHANGELOG.md
74
CHANGELOG.md
@@ -1,5 +1,79 @@
|
||||
# Change Log
|
||||
|
||||
## [v1.7.18](https://github.com/containous/traefik/tree/v1.7.18) (2019-09-23)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.7.17...v1.7.18)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[go,security]** This version is compiled with [Go 1.12.10](https://groups.google.com/d/msg/golang-announce/cszieYyuL9Q/g4Z7pKaqAgAJ), which fixes a vulnerability in previous versions. See the [CVE](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16276) about it for more details.
|
||||
|
||||
## [v1.7.17](https://github.com/containous/traefik/tree/v1.7.17) (2019-09-23)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.7.16...v1.7.17)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[logs,middleware]** Avoid closing stdout when the accesslog handler is closed ([#5459](https://github.com/containous/traefik/pull/5459) by [nrwiersma](https://github.com/nrwiersma))
|
||||
- **[middleware]** Actually send header and code during WriteHeader, if needed ([#5404](https://github.com/containous/traefik/pull/5404) by [mpl](https://github.com/mpl))
|
||||
|
||||
**Documentation:**
|
||||
- **[k8s]** Add note clarifying client certificate header ([#5362](https://github.com/containous/traefik/pull/5362) by [bradjones1](https://github.com/bradjones1))
|
||||
- **[webui]** Update docs links. ([#5412](https://github.com/containous/traefik/pull/5412) by [ldez](https://github.com/ldez))
|
||||
- Update Traefik image version. ([#5399](https://github.com/containous/traefik/pull/5399) by [ldez](https://github.com/ldez))
|
||||
|
||||
## [v1.7.16](https://github.com/containous/traefik/tree/v1.7.16) (2019-09-13)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.7.15...v1.7.16)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[middleware,websocket]** implement Flusher and Hijacker for codeCatcher ([#5376](https://github.com/containous/traefik/pull/5376) by [mpl](https://github.com/mpl))
|
||||
|
||||
## [v1.7.15](https://github.com/containous/traefik/tree/v1.7.15) (2019-09-12)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.7.14...v1.7.15)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[authentication,k8s/ingress]** Kubernetes support for Auth.HeaderField ([#5235](https://github.com/containous/traefik/pull/5235) by [ErikWegner](https://github.com/ErikWegner))
|
||||
- **[k8s,k8s/ingress]** Finish kubernetes throttling refactoring ([#5269](https://github.com/containous/traefik/pull/5269) by [mpl](https://github.com/mpl))
|
||||
- **[k8s]** Throttle Kubernetes config refresh ([#4716](https://github.com/containous/traefik/pull/4716) by [benweissmann](https://github.com/benweissmann))
|
||||
- **[k8s]** Fix wrong handling of insecure tls auth forward ingress annotation ([#5319](https://github.com/containous/traefik/pull/5319) by [majkrzak](https://github.com/majkrzak))
|
||||
- **[middleware]** error pages: do not buffer response when it's not an error ([#5285](https://github.com/containous/traefik/pull/5285) by [mpl](https://github.com/mpl))
|
||||
- **[tls]** Consider default cert domain in certificate store ([#5353](https://github.com/containous/traefik/pull/5353) by [nrwiersma](https://github.com/nrwiersma))
|
||||
- **[tls]** Add TLS minversion constraint ([#5356](https://github.com/containous/traefik/pull/5356) by [dtomcej](https://github.com/dtomcej))
|
||||
|
||||
**Documentation:**
|
||||
- **[acme]** Update Acme doc - Vultr Wildcard & Root ([#5320](https://github.com/containous/traefik/pull/5320) by [ddymko](https://github.com/ddymko))
|
||||
- **[consulcatalog]** Typo in basic auth usersFile label consul-catalog ([#5230](https://github.com/containous/traefik/pull/5230) by [pitan](https://github.com/pitan))
|
||||
- **[logs]** Improve Access Logs Documentation page ([#5238](https://github.com/containous/traefik/pull/5238) by [dduportal](https://github.com/dduportal))
|
||||
|
||||
## [v1.7.14](https://github.com/containous/traefik/tree/v1.7.14) (2019-08-14)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.7.13...v1.7.14)
|
||||
|
||||
**Bug fixes:**
|
||||
- Update to go1.12.8 ([#5201](https://github.com/containous/traefik/pull/5201) by [ldez](https://github.com/ldez)). HTTP/2 Denial of Service [CVE-2019-9512](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9512) and [CVE-2019-9514](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9514)
|
||||
- **[server]** Make hijackConnectionTracker.Close thread safe ([#5194](https://github.com/containous/traefik/pull/5194) by [jlevesy](https://github.com/jlevesy))
|
||||
|
||||
## [v1.7.13](https://github.com/containous/traefik/tree/v1.7.13) (2019-08-07)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.7.12...v1.7.13)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Update lego ([#5166](https://github.com/containous/traefik/pull/5166) by [dabeck](https://github.com/dabeck))
|
||||
- **[consulcatalog]** warning should not be a fail status ([#4537](https://github.com/containous/traefik/pull/4537) by [saez0pub](https://github.com/saez0pub))
|
||||
- **[docker]** Update docker api version ([#4909](https://github.com/containous/traefik/pull/4909) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[dynamodb]** Use dynamodbav tags to override json tags. ([#5002](https://github.com/containous/traefik/pull/5002) by [ldez](https://github.com/ldez))
|
||||
- **[healthcheck]** Wrr loadbalancer honors old weight on recovered servers ([#5051](https://github.com/containous/traefik/pull/5051) by [DougWagner](https://github.com/DougWagner))
|
||||
- **[k8s]** Check for multiport services on Global Backend Ingress ([#5021](https://github.com/containous/traefik/pull/5021) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[logs]** Allows logs to use local time zone instead of UTC ([#4954](https://github.com/containous/traefik/pull/4954) by [dduportal](https://github.com/dduportal))
|
||||
- **[middleware]** Clear TLS client headers if TLSMutualAuth is optional ([#4963](https://github.com/containous/traefik/pull/4963) by [stffabi](https://github.com/stffabi))
|
||||
- **[tls]** Add missing KeyUsages for default generated certificate ([#5150](https://github.com/containous/traefik/pull/5150) by [dtomcej](https://github.com/dtomcej))
|
||||
|
||||
**Documentation:**
|
||||
- **[acme]** Fixed doc link for AlibabaCloud ([#5109](https://github.com/containous/traefik/pull/5109) by [ddymko](https://github.com/ddymko))
|
||||
- **[docker]** Add example for CLI ([#5131](https://github.com/containous/traefik/pull/5131) by [alvarezbruned](https://github.com/alvarezbruned))
|
||||
- **[docker]** Use the latest stable version of traefik in the docs ([#4927](https://github.com/containous/traefik/pull/4927) by [kolaente](https://github.com/kolaente))
|
||||
- **[logs]** Update documentation to clarify the default format for logs ([#4953](https://github.com/containous/traefik/pull/4953) by [dduportal](https://github.com/dduportal))
|
||||
- **[rancher]** Add remarks about Rancher 2 ([#4999](https://github.com/containous/traefik/pull/4999) by [ldez](https://github.com/ldez))
|
||||
- **[tls]** Fixes the TLS Mutual Authentication documentation ([#5085](https://github.com/containous/traefik/pull/5085) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- Format YAML example on user guide ([#5067](https://github.com/containous/traefik/pull/5067) by [gurayyildirim](https://github.com/gurayyildirim))
|
||||
- Update Slack support channel references to Discourse community forum ([#5014](https://github.com/containous/traefik/pull/5014) by [dduportal](https://github.com/dduportal))
|
||||
- Updating Service Fabric documentation ([#5160](https://github.com/containous/traefik/pull/5160) by [gheibia](https://github.com/gheibia))
|
||||
- Improve API / Dashboard wording in documentation ([#4929](https://github.com/containous/traefik/pull/4929) by [dduportal](https://github.com/dduportal))
|
||||
|
||||
## [v1.7.12](https://github.com/containous/traefik/tree/v1.7.12) (2019-05-29)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.7.11...v1.7.12)
|
||||
|
||||
|
@@ -13,7 +13,7 @@ You need to run the `binary` target. This will create binaries for Linux platfor
|
||||
$ make binary
|
||||
docker build -t "traefik-dev:no-more-godep-ever" -f build.Dockerfile .
|
||||
Sending build context to Docker daemon 295.3 MB
|
||||
Step 0 : FROM golang:1.11-alpine
|
||||
Step 0 : FROM golang:1.12-alpine
|
||||
---> 8c6473912976
|
||||
Step 1 : RUN go get github.com/golang/dep/cmd/dep
|
||||
[...]
|
||||
@@ -158,7 +158,7 @@ Integration tests must be run from the `integration/` directory and require the
|
||||
|
||||
## Documentation
|
||||
|
||||
The [documentation site](https://docs.traefik.io/) is built with [mkdocs](https://mkdocs.org/)
|
||||
The [documentation site](https://docs.traefik.io/v1.7/) is built with [mkdocs](https://mkdocs.org/)
|
||||
|
||||
### Building Documentation
|
||||
|
||||
|
267
Gopkg.lock
generated
267
Gopkg.lock
generated
@@ -17,6 +17,14 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "02e53af36e6c978af692887ed449b74026d76fec"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3fb854fc35fafced21615a93db3c0e324c8cb1d5a89f39dd1e72db0c4200345a"
|
||||
name = "contrib.go.opencensus.io/exporter/ocagent"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "dcb33c7f3b7cfe67e8a2cea10207ede1b7c40764"
|
||||
version = "v0.4.12"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:24afd6a7be85997c981693ddcc5f7b37fd3ed5cb119901e14737b9635b944cfb"
|
||||
@@ -34,15 +42,15 @@
|
||||
revision = "72eebf980f467d3ab3a8b4ddf660f664911ce519"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:59f6c2fd10ad014d2907eaa48a19070d5e7af35624328a4fbbf93b7b1c4a19e8"
|
||||
digest = "1:1a06fd7e9325a0b2d1b44ff0253afac49857863cb86ba30ea8212690b7c6287e"
|
||||
name = "github.com/Azure/azure-sdk-for-go"
|
||||
packages = [
|
||||
"services/dns/mgmt/2017-09-01/dns",
|
||||
"version",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "068ec4d616be5b2175509bf1fb3e4c8ea160d5c8"
|
||||
version = "v15.0.1"
|
||||
revision = "98f2f9ff6ad63a307bb3438b7631af594b11f32b"
|
||||
version = "v31.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -56,19 +64,23 @@
|
||||
revision = "d6e3b3328b783f23731bc4d058875b0371ff8109"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2526235ff8cae0162b34e8af3779621de71b3a020dead9bcce74d3f43a18c988"
|
||||
digest = "1:847ae8f26c6796f0dbed826b7bc0297eae976fa2b24045e3e2ddb26846378a65"
|
||||
name = "github.com/Azure/go-autorest"
|
||||
packages = [
|
||||
"autorest",
|
||||
"autorest/adal",
|
||||
"autorest/azure",
|
||||
"autorest/azure/auth",
|
||||
"autorest/azure/cli",
|
||||
"autorest/date",
|
||||
"autorest/to",
|
||||
"autorest/validation",
|
||||
"logger",
|
||||
"tracing",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "9ad9326b278af8fa5cc67c30c0ce9a58cc0862b2"
|
||||
version = "v10.6.0"
|
||||
revision = "ba1147dc57f993013ef255c128ca1cac8a557409"
|
||||
version = "v12.4.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -89,13 +101,6 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "6add9cd6ad42d389d6ead1dde60b4ad71e46fd74"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ab7fee312bbdc8070d0325d841de8704cc78bf032b076200f1458659b74b8ed6"
|
||||
name = "github.com/JamesClonk/vultr"
|
||||
packages = ["lib"]
|
||||
pruneopts = "NUT"
|
||||
revision = "2fd0705ce648e602e6c9c57329a174270a4f6688"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:25870183293a3fb61cc9afd060a61d63a486f091db72af01a8ea3449f5ca530d"
|
||||
name = "github.com/Masterminds/goutils"
|
||||
@@ -215,7 +220,7 @@
|
||||
revision = "063d875e3c5fd734fa2aa12fac83829f62acfc70"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:47071ecf8d840dd357ede1b2aed46576bdd0a866adecef3c9e85a00db9672202"
|
||||
digest = "1:dddee1f9ce7caecc95ae089c721a65ed42b35f8cc8a8b8a3ee6e3758ec93ec4b"
|
||||
name = "github.com/akamai/AkamaiOPEN-edgegrid-golang"
|
||||
packages = [
|
||||
"client-v1",
|
||||
@@ -224,16 +229,17 @@
|
||||
"jsonhooks-v1",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "1471ce9c14c6d8c007516e129262962a628fecdf"
|
||||
version = "v0.7.3"
|
||||
revision = "009960c8b2c7c57a0c5c488a3c8c778c16f3f586"
|
||||
version = "v0.7.4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:823e87ae25170339e2bfd1d6f7c2e27554c6bb5655f91c67b37bd5be45bb6b32"
|
||||
digest = "1:cd8348ab7b1ce85f965157e966791bb13514ea933902ecd1c010b5c529657f3d"
|
||||
name = "github.com/aliyun/alibaba-cloud-sdk-go"
|
||||
packages = [
|
||||
"sdk",
|
||||
"sdk/auth",
|
||||
"sdk/auth/credentials",
|
||||
"sdk/auth/credentials/provider",
|
||||
"sdk/auth/signers",
|
||||
"sdk/endpoints",
|
||||
"sdk/errors",
|
||||
@@ -243,8 +249,8 @@
|
||||
"services/alidns",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "cad214d7d71fba7883fcf3b7e550ba782c15b400"
|
||||
version = "1.27.7"
|
||||
revision = "26f0a5c2d5dc6d4a73c62651d146bdd51dc3bdde"
|
||||
version = "1.60.84"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b39cf81d5f440b9c0757a25058432d33af867e5201109bf53621356d9dab4b73"
|
||||
@@ -263,7 +269,7 @@
|
||||
revision = "48572f11356f1843b694f21a290d4f1006bc5e47"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:bfb036834a43e76abd318f0db39b0bbec6f7865680c1e443475c0297250a89ed"
|
||||
digest = "1:bd56ffde8e12e9faca92f3ac54347c665d931856fba8784007b02e004fbcc30d"
|
||||
name = "github.com/aws/aws-sdk-go"
|
||||
packages = [
|
||||
"aws",
|
||||
@@ -275,15 +281,20 @@
|
||||
"aws/credentials",
|
||||
"aws/credentials/ec2rolecreds",
|
||||
"aws/credentials/endpointcreds",
|
||||
"aws/credentials/processcreds",
|
||||
"aws/credentials/stscreds",
|
||||
"aws/crr",
|
||||
"aws/csm",
|
||||
"aws/defaults",
|
||||
"aws/ec2metadata",
|
||||
"aws/endpoints",
|
||||
"aws/request",
|
||||
"aws/session",
|
||||
"aws/signer/v4",
|
||||
"internal/ini",
|
||||
"internal/sdkio",
|
||||
"internal/sdkrand",
|
||||
"internal/sdkuri",
|
||||
"internal/shareddefaults",
|
||||
"private/protocol",
|
||||
"private/protocol/ec2query",
|
||||
@@ -302,10 +313,11 @@
|
||||
"service/lightsail",
|
||||
"service/route53",
|
||||
"service/sts",
|
||||
"service/sts/stsiface",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "fad131ddc707880428615dc8bc1587b55fb46d74"
|
||||
version = "v1.13.54"
|
||||
revision = "f3fe96892dc0e8e8e9862456e1455e62e4cb55d4"
|
||||
version = "v1.21.9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -332,12 +344,27 @@
|
||||
version = "v2.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:03cfacdc6bfd46007c15786c1ece3fa074f89e5193a292f0f26d9e98c99c7cc2"
|
||||
digest = "1:8f5acd4d4462b5136af644d25101f0968a7a94ee90fcb2059cec5b7cc42e0b20"
|
||||
name = "github.com/census-instrumentation/opencensus-proto"
|
||||
packages = [
|
||||
"gen-go/agent/common/v1",
|
||||
"gen-go/agent/metrics/v1",
|
||||
"gen-go/agent/trace/v1",
|
||||
"gen-go/metrics/v1",
|
||||
"gen-go/resource/v1",
|
||||
"gen-go/trace/v1",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "d89fa54de508111353cb0b06403c00569be780d8"
|
||||
version = "v0.2.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b63319bd1e224704c8a4d7d65c82791ef5ceb168b7c2c028193595064c910f1a"
|
||||
name = "github.com/cloudflare/cloudflare-go"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "1f9007fbecae20711133c60519338c41cef1ffb4"
|
||||
version = "v0.8.5"
|
||||
revision = "b59d4f05eec361422b388c7ed74293ce8a3d5f97"
|
||||
version = "v0.9.4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -450,12 +477,12 @@
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:d3972abc351cd5578e3281d332ac9e7a04dafc6e5af97c064fe396c4f90ee769"
|
||||
digest = "1:fa62421bd924623ac10a160686cc55d529f7274b2caedf7d2c607d14bc50c118"
|
||||
name = "github.com/decker502/dnspod-go"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "f33a2c6040fc2550a631de7b3a53bddccdcd73fb"
|
||||
revision = "71fbbdbdf1a7eeac949586de15bf96d416d3dd63"
|
||||
version = "v0.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7a6852b35eb5bbc184561443762d225116ae630c26a7c4d90546619f1e7d2ad2"
|
||||
@@ -466,20 +493,20 @@
|
||||
version = "v3.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f9adc21a937e5da643ea14a3488cb7506788876737a5e205394e508627a6eec8"
|
||||
name = "github.com/dimchansky/utfbom"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "d2133a1ce379ef6fa992b0514a77146c60db9d1c"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e856fc44ab196970612bdc8c15e65ccf92ed8d4ccb3a2e65b88dc240a2fe5d0b"
|
||||
digest = "1:d9688055094edd046a71ee85204ea97090ecce6f316b739be3a50f5ca6403ebb"
|
||||
name = "github.com/dnsimple/dnsimple-go"
|
||||
packages = ["dnsimple"]
|
||||
pruneopts = "NUT"
|
||||
revision = "f5ead9c20763fd925dea1362f2af5d671ed2a459"
|
||||
version = "v0.21.0"
|
||||
revision = "7e193cc468a07cdf74d76de1e6736c709c5a3872"
|
||||
version = "v0.30.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cf7cba074c4d2f8e2a5cc2f10b1f6762c86cff2e39917b9f9a6dbd7df57fe9c9"
|
||||
@@ -690,12 +717,12 @@
|
||||
revision = "89ef8af493ab468a45a42bb0d89a06fccdd2fb22"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6f26e34204ddad638d25456c6443c2763aa38954558226722c253e08e9f491fa"
|
||||
digest = "1:efcc176c5cfbe8421dd3eb2562be91c3272e80f61b181e31d5745b7fee29a669"
|
||||
name = "github.com/exoscale/egoscale"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "0a91ac8209d6a805f259ff881d0c2654221d0346"
|
||||
version = "v0.14.3"
|
||||
revision = "8f608c40ae891e0240bb6e696a72437be7069d83"
|
||||
version = "v0.18.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b0d5e98ac0f0a509eb320f542e748582d637aae09e74538212e9712d1e71064b"
|
||||
@@ -735,7 +762,7 @@
|
||||
revision = "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d82b2dc81c551e7c15f31523a2cc8ee9121b39cfbf63174d98a0bc8edf2d3c5e"
|
||||
digest = "1:896402a6d43c4fb1e21512e7473a51e7fd2b03a4e1a420a730a4b712d20e5d6a"
|
||||
name = "github.com/go-acme/lego"
|
||||
packages = [
|
||||
"acme",
|
||||
@@ -798,6 +825,7 @@
|
||||
"providers/dns/mydnsjp",
|
||||
"providers/dns/namecheap",
|
||||
"providers/dns/namedotcom",
|
||||
"providers/dns/namesilo",
|
||||
"providers/dns/netcup",
|
||||
"providers/dns/netcup/internal",
|
||||
"providers/dns/nifcloud",
|
||||
@@ -816,6 +844,7 @@
|
||||
"providers/dns/stackpath",
|
||||
"providers/dns/transip",
|
||||
"providers/dns/vegadns",
|
||||
"providers/dns/versio",
|
||||
"providers/dns/vscale",
|
||||
"providers/dns/vscale/internal",
|
||||
"providers/dns/vultr",
|
||||
@@ -823,8 +852,8 @@
|
||||
"registration",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "01903cdfb9869df45cf5274c53226823a2532f2d"
|
||||
version = "v2.6.0"
|
||||
revision = "295dd66f2aa549685e3d6b95c1813881a7807158"
|
||||
version = "v2.7.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "fork-containous"
|
||||
@@ -906,14 +935,6 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "1d0bd113de87027671077d3c71eb3ac5d7dbba72"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:00f1b1d722a012f54670ecc09ee92557d7314bfd9152a085ce91933c1c14b026"
|
||||
name = "github.com/go-resty/resty"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "d4920dcf5b7689548a6db640278a9b35a5b48ec6"
|
||||
version = "v1.9.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8cf58169eb0a8c009ed3a4c36486980d602ab4cc4e478130493d6cd0404f889b"
|
||||
name = "github.com/go-stack/stack"
|
||||
@@ -922,6 +943,14 @@
|
||||
revision = "54be5f394ed2c3e19dac9134a40a95ba5a017f7b"
|
||||
version = "v1.5.4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:bde9f189072512ba353f3641d4839cb4c9c7edf421e467f2c03f267b402bd16c"
|
||||
name = "github.com/gofrs/uuid"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "6b08a5c5172ba18946672b49749cde22873dd7c2"
|
||||
version = "v3.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b518b9be1fc76244e246afe09113e3dd6246073b444787d30883877b82a0b90d"
|
||||
name = "github.com/gogo/protobuf"
|
||||
@@ -943,18 +972,25 @@
|
||||
revision = "44145f04b68cf362d9c4df2182967c2275eaefed"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:03e14cff610a8a58b774e36bd337fa979482be86aab01be81fb8bbd6d0f07fc8"
|
||||
digest = "1:1e21a0bbb10d73b781e87a9a123b96f6ebe08d0451e50f1b97c1213b302af46f"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = [
|
||||
"jsonpb",
|
||||
"proto",
|
||||
"protoc-gen-go/descriptor",
|
||||
"protoc-gen-go/generator",
|
||||
"protoc-gen-go/generator/internal/remap",
|
||||
"protoc-gen-go/plugin",
|
||||
"ptypes",
|
||||
"ptypes/any",
|
||||
"ptypes/duration",
|
||||
"ptypes/struct",
|
||||
"ptypes/timestamp",
|
||||
"ptypes/wrappers",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
|
||||
version = "v1.1.0"
|
||||
revision = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7"
|
||||
version = "v1.3.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -1073,6 +1109,18 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "2bcd89a1743fd4b373f7370ce8ddc14dfbd18229"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3107a6ef51bb9f3bc7cf235af0d92303b39afaa841f02dfc87f1d0a5d05a3fca"
|
||||
name = "github.com/grpc-ecosystem/grpc-gateway"
|
||||
packages = [
|
||||
"internal",
|
||||
"runtime",
|
||||
"utilities",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "ad529a448ba494a88058f9e5be0988713174ac86"
|
||||
version = "v1.9.5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c3f14b698c0f5c5729896489f4b526f519d1d2522e697d63f532901d0e183dff"
|
||||
name = "github.com/hashicorp/consul"
|
||||
@@ -1176,11 +1224,11 @@
|
||||
revision = "8eebe170fa1ba25d3dfb928b3f86a7313b13b9fe"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ac6d01547ec4f7f673311b4663909269bfb8249952de3279799289467837c3cc"
|
||||
digest = "1:1f2aebae7e7c856562355ec0198d8ca2fa222fb05e5b1b66632a1fce39631885"
|
||||
name = "github.com/jmespath/go-jmespath"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "0b12d6b5"
|
||||
revision = "c2b33e84"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:273d270076cfc02c2d481b187203ebb3891825f46fe78b925141a22631c52503"
|
||||
@@ -1190,12 +1238,12 @@
|
||||
revision = "72f9bd7c4e0c2a40055ab3d0f09654f730cce982"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:63d8b364f0768ffda64b8e6e15c10535f3431e3c69d051dbb37f467ada02df98"
|
||||
digest = "1:92f6419f388bd31a433b1388910f15a882c9980e383e89ebf8fb2524583707ac"
|
||||
name = "github.com/json-iterator/go"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "28452fcdec4e44348d2af0d91d1e9e38da3a9e0a"
|
||||
version = "1.0.5"
|
||||
revision = "27518f6661eba504be5a7a9a9f6d9460d892ade3"
|
||||
version = "v1.1.7"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8b3234b10eacd5edea45bf0c13a585b608749da23f94aaf29b46d9ef8a8babf4"
|
||||
@@ -1276,12 +1324,12 @@
|
||||
revision = "1113af38e5916529ad7317b0fe12e273e6e92af5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:111ff5a09a32895248270bfaef9b8b6ac163a8cde9cdd603fed64b3e4b59e8ab"
|
||||
digest = "1:7981caf0d67995c0acbd352785f45b177a2d089ed40f2005794c0772d0fbfff9"
|
||||
name = "github.com/linode/linodego"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "d0d31d8ca62fa3f7e4526ca0ce95de81e4ed001e"
|
||||
version = "v0.5.1"
|
||||
revision = "7adba57685c129bcd29a9edc7008ec3b05680240"
|
||||
version = "v0.10.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:196b0d7580e898df15a7cc5371cbfe2b8e22904f5c6c883ed5db0130e551c8fb"
|
||||
@@ -1394,12 +1442,12 @@
|
||||
revision = "d23ffcb85de31694d6ccaa23ccb4a03e55c1303f"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:b62c4f18ad6eb454ac5253e7791ded3d7867330015ca4b37b6336e57f514585e"
|
||||
digest = "1:f9f72e583aaacf1d1ac5d6121abd4afd3c690baa9e14e1d009df26bf831ba347"
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "b8bc1bf767474819792c23f32d8286a45736f1c6"
|
||||
revision = "af06845cf3004701891bf4fdb884bfe4920b3727"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -1425,6 +1473,22 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "63d60e9d0dbc60cf9164e6510889b0db6683d98c"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2f42fa12d6911c7b7659738758631bec870b7e9b4c6be5444f963cdcfccc191f"
|
||||
name = "github.com/modern-go/concurrent"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
|
||||
version = "1.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c6aca19413b13dc59c220ad7430329e2ec454cc310bc6d8de2c7e2b93c18a0f6"
|
||||
name = "github.com/modern-go/reflect2"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd"
|
||||
version = "1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7bb97a5f80a2429fa605e176e16cf682cbb5ac681f421a06efb4e3b8efae6e5f"
|
||||
name = "github.com/mvdan/xurls"
|
||||
@@ -1456,6 +1520,14 @@
|
||||
revision = "d8152159450570012552f924a0ae6ab3d8c617e0"
|
||||
version = "v0.6.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a1e8eda841bca324a6b4d88824d625b3ba8d86bf9ec68677d595c02d7502918b"
|
||||
name = "github.com/nrdcg/namesilo"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "1b10d027904464789f889ee4cd76af15c3bf4943"
|
||||
version = "v0.2.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:95d27e49401b61dd203a4cf8237037bd6cd49599651f855ac1988c4ae27b090e"
|
||||
@@ -1527,15 +1599,15 @@
|
||||
version = "v0.3.5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8a5a270130e940b167027e8c9a07c20d01410bd6053160432351139ddc2cb501"
|
||||
digest = "1:3b8fccf07d5825ca15ec7c14855ea3ff9cb62f565d8b0e1c97e0b37a0bee30b0"
|
||||
name = "github.com/oracle/oci-go-sdk"
|
||||
packages = [
|
||||
"common",
|
||||
"dns",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "5295b14d42d1417490d318ff91475c5868e0fd8f"
|
||||
version = "v4.1.0"
|
||||
revision = "bf9cc5e4c798e1e0b0749ce89c861cb13f37862d"
|
||||
version = "v5.15.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -1678,7 +1750,7 @@
|
||||
revision = "256dc444b735e061061cf46c809487313d5b0065"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:253f275bd72c42f8d234712d1574c8b222fe9b72838bfaca11b21ace9c0e3d0a"
|
||||
digest = "1:09ba8f72048eacb4b5545d81392e594f4e5622cd670e517847c47b5a194bb31c"
|
||||
name = "github.com/sacloud/libsacloud"
|
||||
packages = [
|
||||
".",
|
||||
@@ -1688,8 +1760,8 @@
|
||||
"utils/mutexkv",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "41c392dee98a83260abbe0fcd5c13beb7c75d103"
|
||||
version = "v1.21.1"
|
||||
revision = "5294ac87ea76a7dffd4fb6b088f3a6ce6c4f91b9"
|
||||
version = "v1.26.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:142520cf3c9bb85449dd0000f820b8c604531587ee654793c54909be7dabadac"
|
||||
@@ -1698,14 +1770,6 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "1d7be4effb13d2d908342d349d71a284a7542693"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6bc0652ea6e39e22ccd522458b8bdd8665bf23bdc5a20eec90056e4dc7e273ca"
|
||||
name = "github.com/satori/go.uuid"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:01252cd79aac70f16cac02a72a1067dd136e0ad6d5b597d0129cf74c739fd8d1"
|
||||
name = "github.com/sirupsen/logrus"
|
||||
@@ -1776,8 +1840,7 @@
|
||||
version = "1.0.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:21ef4b12d6b6c6dfe389caa82f859d1e35eeb24ce61c60d09db57addb6a95781"
|
||||
digest = "1:4da573eeacb95c9452ae7ddc14b06148a99b29bf98522abb221ef9799426187c"
|
||||
name = "github.com/transip/gotransip"
|
||||
packages = [
|
||||
".",
|
||||
@@ -1785,7 +1848,8 @@
|
||||
"util",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "c6e2ce0bbb4a601a909e3b7a773358d6b503e663"
|
||||
revision = "efb64632cab7701ec33f1eaeaa738e2207efe68e"
|
||||
version = "v5.14"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9b2996458a2f7d1f3e0ebf08152acfe8c1106f3fe855d08121c5ee7d801a063f"
|
||||
@@ -1891,6 +1955,14 @@
|
||||
revision = "939c094524d124c55fa8afe0e077701db4a865e2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b1d2c51a689eef501cb5726f8d9997c0ca4415cbfa7105fe6e64b1844eddf1fb"
|
||||
name = "github.com/vultr/govultr"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "ca447e056e08d93aa6e5b09e6ae3565dd1825281"
|
||||
version = "v0.1.4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:669d39abbe79469bb50e32a26541026fd91326ac0ca56a9ad83be5b9887e5a8c"
|
||||
@@ -1915,7 +1987,7 @@
|
||||
revision = "0c8571ac0ce161a5feb57375a9cdf148c98c0f70"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:045bc0ab96bb83bdffd2606f019003da03d1c139d3cb8aad13596863e4dd37d6"
|
||||
digest = "1:a6f0a3485a3edbe3d66a5036d5dd8be1da89b8e077d98bf8a95ccfdad854d6cf"
|
||||
name = "go.opencensus.io"
|
||||
packages = [
|
||||
".",
|
||||
@@ -1923,8 +1995,10 @@
|
||||
"internal/tagencoding",
|
||||
"metric/metricdata",
|
||||
"metric/metricproducer",
|
||||
"plugin/ocgrpc",
|
||||
"plugin/ochttp",
|
||||
"plugin/ochttp/propagation/b3",
|
||||
"plugin/ochttp/propagation/tracecontext",
|
||||
"resource",
|
||||
"stats",
|
||||
"stats/internal",
|
||||
@@ -1936,8 +2010,8 @@
|
||||
"trace/tracestate",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "75c0cca22312e51bfd4fafdbe9197ae399e18b38"
|
||||
version = "v0.20.2"
|
||||
revision = "df6e2001952312404b06f5f6f03fcb4aec1648e5"
|
||||
version = "v0.21.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -2010,6 +2084,14 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "ec22f46f877b4505e0117eeaab541714644fdd28"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:382bb5a7fb4034db3b6a2d19e5a4a6bcf52f4750530603c01ca18a172fa3089b"
|
||||
name = "golang.org/x/sync"
|
||||
packages = ["semaphore"]
|
||||
pruneopts = "NUT"
|
||||
revision = "112230192c580c3556b8cee6403af37a4fc5f28c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:4e67fdd7a13cbdb3c0dff0a7505abbdf4f42b12b27da350d66bffdc700db2899"
|
||||
@@ -2054,7 +2136,7 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:70c173b8ecc111dd01dc07f0ada72c076e4ed91618ee559312ef8adf154cc539"
|
||||
digest = "1:87f5f837cf3bee0aedbb43d1eeec6dd7d741a1e382d2f3b488fe8fa6c3486cbc"
|
||||
name = "google.golang.org/api"
|
||||
packages = [
|
||||
"dns/v1",
|
||||
@@ -2064,6 +2146,7 @@
|
||||
"googleapi/transport",
|
||||
"internal",
|
||||
"option",
|
||||
"support/bundler",
|
||||
"transport/http",
|
||||
"transport/http/internal/propagation",
|
||||
]
|
||||
@@ -2089,9 +2172,13 @@
|
||||
revision = "4f7eeb5305a4ba1966344836ba4af9996b7b4e05"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:93180612a69db36a06d801302b867d53a50a8a5f0943b34db66adc0574ea57df"
|
||||
digest = "1:a9d09c09842a02162edf759a35329c180e8ce82c2c6ae455a3c56d67b549be5b"
|
||||
name = "google.golang.org/genproto"
|
||||
packages = ["googleapis/rpc/status"]
|
||||
packages = [
|
||||
"googleapis/api/httpbody",
|
||||
"googleapis/rpc/status",
|
||||
"protobuf/field_mask",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "09f6ed296fc66555a25fe4ce95173148778dfa85"
|
||||
|
||||
@@ -2185,7 +2272,15 @@
|
||||
revision = "a5bcac82d3f637d3928d30476610891935b2d691"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:85a9c61c8d566fbbb108da8714893e6fc892fdb7a2910612a27f551ce30a0dd3"
|
||||
digest = "1:0942599d1f614d9ca4dfe052db1f60d4547b1b581206006be352f629a8b37d8d"
|
||||
name = "gopkg.in/resty.v1"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "fa5875c0caa5c260ab78acec5a244215a730247f"
|
||||
version = "v1.12.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a56dc1133e9c449d41d22ffabd6c2343cd25ab31c9cad0e74a144006810d1544"
|
||||
name = "gopkg.in/square/go-jose.v2"
|
||||
packages = [
|
||||
".",
|
||||
@@ -2193,8 +2288,8 @@
|
||||
"json",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "6ee92191fea850cdcab9a18867abf5f521cdbadb"
|
||||
version = "v2.1.4"
|
||||
revision = "730df5f748271903322feb182be83b43ebbbe27d"
|
||||
version = "v2.3.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:302ad18387350c3d9792da66de666f76d2ca8c62c47dd6b9434269c7cfa18971"
|
||||
|
@@ -180,7 +180,11 @@
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-acme/lego"
|
||||
version = "2.6.0"
|
||||
version = "2.7.2"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/golang/protobuf"
|
||||
version = "v1.3.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "google.golang.org/grpc"
|
||||
|
2
Makefile
2
Makefile
@@ -76,7 +76,7 @@ test-integration: build ## run the integration tests
|
||||
TEST_HOST=1 ./script/make.sh test-integration
|
||||
|
||||
validate: build ## validate code, vendor and autogen
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint validate-misspell validate-vendor validate-autogen
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-golint validate-misspell validate-vendor validate-autogen
|
||||
|
||||
build: dist
|
||||
docker build $(DOCKER_BUILD_ARGS) -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
|
||||
|
30
README.md
30
README.md
@@ -4,7 +4,7 @@
|
||||
</p>
|
||||
|
||||
[](https://semaphoreci.com/containous/traefik)
|
||||
[](https://docs.traefik.io)
|
||||
[](https://docs.traefik.io/v1.7)
|
||||
[](http://goreportcard.com/report/containous/traefik)
|
||||
[](https://microbadger.com/images/traefik)
|
||||
[](https://github.com/containous/traefik/blob/master/LICENSE.md)
|
||||
@@ -70,22 +70,22 @@ _(But if you'd rather configure some of your routes manually, Traefik supports t
|
||||
|
||||
## Supported Backends
|
||||
|
||||
- [Docker](https://docs.traefik.io/configuration/backends/docker) / [Swarm mode](https://docs.traefik.io/configuration/backends/docker#docker-swarm-mode)
|
||||
- [Kubernetes](https://docs.traefik.io/configuration/backends/kubernetes)
|
||||
- [Mesos](https://docs.traefik.io/configuration/backends/mesos) / [Marathon](https://docs.traefik.io/configuration/backends/marathon)
|
||||
- [Rancher](https://docs.traefik.io/configuration/backends/rancher) (API, Metadata)
|
||||
- [Azure Service Fabric](https://docs.traefik.io/configuration/backends/servicefabric)
|
||||
- [Consul Catalog](https://docs.traefik.io/configuration/backends/consulcatalog)
|
||||
- [Consul](https://docs.traefik.io/configuration/backends/consul) / [Etcd](https://docs.traefik.io/configuration/backends/etcd) / [Zookeeper](https://docs.traefik.io/configuration/backends/zookeeper) / [BoltDB](https://docs.traefik.io/configuration/backends/boltdb)
|
||||
- [Eureka](https://docs.traefik.io/configuration/backends/eureka)
|
||||
- [Amazon ECS](https://docs.traefik.io/configuration/backends/ecs)
|
||||
- [Amazon DynamoDB](https://docs.traefik.io/configuration/backends/dynamodb)
|
||||
- [File](https://docs.traefik.io/configuration/backends/file)
|
||||
- [Rest](https://docs.traefik.io/configuration/backends/rest)
|
||||
- [Docker](https://docs.traefik.io/v1.7/configuration/backends/docker) / [Swarm mode](https://docs.traefik.io/v1.7/configuration/backends/docker#docker-swarm-mode)
|
||||
- [Kubernetes](https://docs.traefik.io/v1.7/configuration/backends/kubernetes)
|
||||
- [Mesos](https://docs.traefik.io/v1.7/configuration/backends/mesos) / [Marathon](https://docs.traefik.io/v1.7/configuration/backends/marathon)
|
||||
- [Rancher](https://docs.traefik.io/v1.7/configuration/backends/rancher) (API, Metadata)
|
||||
- [Azure Service Fabric](https://docs.traefik.io/v1.7/configuration/backends/servicefabric)
|
||||
- [Consul Catalog](https://docs.traefik.io/v1.7/configuration/backends/consulcatalog)
|
||||
- [Consul](https://docs.traefik.io/v1.7/configuration/backends/consul) / [Etcd](https://docs.traefik.io/v1.7/configuration/backends/etcd) / [Zookeeper](https://docs.traefik.io/v1.7/configuration/backends/zookeeper) / [BoltDB](https://docs.traefik.io/v1.7/configuration/backends/boltdb)
|
||||
- [Eureka](https://docs.traefik.io/v1.7/configuration/backends/eureka)
|
||||
- [Amazon ECS](https://docs.traefik.io/v1.7/configuration/backends/ecs)
|
||||
- [Amazon DynamoDB](https://docs.traefik.io/v1.7/configuration/backends/dynamodb)
|
||||
- [File](https://docs.traefik.io/v1.7/configuration/backends/file)
|
||||
- [Rest](https://docs.traefik.io/v1.7/configuration/backends/rest)
|
||||
|
||||
## Quickstart
|
||||
|
||||
To get your hands on Traefik, you can use the [5-Minute Quickstart](http://docs.traefik.io/#the-traefik-quickstart-using-docker) in our documentation (you will need Docker).
|
||||
To get your hands on Traefik, you can use the [5-Minute Quickstart](http://docs.traefik.io/v1.7/#the-traefik-quickstart-using-docker) in our documentation (you will need Docker).
|
||||
|
||||
Alternatively, if you don't want to install anything on your computer, you can try Traefik online in this great [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers.
|
||||
|
||||
@@ -100,7 +100,7 @@ You can access the simple HTML frontend of Traefik.
|
||||
|
||||
## Documentation
|
||||
|
||||
You can find the complete documentation at [https://docs.traefik.io](https://docs.traefik.io).
|
||||
You can find the complete documentation at [https://docs.traefik.io/v1.7](https://docs.traefik.io/v1.7).
|
||||
A collection of contributions around Traefik can be found at [https://awesome.traefik.io](https://awesome.traefik.io).
|
||||
|
||||
## Support
|
||||
|
@@ -1365,7 +1365,9 @@ var _templatesKubernetesTmpl = []byte(`[backends]
|
||||
|
||||
{{if $frontend.Auth }}
|
||||
[frontends."{{ $frontendName }}".auth]
|
||||
headerField = "X-WebAuth-User"
|
||||
{{if $frontend.Auth.HeaderField }}
|
||||
headerField = "{{ $frontend.Auth.HeaderField }}"
|
||||
{{end}}
|
||||
|
||||
{{if $frontend.Auth.Basic }}
|
||||
[frontends."{{ $frontendName }}".auth.basic]
|
||||
|
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.11-alpine
|
||||
FROM golang:1.12-alpine
|
||||
|
||||
RUN apk --update upgrade \
|
||||
&& apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar ca-certificates tzdata \
|
||||
@@ -6,11 +6,10 @@ RUN apk --update upgrade \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
RUN go get golang.org/x/lint/golint \
|
||||
&& go get github.com/kisielk/errcheck \
|
||||
&& go get github.com/client9/misspell/cmd/misspell
|
||||
|
||||
# Which docker version to test on
|
||||
ARG DOCKER_VERSION=17.03.2
|
||||
ARG DOCKER_VERSION=18.09.7
|
||||
ARG DEP_VERSION=0.5.1
|
||||
|
||||
# Download go-bindata binary to bin folder in $GOPATH
|
||||
@@ -25,7 +24,7 @@ RUN mkdir -p /usr/local/bin \
|
||||
|
||||
# Download docker
|
||||
RUN mkdir -p /usr/local/bin \
|
||||
&& curl -fL https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}-ce.tgz \
|
||||
&& curl -fL https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz \
|
||||
| tar -xzC /usr/local/bin --transform 's#^.+/##x'
|
||||
|
||||
WORKDIR /go/src/github.com/containous/traefik
|
||||
|
@@ -111,6 +111,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
defaultConsulCatalog.Prefix = "traefik"
|
||||
defaultConsulCatalog.FrontEndRule = "Host:{{.ServiceName}}.{{.Domain}}"
|
||||
defaultConsulCatalog.Stale = false
|
||||
defaultConsulCatalog.StrictChecks = true
|
||||
|
||||
// default Etcd
|
||||
var defaultEtcd etcd.Provider
|
||||
|
@@ -352,14 +352,14 @@ func stats(globalConfiguration *configuration.GlobalConfiguration) {
|
||||
Stats collection is enabled.
|
||||
Many thanks for contributing to Traefik's improvement by allowing us to receive anonymous information from your configuration.
|
||||
Help us improve Traefik by leaving this feature on :)
|
||||
More details on: https://docs.traefik.io/basics/#collected-data
|
||||
More details on: https://docs.traefik.io/v1.7/basics/#collected-data
|
||||
`)
|
||||
collect(globalConfiguration)
|
||||
} else {
|
||||
log.Info(`
|
||||
Stats collection is disabled.
|
||||
Help us improve Traefik by turning this feature on :)
|
||||
More details on: https://docs.traefik.io/basics/#collected-data
|
||||
More details on: https://docs.traefik.io/v1.7/basics/#collected-data
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
@@ -212,6 +212,12 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Thanks to SSLv3 being enabled by mistake in golang 1.12,
|
||||
// If no minVersion is set, apply TLS1.0 as the minimum.
|
||||
if entryPoint.TLS != nil && len(entryPoint.TLS.MinVersion) == 0 {
|
||||
entryPoint.TLS.MinVersion = "VersionTLS10"
|
||||
}
|
||||
|
||||
if entryPoint.TLS != nil && entryPoint.TLS.DefaultCertificate == nil && len(entryPoint.TLS.Certificates) > 0 {
|
||||
log.Infof("No tls.defaultCertificate given for %s: using the first item in tls.certificates as a fallback.", entryPointName)
|
||||
entryPoint.TLS.DefaultCertificate = &entryPoint.TLS.Certificates[0]
|
||||
|
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/containous/traefik/provider"
|
||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/provider/file"
|
||||
"github.com/containous/traefik/tls"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -269,3 +270,69 @@ func TestInitACMEProvider(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetEffectiveConfigurationTLSMinVersion(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
provided EntryPoint
|
||||
expected EntryPoint
|
||||
}{
|
||||
{
|
||||
desc: "Entrypoint with no TLS",
|
||||
provided: EntryPoint{
|
||||
Address: ":80",
|
||||
},
|
||||
expected: EntryPoint{
|
||||
Address: ":80",
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Entrypoint with TLS Specifying MinVersion",
|
||||
provided: EntryPoint{
|
||||
Address: ":443",
|
||||
TLS: &tls.TLS{
|
||||
MinVersion: "VersionTLS12",
|
||||
},
|
||||
},
|
||||
expected: EntryPoint{
|
||||
Address: ":443",
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
TLS: &tls.TLS{
|
||||
MinVersion: "VersionTLS12",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Entrypoint with TLS without Specifying MinVersion",
|
||||
provided: EntryPoint{
|
||||
Address: ":443",
|
||||
TLS: &tls.TLS{},
|
||||
},
|
||||
expected: EntryPoint{
|
||||
Address: ":443",
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
TLS: &tls.TLS{
|
||||
MinVersion: "VersionTLS10",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gc := &GlobalConfiguration{
|
||||
EntryPoints: map[string]*EntryPoint{
|
||||
"foo": &test.provided,
|
||||
},
|
||||
}
|
||||
|
||||
gc.SetEffectiveConfiguration(defaultConfigFile)
|
||||
|
||||
assert.Equal(t, &test.expected, gc.EntryPoints["foo"])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[Unit]
|
||||
Description=Traefik
|
||||
Documentation=https://docs.traefik.io
|
||||
Documentation=https://docs.traefik.io/v1.7
|
||||
#After=network-online.target
|
||||
#AssertFileIsExecutable=/usr/bin/traefik
|
||||
#AssertPathExists=/etc/traefik/traefik.toml
|
||||
|
@@ -279,7 +279,7 @@ For example, `CF_API_EMAIL_FILE=/run/secrets/traefik_cf-api-email` could be used
|
||||
| Provider Name | Provider Code | Environment Variables | Wildcard & Root Domain Support |
|
||||
|-------------------------------------------------------------|----------------|---------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|
|
||||
| [ACME DNS](https://github.com/joohoi/acme-dns) | `acme-dns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH` | Not tested yet |
|
||||
| [Alibaba Cloud](https://www.vultr.com) | `alidns` | `ALICLOUD_ACCESS_KEY`, `ALICLOUD_SECRET_KEY`, `ALICLOUD_REGION_ID` | Not tested yet |
|
||||
| [Alibaba Cloud](https://www.alibabacloud.com) | `alidns` | `ALICLOUD_ACCESS_KEY`, `ALICLOUD_SECRET_KEY`, `ALICLOUD_REGION_ID` | Not tested yet |
|
||||
| [Auroradns](https://www.pcextreme.com/aurora/dns) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` | Not tested yet |
|
||||
| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP`, `[AZURE_METADATA_ENDPOINT]` | Not tested yet |
|
||||
| [Bindman](https://github.com/labbsr0x/bindman-dns-webhook) | `bindman` | `BINDMAN_MANAGER_ADDRESS` | YES |
|
||||
@@ -309,13 +309,14 @@ For example, `CF_API_EMAIL_FILE=/run/secrets/traefik_cf-api-email` could be used
|
||||
| HTTP request | `httpreq` | `HTTPREQ_ENDPOINT`, `HTTPREQ_MODE`, `HTTPREQ_USERNAME`, `HTTPREQ_PASSWORD` (1) | YES |
|
||||
| [IIJ](https://www.iij.ad.jp/) | `iij` | `IIJ_API_ACCESS_KEY`, `IIJ_API_SECRET_KEY`, `IIJ_DO_SERVICE_CODE` | Not tested yet |
|
||||
| [INWX](https://www.inwx.de/en) | `inwx` | `INWX_USERNAME`, `INWX_PASSWORD` | YES |
|
||||
| [Joker.com](https://joker.com) | `joker` | `JOKER_API_KEY` | YES |
|
||||
| [Joker.com](https://joker.com) | `joker` | `JOKER_API_KEY` or `JOKER_USERNAME`, `JOKER_PASSWORD` | YES |
|
||||
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | Not tested yet |
|
||||
| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` | Not tested yet |
|
||||
| [Linode v4](https://www.linode.com) | `linodev4` | `LINODE_TOKEN` | Not tested yet |
|
||||
| manual | - | none, but you need to run Traefik interactively, turn on `acmeLogging` to see instructions and press <kbd>Enter</kbd>. | YES |
|
||||
| [MyDNS.jp](https://www.mydns.jp/) | `mydnsjp` | `MYDNSJP_MASTER_ID`, `MYDNSJP_PASSWORD` | YES |
|
||||
| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` | YES |
|
||||
| [Namesilo](https://www.namesilo.com/) | `namesilo` | `NAMESILO_API_KEY` | YES |
|
||||
| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | Not tested yet |
|
||||
| [Netcup](https://www.netcup.eu/) | `netcup` | `NETCUP_CUSTOMER_NUMBER`, `NETCUP_API_KEY`, `NETCUP_API_PASSWORD` | Not tested yet |
|
||||
| [NIFCloud](https://cloud.nifty.com/service/dns.htm) | `nifcloud` | `NIFCLOUD_ACCESS_KEY_ID`, `NIFCLOUD_SECRET_ACCESS_KEY` | Not tested yet |
|
||||
@@ -333,8 +334,9 @@ For example, `CF_API_EMAIL_FILE=/run/secrets/traefik_cf-api-email` could be used
|
||||
| [Stackpath](https://www.stackpath.com/) | `stackpath` | `STACKPATH_CLIENT_ID`, `STACKPATH_CLIENT_SECRET`, `STACKPATH_STACK_ID` | Not tested yet |
|
||||
| [TransIP](https://www.transip.nl/) | `transip` | `TRANSIP_ACCOUNT_NAME`, `TRANSIP_PRIVATE_KEY_PATH` | YES |
|
||||
| [VegaDNS](https://github.com/shupp/VegaDNS-API) | `vegadns` | `SECRET_VEGADNS_KEY`, `SECRET_VEGADNS_SECRET`, `VEGADNS_URL` | Not tested yet |
|
||||
| [Versio](https://www.versio.nl/domeinnamen) | `versio` | `VERSIO_USERNAME`, `VERSIO_PASSWORD` | YES |
|
||||
| [Vscale](https://vscale.io/) | `vscale` | `VSCALE_API_TOKEN` | YES |
|
||||
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | Not tested yet |
|
||||
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | YES |
|
||||
| [Zone.ee](https://www.zone.ee) | `zoneee` | `ZONEEE_API_USER`, `ZONEEE_API_KEY` | YES |
|
||||
|
||||
- (1): more information about the HTTP message format can be found [here](https://go-acme.github.io/lego/dns/httpreq/)
|
||||
|
@@ -35,7 +35,7 @@
|
||||
|
||||
For more customization, see [entry points](/configuration/entrypoints/) documentation and the examples below.
|
||||
|
||||
## Web UI
|
||||
## Dashboard (Web UI)
|
||||
|
||||

|
||||
|
||||
@@ -322,9 +322,12 @@ curl -s "http://localhost:8080/health" | jq .
|
||||
}
|
||||
```
|
||||
|
||||
## Metrics
|
||||
## Dashboard Statistics
|
||||
|
||||
You can enable Traefik to export internal metrics to different monitoring systems.
|
||||
You can control how the Traefik's internal metrics are shown in the Dashboard.
|
||||
|
||||
If you want to export internal metrics to different monitoring systems,
|
||||
please check the page [Metrics](./metrics.md).
|
||||
|
||||
```toml
|
||||
[api]
|
||||
|
@@ -37,6 +37,15 @@ stale = false
|
||||
#
|
||||
domain = "consul.localhost"
|
||||
|
||||
# Keep a Consul node only if all checks status are passing
|
||||
# If true, only the Consul nodes with checks status 'passing' will be kept.
|
||||
# if false, only the Consul nodes with checks status 'passing' or 'warning' will be kept.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
strictChecks = true
|
||||
|
||||
# Prefix for Consul catalog tags.
|
||||
#
|
||||
# Optional
|
||||
@@ -121,10 +130,10 @@ Additional settings can be defined using Consul Catalog tags.
|
||||
| `<prefix>.frontend.auth.basic=EXPR` | Sets basic authentication to this frontend in CSV format: `User:Hash,User:Hash` (DEPRECATED). |
|
||||
| `<prefix>.frontend.auth.basic.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
|
||||
| `<prefix>.frontend.auth.basic.users=EXPR` | Sets basic authentication to this frontend in CSV format: `User:Hash,User:Hash`. |
|
||||
| `<prefix>.frontend.auth.basic.usersfile=/path/.htpasswd` | Sets basic authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `<prefix>.frontend.auth.basic.usersFile=/path/.htpasswd` | Sets basic authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `<prefix>.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
|
||||
| `<prefix>.frontend.auth.digest.users=EXPR` | Sets digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. |
|
||||
| `<prefix>.frontend.auth.digest.usersfile=/path/.htdigest` | Sets digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `<prefix>.frontend.auth.digest.usersFile=/path/.htdigest` | Sets digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
|
||||
| `<prefix>.frontend.auth.forward.address=https://example.com` | Sets the URL of the authentication server. |
|
||||
| `<prefix>.frontend.auth.forward.authResponseHeaders=EXPR` | Sets the forward authentication authResponseHeaders in CSV format: `X-Auth-User,X-Auth-Header` |
|
||||
| `<prefix>.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. |
|
||||
|
@@ -73,6 +73,14 @@ See also [Kubernetes user guide](/user-guide/kubernetes).
|
||||
#
|
||||
# enablePassTLSCert = true
|
||||
|
||||
# Throttle how frequently we refresh our configuration from Ingresses when there
|
||||
# are frequent changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: 0 (no throttling)
|
||||
#
|
||||
# throttleDuration = 10s
|
||||
|
||||
# Override default configuration template.
|
||||
#
|
||||
# Optional
|
||||
@@ -210,10 +218,14 @@ infos:
|
||||
serialnumber: true
|
||||
```
|
||||
|
||||
If `pem` is set, it will add a `X-Forwarded-Tls-Client-Cert` header that contains the escaped pem as value.
|
||||
If `pem` is set, it will add a `X-Forwarded-Tls-Client-Cert` header that contains the escaped pem as value.
|
||||
If at least one flag of the `infos` part is set, it will add a `X-Forwarded-Tls-Client-Cert-Infos` header that contains an escaped string composed of the client certificate data selected by the infos flags.
|
||||
This infos part is composed like the following example (not escaped):
|
||||
```Subject="C=FR,ST=SomeState,L=Lyon,O=Cheese,CN=*.cheese.org",NB=1531900816,NA=1563436816,SAN=*.cheese.org,*.cheese.net,cheese.in,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2```
|
||||
```
|
||||
Subject="C=FR,ST=SomeState,L=Lyon,O=Cheese,CN=*.cheese.org",NB=1531900816,NA=1563436816,SAN=*.cheese.org,*.cheese.net,cheese.in,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2
|
||||
```
|
||||
|
||||
Note these options work only with certificates issued by CAs included in the applicable [EntryPoint ClientCA section](/configuration/entrypoints/#tls-mutual-authentication); certificates from other CAs are not parsed or passed through as-is.
|
||||
|
||||
<4> `traefik.ingress.kubernetes.io/rate-limit` example:
|
||||
|
||||
@@ -231,7 +243,7 @@ rateset:
|
||||
```
|
||||
|
||||
<5> `traefik.ingress.kubernetes.io/rule-type`
|
||||
Note: `ReplacePath` is deprecated in this annotation, use the `traefik.ingress.kubernetes.io/request-modifier` annotation instead. Default: `PathPrefix`.
|
||||
Note: `ReplacePath` is deprecated in this annotation, use the `traefik.ingress.kubernetes.io/request-modifier` annotation instead. Default: `PathPrefix`.
|
||||
|
||||
<6> `traefik.ingress.kubernetes.io/service-weights`:
|
||||
Service weights enable to split traffic across multiple backing services in a fine-grained manner.
|
||||
|
@@ -2,6 +2,11 @@
|
||||
|
||||
Traefik can be configured to use Rancher as a provider.
|
||||
|
||||
!!! important
|
||||
This provider is specific to Rancher 1.x.
|
||||
Rancher 2.x requires Kubernetes and does not have a metadata endpoint of its own for Traefik to query.
|
||||
As such, Rancher 2.x users should utilize the [Kubernetes provider](./kubernetes.md) directly.
|
||||
|
||||
## Global Configuration
|
||||
|
||||
```toml
|
||||
|
@@ -69,6 +69,8 @@ Here is an example of an extension setting Traefik labels:
|
||||
</StatelessServiceType>
|
||||
```
|
||||
|
||||
> **Note**: The `Label` tag and its `Key` attribute are case sensitive. That is, if you use `label` instead of `Label` or `key` instead of `Key`, they will be silently ignored.
|
||||
|
||||
#### Property Manager
|
||||
|
||||
Set Labels with the property manager API to overwrite and add labels, while your service is running.
|
||||
|
@@ -97,7 +97,7 @@ In compose file the entrypoint syntax is different. Notice how quotes are used:
|
||||
|
||||
```yaml
|
||||
traefik:
|
||||
image: traefik
|
||||
image: traefik:v1.7
|
||||
command:
|
||||
- --defaultentrypoints=powpow
|
||||
- "--entryPoints=Name:powpow Address::42 Compress:true"
|
||||
@@ -105,7 +105,7 @@ traefik:
|
||||
or
|
||||
```yaml
|
||||
traefik:
|
||||
image: traefik
|
||||
image: traefik:v1.7
|
||||
command: --defaultentrypoints=powpow --entryPoints='Name:powpow Address::42 Compress:true'
|
||||
```
|
||||
|
||||
@@ -235,8 +235,10 @@ If you need to add or remove TLS certificates while Traefik is started, Dynamic
|
||||
## TLS Mutual Authentication
|
||||
|
||||
TLS Mutual Authentication can be `optional` or not.
|
||||
If it's `optional`, Traefik will authorize connection with certificates not signed by a specified Certificate Authority (CA).
|
||||
Otherwise, Traefik will only accept clients that present a certificate signed by a specified Certificate Authority (CA).
|
||||
|
||||
* If `optional = true`, if a certificate is provided, verifies if it is signed by a specified Certificate Authority (CA). Otherwise proceeds without any certificate.
|
||||
* If `optional = false`, Traefik will only accept clients that present a certificate signed by a specified Certificate Authority (CA).
|
||||
|
||||
`ClientCAFiles` can be configured with multiple `CA:s` in the same file or use multiple files containing one or several `CA:s`.
|
||||
The `CA:s` has to be in PEM format.
|
||||
|
||||
|
@@ -39,7 +39,7 @@ logLevel = "INFO"
|
||||
|
||||
For more information about the CLI, see the documentation about [Traefik command](/basics/#traefik).
|
||||
|
||||
```shell
|
||||
```bash
|
||||
--logLevel="DEBUG"
|
||||
--traefikLog.filePath="/path/to/traefik.log"
|
||||
--traefikLog.format="json"
|
||||
@@ -54,7 +54,6 @@ For more information about the CLI, see the documentation about [Traefik command
|
||||
--accessLog.fields.headers.names="User-Agent=redact Authorization=drop Content-Type=keep"
|
||||
```
|
||||
|
||||
|
||||
## Traefik Logs
|
||||
|
||||
By default the Traefik log is written to stdout in text format.
|
||||
@@ -66,7 +65,7 @@ To write the logs into a log file specify the `filePath`:
|
||||
filePath = "/path/to/traefik.log"
|
||||
```
|
||||
|
||||
To write JSON format logs, specify `json` as the format:
|
||||
To switch to JSON format instead of standard format (`common`), specify `json` as the format:
|
||||
|
||||
```toml
|
||||
[traefikLog]
|
||||
@@ -74,7 +73,6 @@ To write JSON format logs, specify `json` as the format:
|
||||
format = "json"
|
||||
```
|
||||
|
||||
|
||||
Deprecated way (before 1.4):
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
@@ -105,13 +103,12 @@ To customize the log level:
|
||||
logLevel = "ERROR"
|
||||
```
|
||||
|
||||
|
||||
## Access Logs
|
||||
|
||||
Access logs are written when `[accessLog]` is defined.
|
||||
By default it will write to stdout and produce logs in the textual Common Log Format (CLF), extended with additional fields.
|
||||
Access logs are written when the entry `[accessLog]` is defined (or the command line flag `--accesslog`).
|
||||
By default it writes to stdout and produces logs in the textual [Common Log Format (CLF)](#clf-common-log-format), extended with additional fields.
|
||||
|
||||
To enable access logs using the default settings just add the `[accessLog]` entry:
|
||||
To enable access logs using the default settings, add the `[accessLog]` entry in your `traefik.toml` configuration file:
|
||||
|
||||
```toml
|
||||
[accessLog]
|
||||
@@ -124,12 +121,12 @@ To write the logs into a log file specify the `filePath`:
|
||||
filePath = "/path/to/access.log"
|
||||
```
|
||||
|
||||
To write JSON format logs, specify `json` as the format:
|
||||
To switch to JSON format instead of [Common Log Format (CLF)](#clf-common-log-format), specify `json` as the format:
|
||||
|
||||
```toml
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
format = "json"
|
||||
format = "json" # Default: "common"
|
||||
```
|
||||
|
||||
To write the logs in async, specify `bufferingSize` as the format (must be >0):
|
||||
@@ -152,7 +149,7 @@ To filter logs you can specify a set of filters which are logically "OR-connecte
|
||||
```toml
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
format = "json"
|
||||
format = "json" # Default: "common"
|
||||
|
||||
[accessLog.filters]
|
||||
|
||||
@@ -178,22 +175,42 @@ format = "json"
|
||||
minDuration = "10ms"
|
||||
```
|
||||
|
||||
To customize logs format:
|
||||
### CLF - Common Log Format
|
||||
|
||||
By default, Traefik use the CLF (`common`) as access log format.
|
||||
|
||||
```html
|
||||
<remote_IP_address> - <client_user_name_if_available> [<timestamp>] "<request_method> <request_path> <request_protocol>" <origin_server_HTTP_status> <origin_server_content_size> "<request_referrer>" "<request_user_agent>" <number_of_requests_received_since_Traefik_started> "<Traefik_frontend_name>" "<Traefik_backend_URL>" <request_duration_in_ms>ms
|
||||
```
|
||||
|
||||
### Customize Fields
|
||||
|
||||
You can customize the fields written in the access logs.
|
||||
The list of available fields is found below: [List of All Available Fields](#list-of-all-available-fields).
|
||||
|
||||
Each field has a "mode" which defines if it is written or not in the access log lines.
|
||||
The possible values for the mode are:
|
||||
|
||||
* `keep`: the field and its value are written on the access log line. This is the default behavior.
|
||||
* `drop`: the field is not written at all on the access log.
|
||||
|
||||
To customize the fields, you must:
|
||||
|
||||
* Switch to the JSON format (mandatory)
|
||||
* Define the "default mode" for all fields (default is `keep`)
|
||||
* OR Define the fields which does not follow the default mode
|
||||
|
||||
```toml
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
# Access Log Format
|
||||
#
|
||||
# Optional
|
||||
# Default: "common"
|
||||
#
|
||||
# Accepted values "common", "json"
|
||||
#
|
||||
format = "json"
|
||||
|
||||
[accessLog.filters]
|
||||
|
||||
# statusCodes keep only access logs with status codes in the specified range
|
||||
#
|
||||
# Optional
|
||||
# Default: []
|
||||
#
|
||||
statusCodes = ["200", "300-302"]
|
||||
|
||||
[accessLog.fields]
|
||||
|
||||
# defaultMode
|
||||
@@ -209,6 +226,43 @@ format = "json"
|
||||
[accessLog.fields.names]
|
||||
"ClientUsername" = "drop"
|
||||
# ...
|
||||
```
|
||||
|
||||
### Customize Headers
|
||||
|
||||
Access logs prints the headers of each request, as fields of the access log line.
|
||||
You can customize which and how the headers are printed, likewise the other fields (see ["Customize Fields" section](#customize-fields)).
|
||||
|
||||
Each header has a "mode" which defines how it is written in the access log lines.
|
||||
The possible values for the mode are:
|
||||
|
||||
* `keep`: the header and its value are written on the access log line. This is the default behavior.
|
||||
* `drop`: the header is not written at all on the access log.
|
||||
* `redacted`: the header is written, but its value is redacted to avoid leaking sensitive information.
|
||||
|
||||
To customize the headers, you must:
|
||||
|
||||
* Switch to the JSON format (mandatory)
|
||||
* Define the "default mode" for all headers (default is `keep`)
|
||||
* OR Define the headers which does not follow the default mode
|
||||
|
||||
!!! important
|
||||
The headers are written with the prefix `request_` in the access log.
|
||||
This prefix must not be included when specifying a header in the TOML configuration.
|
||||
|
||||
* Do: `"User-Agent" = "drop"`
|
||||
* Don't: `"redacted_User-Agent" = "drop"`
|
||||
|
||||
```toml
|
||||
[accessLog]
|
||||
# Access Log Format
|
||||
#
|
||||
# Optional
|
||||
# Default: "common"
|
||||
#
|
||||
# Accepted values "common", "json"
|
||||
#
|
||||
format = "json"
|
||||
|
||||
[accessLog.fields.headers]
|
||||
# defaultMode
|
||||
@@ -227,8 +281,7 @@ format = "json"
|
||||
# ...
|
||||
```
|
||||
|
||||
|
||||
### List of all available fields
|
||||
### List of All Available Fields
|
||||
|
||||
| Field | Description |
|
||||
|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
@@ -263,6 +316,8 @@ format = "json"
|
||||
| `Overhead` | The processing time overhead caused by Traefik. |
|
||||
| `RetryAttempts` | The amount of attempts the request was retried. |
|
||||
|
||||
### Depreciation Notice
|
||||
|
||||
Deprecated way (before 1.4):
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
@@ -276,15 +331,6 @@ Deprecated way (before 1.4):
|
||||
accessLogsFile = "log/access.log"
|
||||
```
|
||||
|
||||
### CLF - Common Log Format
|
||||
|
||||
By default, Traefik use the CLF (`common`) as access log format.
|
||||
|
||||
```html
|
||||
<remote_IP_address> - <client_user_name_if_available> [<timestamp>] "<request_method> <request_path> <request_protocol>" <origin_server_HTTP_status> <origin_server_content_size> "<request_referrer>" "<request_user_agent>" <number_of_requests_received_since_Traefik_started> "<Traefik_frontend_name>" "<Traefik_backend_URL>" <request_duration_in_ms>ms
|
||||
```
|
||||
|
||||
|
||||
## Log Rotation
|
||||
|
||||
Traefik will close and reopen its log files, assuming they're configured, on receipt of a USR1 signal.
|
||||
@@ -292,3 +338,34 @@ This allows the logs to be rotated and processed by an external program, such as
|
||||
|
||||
!!! note
|
||||
This does not work on Windows due to the lack of USR signals.
|
||||
|
||||
## Time Zones
|
||||
|
||||
The timestamp of each log line is in UTC time by default.
|
||||
|
||||
If you want to use local timezone, you need to ensure the 3 following elements:
|
||||
|
||||
1. Provide the timezone data into /usr/share/zoneinfo
|
||||
2. Set the environement variable TZ to the timezone to be used
|
||||
3. Specify the field StartLocal instead of StartUTC (works on default Common Log Format (CLF) as well as JSON)
|
||||
|
||||
Example using docker-compose:
|
||||
|
||||
```yml
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: containous/traefik:[latest stable version]
|
||||
ports:
|
||||
- "80:80"
|
||||
environment:
|
||||
- "TZ=US/Alaska"
|
||||
command:
|
||||
- --docker
|
||||
- --accesslog
|
||||
- --accesslog.fields.names="StartUTC=drop"
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||
- "/usr/share/zoneinfo:/usr/share/zoneinfo:ro"
|
||||
```
|
||||
|
@@ -6,7 +6,7 @@
|
||||
[](/)
|
||||
[](https://goreportcard.com/report/github.com/containous/traefik)
|
||||
[](https://github.com/containous/traefik/blob/master/LICENSE.md)
|
||||
[](https://slack.traefik.io)
|
||||
[](https://community.containo.us/)
|
||||
[](https://twitter.com/intent/follow?screen_name=traefik)
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ version: '3'
|
||||
|
||||
services:
|
||||
reverse-proxy:
|
||||
image: traefik # The official Traefik docker image
|
||||
image: traefik:v1.7 # The official Traefik docker image
|
||||
command: --api --docker # Enables the web UI and tells Traefik to listen to docker
|
||||
ports:
|
||||
- "80:80" # The HTTP port
|
||||
|
@@ -91,7 +91,7 @@ To watch docker events, add `--docker.watch`.
|
||||
version: "3"
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:1.5
|
||||
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||
command:
|
||||
- "--api"
|
||||
- "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
|
||||
@@ -156,7 +156,7 @@ The initializer in a docker-compose file will be:
|
||||
|
||||
```yaml
|
||||
traefik_init:
|
||||
image: traefik:1.5
|
||||
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||
command:
|
||||
- "storeconfig"
|
||||
- "--api"
|
||||
@@ -177,7 +177,7 @@ And now, the Traefik part will only have the Consul configuration.
|
||||
|
||||
```yaml
|
||||
traefik:
|
||||
image: traefik:1.5
|
||||
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||
depends_on:
|
||||
- traefik_init
|
||||
- consul
|
||||
@@ -200,7 +200,7 @@ The new configuration will be stored in Consul, and you need to restart the Trae
|
||||
version: "3.4"
|
||||
services:
|
||||
traefik_init:
|
||||
image: traefik:1.5
|
||||
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||
command:
|
||||
- "storeconfig"
|
||||
- "--api"
|
||||
@@ -229,7 +229,7 @@ services:
|
||||
depends_on:
|
||||
- consul
|
||||
traefik:
|
||||
image: traefik:1.5
|
||||
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||
depends_on:
|
||||
- traefik_init
|
||||
- consul
|
||||
|
@@ -50,7 +50,7 @@ version: '2'
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:1.5.4
|
||||
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||
restart: always
|
||||
ports:
|
||||
- 80:80
|
||||
@@ -108,6 +108,28 @@ onHostRule = true
|
||||
entryPoint = "http"
|
||||
```
|
||||
|
||||
Alternatively, the `TOML` file above can also be translated into command line switches.
|
||||
This is the `command` value of the `traefik` service in the `docker-compose.yml` manifest:
|
||||
|
||||
```yaml
|
||||
command:
|
||||
- --debug=false
|
||||
- --logLevel=ERROR
|
||||
- --defaultentrypoints=https,http
|
||||
- --entryPoints=Name:http Address::80 Redirect.EntryPoint:https
|
||||
- --entryPoints=Name:https Address::443 TLS
|
||||
- --retry
|
||||
- --docker.endpoint=unix:///var/run/docker.sock
|
||||
- --docker.domain=my-awesome-app.org
|
||||
- --docker.watch=true
|
||||
- --docker.exposedbydefault=false
|
||||
- --acme.email=your-email-here@my-awesome-app.org
|
||||
- --acme.storage=acme.json
|
||||
- --acme.entryPoint=https
|
||||
- --acme.onHostRule=true
|
||||
- --acme.httpchallenge.entrypoint=http
|
||||
```
|
||||
|
||||
This is the minimum configuration required to do the following:
|
||||
|
||||
- Log `ERROR`-level messages (or more severe) to the console, but silence `DEBUG`-level messages
|
||||
|
@@ -336,37 +336,37 @@ Pay attention to the **labels** section:
|
||||
|
||||
```
|
||||
home:
|
||||
image: abiosoft/caddy:0.10.14
|
||||
networks:
|
||||
- ntw_front
|
||||
volumes:
|
||||
- ./www/home/srv/:/srv/
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 2
|
||||
#placement:
|
||||
# constraints: [node.role==manager]
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
max_attempts: 5
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.20'
|
||||
memory: 9M
|
||||
reservations:
|
||||
cpus: '0.05'
|
||||
memory: 9M
|
||||
labels:
|
||||
- "traefik.frontend.rule=PathPrefixStrip:/"
|
||||
- "traefik.backend=home"
|
||||
- "traefik.port=2015"
|
||||
- "traefik.weight=10"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.passHostHeader=true"
|
||||
- "traefik.docker.network=ntw_front"
|
||||
- "traefik.frontend.entryPoints=http"
|
||||
- "traefik.backend.loadbalancer.swarm=true"
|
||||
- "traefik.backend.loadbalancer.method=drr"
|
||||
image: abiosoft/caddy:0.10.14
|
||||
networks:
|
||||
- ntw_front
|
||||
volumes:
|
||||
- ./www/home/srv/:/srv/
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 2
|
||||
#placement:
|
||||
# constraints: [node.role==manager]
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
max_attempts: 5
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.20'
|
||||
memory: 9M
|
||||
reservations:
|
||||
cpus: '0.05'
|
||||
memory: 9M
|
||||
labels:
|
||||
- "traefik.frontend.rule=PathPrefixStrip:/"
|
||||
- "traefik.backend=home"
|
||||
- "traefik.port=2015"
|
||||
- "traefik.weight=10"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.passHostHeader=true"
|
||||
- "traefik.docker.network=ntw_front"
|
||||
- "traefik.frontend.entryPoints=http"
|
||||
- "traefik.backend.loadbalancer.swarm=true"
|
||||
- "traefik.backend.loadbalancer.method=drr"
|
||||
```
|
||||
|
||||
Something more tricky using `regex`.
|
||||
@@ -377,39 +377,39 @@ The double sign `$$` are variables managed by the docker compose file ([document
|
||||
|
||||
```
|
||||
portainer:
|
||||
image: portainer/portainer:1.16.5
|
||||
networks:
|
||||
- ntw_front
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints: [node.role==manager]
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
max_attempts: 5
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.33'
|
||||
memory: 20M
|
||||
reservations:
|
||||
cpus: '0.05'
|
||||
memory: 10M
|
||||
labels:
|
||||
- "traefik.frontend.rule=PathPrefixStrip:/portainer"
|
||||
- "traefik.backend=portainer"
|
||||
- "traefik.port=9000"
|
||||
- "traefik.weight=10"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.passHostHeader=true"
|
||||
- "traefik.docker.network=ntw_front"
|
||||
- "traefik.frontend.entryPoints=http"
|
||||
- "traefik.backend.loadbalancer.swarm=true"
|
||||
- "traefik.backend.loadbalancer.method=drr"
|
||||
# https://github.com/containous/traefik/issues/563#issuecomment-421360934
|
||||
- "traefik.frontend.redirect.regex=^(.*)/portainer$$"
|
||||
- "traefik.frontend.redirect.replacement=$$1/portainer/"
|
||||
- "traefik.frontend.rule=PathPrefix:/portainer;ReplacePathRegex: ^/portainer/(.*) /$$1"
|
||||
image: portainer/portainer:1.16.5
|
||||
networks:
|
||||
- ntw_front
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints: [node.role==manager]
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
max_attempts: 5
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.33'
|
||||
memory: 20M
|
||||
reservations:
|
||||
cpus: '0.05'
|
||||
memory: 10M
|
||||
labels:
|
||||
- "traefik.frontend.rule=PathPrefixStrip:/portainer"
|
||||
- "traefik.backend=portainer"
|
||||
- "traefik.port=9000"
|
||||
- "traefik.weight=10"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.passHostHeader=true"
|
||||
- "traefik.docker.network=ntw_front"
|
||||
- "traefik.frontend.entryPoints=http"
|
||||
- "traefik.backend.loadbalancer.swarm=true"
|
||||
- "traefik.backend.loadbalancer.method=drr"
|
||||
# https://github.com/containous/traefik/issues/563#issuecomment-421360934
|
||||
- "traefik.frontend.redirect.regex=^(.*)/portainer$$"
|
||||
- "traefik.frontend.redirect.replacement=$$1/portainer/"
|
||||
- "traefik.frontend.rule=PathPrefix:/portainer;ReplacePathRegex: ^/portainer/(.*) /$$1"
|
||||
```
|
||||
|
@@ -118,7 +118,7 @@ spec:
|
||||
serviceAccountName: traefik-ingress-controller
|
||||
terminationGracePeriodSeconds: 60
|
||||
containers:
|
||||
- image: traefik
|
||||
- image: traefik:v1.7
|
||||
name: traefik-ingress-lb
|
||||
ports:
|
||||
- name: http
|
||||
@@ -180,7 +180,7 @@ spec:
|
||||
serviceAccountName: traefik-ingress-controller
|
||||
terminationGracePeriodSeconds: 60
|
||||
containers:
|
||||
- image: traefik
|
||||
- image: traefik:v1.7
|
||||
name: traefik-ingress-lb
|
||||
ports:
|
||||
- name: http
|
||||
|
@@ -139,7 +139,7 @@ Here is the [docker-compose file](https://docs.docker.com/compose/compose-file/)
|
||||
|
||||
```yaml
|
||||
traefik:
|
||||
image: traefik
|
||||
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||
command: --consul --consul.endpoint=127.0.0.1:8500
|
||||
ports:
|
||||
- "80:80"
|
||||
|
@@ -85,7 +85,7 @@ docker-machine ssh manager "docker service create \
|
||||
--publish 80:80 --publish 8080:8080 \
|
||||
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||
--network traefik-net \
|
||||
traefik \
|
||||
traefik:<stable version from https://hub.docker.com/_/traefik> \
|
||||
--docker \
|
||||
--docker.swarmMode \
|
||||
--docker.domain=traefik \
|
||||
|
@@ -81,7 +81,7 @@ docker $(docker-machine config mhs-demo0) run \
|
||||
-p 80:80 -p 8080:8080 \
|
||||
--net=my-net \
|
||||
-v /var/lib/boot2docker/:/ssl \
|
||||
traefik \
|
||||
traefik:<stable version from https://hub.docker.com/_/traefik> \
|
||||
-l DEBUG \
|
||||
-c /dev/null \
|
||||
--docker \
|
||||
|
@@ -1,5 +1,5 @@
|
||||
traefik:
|
||||
image: traefik
|
||||
image: traefik:v1.7
|
||||
command: --api --rancher --rancher.domain=rancher.localhost --rancher.endpoint=http://example.com --rancher.accesskey=XXXXXXX --rancher.secretkey=YYYYYY --logLevel=DEBUG
|
||||
ports:
|
||||
- "80:80"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
traefik:
|
||||
image: traefik
|
||||
image: traefik:v1.7
|
||||
command: -c /dev/null --api --docker --docker.domain=docker.localhost --logLevel=DEBUG
|
||||
ports:
|
||||
- "80:80"
|
||||
|
@@ -26,7 +26,7 @@ spec:
|
||||
serviceAccountName: traefik-ingress-controller
|
||||
terminationGracePeriodSeconds: 60
|
||||
containers:
|
||||
- image: traefik
|
||||
- image: traefik:v1.7
|
||||
name: traefik-ingress-lb
|
||||
ports:
|
||||
- name: http
|
||||
|
@@ -22,7 +22,7 @@ spec:
|
||||
serviceAccountName: traefik-ingress-controller
|
||||
terminationGracePeriodSeconds: 60
|
||||
containers:
|
||||
- image: traefik
|
||||
- image: traefik:v1.7
|
||||
name: traefik-ingress-lb
|
||||
ports:
|
||||
- name: http
|
||||
|
@@ -13,7 +13,7 @@ version: '3'
|
||||
|
||||
services:
|
||||
reverse-proxy:
|
||||
image: traefik # The official Traefik docker image
|
||||
image: traefik:v1.7 # The official Traefik docker image
|
||||
command: --api --docker # Enables the web UI and tells Traefik to listen to docker
|
||||
ports:
|
||||
- "80:80" # The HTTP port
|
||||
@@ -101,7 +101,7 @@ IP: 172.27.0.4
|
||||
|
||||
### 4 — Enjoy Traefik's Magic
|
||||
|
||||
Now that you have a basic understanding of how Traefik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](https://docs.traefik.io/) and let Traefik work for you!
|
||||
Whatever your infrastructure is, there is probably [an available Traefik backend](https://docs.traefik.io/#supported-backends) that will do the job.
|
||||
Now that you have a basic understanding of how Traefik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](https://docs.traefik.io/v1.7/) and let Traefik work for you!
|
||||
Whatever your infrastructure is, there is probably [an available Traefik backend](https://docs.traefik.io/v1.7/#supported-backends) that will do the job.
|
||||
|
||||
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Traefik's let's encrypt integration](https://docs.traefik.io/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](https://docs.traefik.io/user-guide/docker-and-lets-encrypt/).
|
||||
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Traefik's let's encrypt integration](https://docs.traefik.io/v1.7/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](https://docs.traefik.io/v1.7/user-guide/docker-and-lets-encrypt/).
|
||||
|
@@ -3,7 +3,7 @@ version: '3'
|
||||
services:
|
||||
# The reverse proxy service (Traefik)
|
||||
reverse-proxy:
|
||||
image: traefik # The official Traefik docker image
|
||||
image: traefik:v1.7 # The official Traefik docker image
|
||||
command: --api --docker # Enables the web UI and tells Traefik to listen to docker
|
||||
ports:
|
||||
- "80:80" # The HTTP port
|
||||
|
@@ -12,7 +12,7 @@ RUN yarn install
|
||||
RUN npm run build
|
||||
|
||||
# BUILD
|
||||
FROM golang:1.11-alpine as gobuild
|
||||
FROM golang:1.12-alpine as gobuild
|
||||
|
||||
RUN apk --update upgrade \
|
||||
&& apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar ca-certificates tzdata \
|
||||
|
@@ -49,11 +49,16 @@ func (opt Options) String() string {
|
||||
return fmt.Sprintf("[Hostname: %s Headers: %v Path: %s Port: %d Interval: %s]", opt.Hostname, opt.Headers, opt.Path, opt.Port, opt.Interval)
|
||||
}
|
||||
|
||||
type backendURL struct {
|
||||
url *url.URL
|
||||
weight int
|
||||
}
|
||||
|
||||
// BackendConfig HealthCheck configuration for a backend
|
||||
type BackendConfig struct {
|
||||
Options
|
||||
name string
|
||||
disabledURLs []*url.URL
|
||||
disabledURLs []backendURL
|
||||
requestTimeout time.Duration
|
||||
}
|
||||
|
||||
@@ -129,18 +134,18 @@ func (hc *HealthCheck) execute(ctx context.Context, backend *BackendConfig) {
|
||||
|
||||
func (hc *HealthCheck) checkBackend(backend *BackendConfig) {
|
||||
enabledURLs := backend.LB.Servers()
|
||||
var newDisabledURLs []*url.URL
|
||||
for _, url := range backend.disabledURLs {
|
||||
var newDisabledURLs []backendURL
|
||||
for _, backendurl := range backend.disabledURLs {
|
||||
serverUpMetricValue := float64(0)
|
||||
if err := checkHealth(url, backend); err == nil {
|
||||
log.Warnf("Health check up: Returning to server list. Backend: %q URL: %q", backend.name, url.String())
|
||||
backend.LB.UpsertServer(url, roundrobin.Weight(1))
|
||||
if err := checkHealth(backendurl.url, backend); err == nil {
|
||||
log.Warnf("Health check up: Returning to server list. Backend: %q URL: %q Weight: %d", backend.name, backendurl.url.String(), backendurl.weight)
|
||||
backend.LB.UpsertServer(backendurl.url, roundrobin.Weight(backendurl.weight))
|
||||
serverUpMetricValue = 1
|
||||
} else {
|
||||
log.Warnf("Health check still failing. Backend: %q URL: %q Reason: %s", backend.name, url.String(), err)
|
||||
newDisabledURLs = append(newDisabledURLs, url)
|
||||
log.Warnf("Health check still failing. Backend: %q URL: %q Reason: %s", backend.name, backendurl.url.String(), err)
|
||||
newDisabledURLs = append(newDisabledURLs, backendurl)
|
||||
}
|
||||
labelValues := []string{"backend", backend.name, "url", url.String()}
|
||||
labelValues := []string{"backend", backend.name, "url", backendurl.url.String()}
|
||||
hc.metrics.BackendServerUpGauge().With(labelValues...).Set(serverUpMetricValue)
|
||||
}
|
||||
backend.disabledURLs = newDisabledURLs
|
||||
@@ -148,9 +153,18 @@ func (hc *HealthCheck) checkBackend(backend *BackendConfig) {
|
||||
for _, url := range enabledURLs {
|
||||
serverUpMetricValue := float64(1)
|
||||
if err := checkHealth(url, backend); err != nil {
|
||||
log.Warnf("Health check failed: Remove from server list. Backend: %q URL: %q Reason: %s", backend.name, url.String(), err)
|
||||
weight := 1
|
||||
rr, ok := backend.LB.(*roundrobin.RoundRobin)
|
||||
if ok {
|
||||
var gotWeight bool
|
||||
weight, gotWeight = rr.ServerWeight(url)
|
||||
if !gotWeight {
|
||||
weight = 1
|
||||
}
|
||||
}
|
||||
log.Warnf("Health check failed: Remove from server list. Backend: %q URL: %q Weight: %d Reason: %s", backend.name, url.String(), weight, err)
|
||||
backend.LB.RemoveServer(url)
|
||||
backend.disabledURLs = append(backend.disabledURLs, url)
|
||||
backend.disabledURLs = append(backend.disabledURLs, backendURL{url, weight})
|
||||
serverUpMetricValue = 0
|
||||
}
|
||||
labelValues := []string{"backend", backend.name, "url", url.String()}
|
||||
|
@@ -112,7 +112,7 @@ func TestSetBackendsConfiguration(t *testing.T) {
|
||||
if test.startHealthy {
|
||||
lb.servers = append(lb.servers, serverURL)
|
||||
} else {
|
||||
backend.disabledURLs = append(backend.disabledURLs, serverURL)
|
||||
backend.disabledURLs = append(backend.disabledURLs, backendURL{serverURL, 1})
|
||||
}
|
||||
|
||||
collectingMetrics := testhelpers.NewCollectingHealthCheckMetrics()
|
||||
|
@@ -3,6 +3,7 @@ package accesslog
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -32,6 +33,19 @@ const (
|
||||
JSONFormat = "json"
|
||||
)
|
||||
|
||||
type noopCloser struct {
|
||||
*os.File
|
||||
}
|
||||
|
||||
func (n noopCloser) Write(p []byte) (int, error) {
|
||||
return n.File.Write(p)
|
||||
}
|
||||
|
||||
func (n noopCloser) Close() error {
|
||||
// noop
|
||||
return nil
|
||||
}
|
||||
|
||||
type logHandlerParams struct {
|
||||
logDataTable *LogData
|
||||
crr *captureRequestReader
|
||||
@@ -42,7 +56,7 @@ type logHandlerParams struct {
|
||||
type LogHandler struct {
|
||||
config *types.AccessLog
|
||||
logger *logrus.Logger
|
||||
file *os.File
|
||||
file io.WriteCloser
|
||||
mu sync.Mutex
|
||||
httpCodeRanges types.HTTPCodeRanges
|
||||
logHandlerChan chan logHandlerParams
|
||||
@@ -51,7 +65,7 @@ type LogHandler struct {
|
||||
|
||||
// NewLogHandler creates a new LogHandler
|
||||
func NewLogHandler(config *types.AccessLog) (*LogHandler, error) {
|
||||
file := os.Stdout
|
||||
var file io.WriteCloser = noopCloser{os.Stdout}
|
||||
if len(config.FilePath) > 0 {
|
||||
f, err := openAccessLogFile(config.FilePath)
|
||||
if err != nil {
|
||||
@@ -205,14 +219,15 @@ func (l *LogHandler) Close() error {
|
||||
// Rotate closes and reopens the log file to allow for rotation
|
||||
// by an external source.
|
||||
func (l *LogHandler) Rotate() error {
|
||||
var err error
|
||||
|
||||
if l.file != nil {
|
||||
defer func(f *os.File) {
|
||||
f.Close()
|
||||
}(l.file)
|
||||
if l.config.FilePath == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if l.file != nil {
|
||||
defer func(f io.Closer) { _ = f.Close() }(l.file)
|
||||
}
|
||||
|
||||
var err error
|
||||
l.file, err = os.OpenFile(l.config.FilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0664)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@@ -24,6 +24,8 @@ func (f *CommonLogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
var timestamp = defaultValue
|
||||
if v, ok := entry.Data[StartUTC]; ok {
|
||||
timestamp = v.(time.Time).Format(commonLogTimeFormat)
|
||||
} else if v, ok := entry.Data[StartLocal]; ok {
|
||||
timestamp = v.(time.Time).Local().Format(commonLogTimeFormat)
|
||||
}
|
||||
|
||||
var elapsedMillis int64
|
||||
|
@@ -2,6 +2,7 @@ package accesslog
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -57,10 +58,34 @@ func TestCommonLogFormatter_Format(t *testing.T) {
|
||||
BackendURL: "http://10.0.0.2/toto",
|
||||
},
|
||||
expectedLog: `10.0.0.1 - Client [10/Nov/2009:23:00:00 +0000] "GET /foo http" 123 132 "referer" "agent" - "foo" "http://10.0.0.2/toto" 123000ms
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "all data with local time",
|
||||
data: map[string]interface{}{
|
||||
StartLocal: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
|
||||
Duration: 123 * time.Second,
|
||||
ClientHost: "10.0.0.1",
|
||||
ClientUsername: "Client",
|
||||
RequestMethod: http.MethodGet,
|
||||
RequestPath: "/foo",
|
||||
RequestProtocol: "http",
|
||||
OriginStatus: 123,
|
||||
OriginContentSize: 132,
|
||||
RequestRefererHeader: "referer",
|
||||
RequestUserAgentHeader: "agent",
|
||||
RequestCount: nil,
|
||||
FrontendName: "foo",
|
||||
BackendURL: "http://10.0.0.2/toto",
|
||||
},
|
||||
expectedLog: `10.0.0.1 - Client [10/Nov/2009:14:00:00 -0900] "GET /foo http" 123 132 "referer" "agent" - "foo" "http://10.0.0.2/toto" 123000ms
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
// Set timezone to Alaska to have a constant behavior
|
||||
os.Setenv("TZ", "US/Alaska")
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
@@ -19,7 +19,10 @@ import (
|
||||
)
|
||||
|
||||
// Compile time validation that the response recorder implements http interfaces correctly.
|
||||
var _ middlewares.Stateful = &responseRecorderWithCloseNotify{}
|
||||
var (
|
||||
_ middlewares.Stateful = &responseRecorderWithCloseNotify{}
|
||||
_ middlewares.Stateful = &codeCatcherWithCloseNotify{}
|
||||
)
|
||||
|
||||
// Handler is a middleware that provides the custom error pages
|
||||
type Handler struct {
|
||||
@@ -74,25 +77,29 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.
|
||||
return
|
||||
}
|
||||
|
||||
recorder := newResponseRecorder(w)
|
||||
next.ServeHTTP(recorder, req)
|
||||
catcher := newCodeCatcher(w, h.httpCodeRanges)
|
||||
next.ServeHTTP(catcher, req)
|
||||
if !catcher.isFilteredCode() {
|
||||
return
|
||||
}
|
||||
|
||||
// check the recorder code against the configured http status code ranges
|
||||
code := catcher.getCode()
|
||||
for _, block := range h.httpCodeRanges {
|
||||
if recorder.GetCode() >= block[0] && recorder.GetCode() <= block[1] {
|
||||
log.Errorf("Caught HTTP Status Code %d, returning error page", recorder.GetCode())
|
||||
if code >= block[0] && code <= block[1] {
|
||||
log.Errorf("Caught HTTP Status Code %d, returning error page", code)
|
||||
|
||||
var query string
|
||||
if len(h.backendQuery) > 0 {
|
||||
query = "/" + strings.TrimPrefix(h.backendQuery, "/")
|
||||
query = strings.Replace(query, "{status}", strconv.Itoa(recorder.GetCode()), -1)
|
||||
query = strings.Replace(query, "{status}", strconv.Itoa(code), -1)
|
||||
}
|
||||
|
||||
pageReq, err := newRequest(h.backendURL + query)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
w.WriteHeader(recorder.GetCode())
|
||||
fmt.Fprint(w, http.StatusText(recorder.GetCode()))
|
||||
w.WriteHeader(code)
|
||||
fmt.Fprint(w, http.StatusText(code))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -102,16 +109,11 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.
|
||||
h.backendHandler.ServeHTTP(recorderErrorPage, pageReq.WithContext(req.Context()))
|
||||
|
||||
utils.CopyHeaders(w.Header(), recorderErrorPage.Header())
|
||||
w.WriteHeader(recorder.GetCode())
|
||||
w.WriteHeader(code)
|
||||
w.Write(recorderErrorPage.GetBody().Bytes())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// did not catch a configured status code so proceed with the request
|
||||
utils.CopyHeaders(w.Header(), recorder.Header())
|
||||
w.WriteHeader(recorder.GetCode())
|
||||
w.Write(recorder.GetBody().Bytes())
|
||||
}
|
||||
|
||||
func newRequest(baseURL string) (*http.Request, error) {
|
||||
@@ -129,6 +131,133 @@ func newRequest(baseURL string) (*http.Request, error) {
|
||||
return req, nil
|
||||
}
|
||||
|
||||
type responseInterceptor interface {
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
getCode() int
|
||||
isFilteredCode() bool
|
||||
}
|
||||
|
||||
// codeCatcher is a response writer that detects as soon as possible whether the
|
||||
// response is a code within the ranges of codes it watches for. If it is, it
|
||||
// simply drops the data from the response. Otherwise, it forwards it directly to
|
||||
// the original client (its responseWriter) without any buffering.
|
||||
type codeCatcher struct {
|
||||
headerMap http.Header
|
||||
code int
|
||||
httpCodeRanges types.HTTPCodeRanges
|
||||
firstWrite bool
|
||||
caughtFilteredCode bool
|
||||
responseWriter http.ResponseWriter
|
||||
headersSent bool
|
||||
err error
|
||||
}
|
||||
|
||||
type codeCatcherWithCloseNotify struct {
|
||||
*codeCatcher
|
||||
}
|
||||
|
||||
// CloseNotify returns a channel that receives at most a
|
||||
// single value (true) when the client connection has gone away.
|
||||
func (cc *codeCatcherWithCloseNotify) CloseNotify() <-chan bool {
|
||||
return cc.responseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
func newCodeCatcher(rw http.ResponseWriter, httpCodeRanges types.HTTPCodeRanges) responseInterceptor {
|
||||
catcher := &codeCatcher{
|
||||
headerMap: make(http.Header),
|
||||
code: http.StatusOK, // If backend does not call WriteHeader on us, we consider it's a 200.
|
||||
responseWriter: rw,
|
||||
httpCodeRanges: httpCodeRanges,
|
||||
firstWrite: true,
|
||||
}
|
||||
if _, ok := rw.(http.CloseNotifier); ok {
|
||||
return &codeCatcherWithCloseNotify{catcher}
|
||||
}
|
||||
return catcher
|
||||
}
|
||||
|
||||
func (cc *codeCatcher) Header() http.Header {
|
||||
if cc.headerMap == nil {
|
||||
cc.headerMap = make(http.Header)
|
||||
}
|
||||
|
||||
return cc.headerMap
|
||||
}
|
||||
|
||||
func (cc *codeCatcher) getCode() int {
|
||||
return cc.code
|
||||
}
|
||||
|
||||
// isFilteredCode returns whether the codeCatcher received a response code among the ones it is watching,
|
||||
// and for which the response should be deferred to the error handler.
|
||||
func (cc *codeCatcher) isFilteredCode() bool {
|
||||
return cc.caughtFilteredCode
|
||||
}
|
||||
|
||||
func (cc *codeCatcher) Write(buf []byte) (int, error) {
|
||||
if !cc.firstWrite {
|
||||
if cc.caughtFilteredCode {
|
||||
// We don't care about the contents of the response,
|
||||
// since we want to serve the ones from the error page,
|
||||
// so we just drop them.
|
||||
return len(buf), nil
|
||||
}
|
||||
return cc.responseWriter.Write(buf)
|
||||
}
|
||||
cc.firstWrite = false
|
||||
|
||||
// If WriteHeader was already called from the caller, this is a NOOP.
|
||||
// Otherwise, cc.code is actually a 200 here.
|
||||
cc.WriteHeader(cc.code)
|
||||
|
||||
if cc.caughtFilteredCode {
|
||||
return len(buf), nil
|
||||
}
|
||||
return cc.responseWriter.Write(buf)
|
||||
}
|
||||
|
||||
func (cc *codeCatcher) WriteHeader(code int) {
|
||||
if cc.headersSent || cc.caughtFilteredCode {
|
||||
return
|
||||
}
|
||||
|
||||
cc.code = code
|
||||
for _, block := range cc.httpCodeRanges {
|
||||
if cc.code >= block[0] && cc.code <= block[1] {
|
||||
cc.caughtFilteredCode = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// it will be up to the other response recorder to send the headers,
|
||||
// so it is out of our hands now.
|
||||
if cc.caughtFilteredCode {
|
||||
return
|
||||
}
|
||||
utils.CopyHeaders(cc.responseWriter.Header(), cc.Header())
|
||||
cc.responseWriter.WriteHeader(cc.code)
|
||||
cc.headersSent = true
|
||||
}
|
||||
|
||||
// Hijack hijacks the connection
|
||||
func (cc *codeCatcher) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if hj, ok := cc.responseWriter.(http.Hijacker); ok {
|
||||
return hj.Hijack()
|
||||
}
|
||||
return nil, nil, fmt.Errorf("%T is not a http.Hijacker", cc.responseWriter)
|
||||
}
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
func (cc *codeCatcher) Flush() {
|
||||
// If WriteHeader was already called from the caller, this is a NOOP.
|
||||
// Otherwise, cc.code is actually a 200 here.
|
||||
cc.WriteHeader(cc.code)
|
||||
|
||||
if flusher, ok := cc.responseWriter.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
type responseRecorder interface {
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
|
@@ -34,6 +34,30 @@ func TestHandler(t *testing.T) {
|
||||
assert.Contains(t, recorder.Body.String(), http.StatusText(http.StatusOK))
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "no error, but not a 200",
|
||||
errorPage: &types.ErrorPage{Backend: "error", Query: "/test", Status: []string{"500-501", "503-599"}},
|
||||
backendCode: http.StatusPartialContent,
|
||||
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "My error page.")
|
||||
}),
|
||||
validate: func(t *testing.T, recorder *httptest.ResponseRecorder) {
|
||||
assert.Equal(t, http.StatusPartialContent, recorder.Code, "HTTP status")
|
||||
assert.Contains(t, recorder.Body.String(), http.StatusText(http.StatusPartialContent))
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "a 304, so no Write called",
|
||||
errorPage: &types.ErrorPage{Backend: "error", Query: "/test", Status: []string{"500-501", "503-599"}},
|
||||
backendCode: http.StatusNotModified,
|
||||
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "whatever, should not be called")
|
||||
}),
|
||||
validate: func(t *testing.T, recorder *httptest.ResponseRecorder) {
|
||||
assert.Equal(t, http.StatusNotModified, recorder.Code, "HTTP status")
|
||||
assert.Contains(t, recorder.Body.String(), "")
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "in the range",
|
||||
errorPage: &types.ErrorPage{Backend: "error", Query: "/test", Status: []string{"500-501", "503-599"}},
|
||||
@@ -108,6 +132,9 @@ func TestHandler(t *testing.T) {
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(test.backendCode)
|
||||
if test.backendCode == http.StatusNotModified {
|
||||
return
|
||||
}
|
||||
fmt.Fprintln(w, http.StatusText(test.backendCode))
|
||||
})
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/http/httptrace"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -35,7 +36,7 @@ func TestRetry(t *testing.T) {
|
||||
desc: "no retry when max request attempts is one",
|
||||
maxRequestAttempts: 1,
|
||||
wantRetryAttempts: 0,
|
||||
wantResponseStatus: http.StatusInternalServerError,
|
||||
wantResponseStatus: http.StatusBadGateway,
|
||||
amountFaultyEndpoints: 1,
|
||||
},
|
||||
{
|
||||
@@ -56,7 +57,7 @@ func TestRetry(t *testing.T) {
|
||||
desc: "max attempts exhausted delivers the 5xx response",
|
||||
maxRequestAttempts: 3,
|
||||
wantRetryAttempts: 2,
|
||||
wantResponseStatus: http.StatusInternalServerError,
|
||||
wantResponseStatus: http.StatusBadGateway,
|
||||
amountFaultyEndpoints: 3,
|
||||
},
|
||||
}
|
||||
@@ -82,17 +83,18 @@ func TestRetry(t *testing.T) {
|
||||
t.Fatalf("Error creating load balancer: %s", err)
|
||||
}
|
||||
|
||||
basePort := 33444
|
||||
// out of range port
|
||||
basePort := 1133444
|
||||
for i := 0; i < test.amountFaultyEndpoints; i++ {
|
||||
// 192.0.2.0 is a non-routable IP for testing purposes.
|
||||
// See: https://stackoverflow.com/questions/528538/non-routable-ip-address/18436928#18436928
|
||||
// We only use the port specification here because the URL is used as identifier
|
||||
// in the load balancer and using the exact same URL would not add a new server.
|
||||
loadBalancer.UpsertServer(testhelpers.MustParseURL("http://192.0.2.0:" + string(basePort+i)))
|
||||
_ = loadBalancer.UpsertServer(testhelpers.MustParseURL("http://192.0.2.0:" + strconv.Itoa(basePort+i)))
|
||||
}
|
||||
|
||||
// add the functioning server to the end of the load balancer list
|
||||
loadBalancer.UpsertServer(testhelpers.MustParseURL(backendServer.URL))
|
||||
_ = loadBalancer.UpsertServer(testhelpers.MustParseURL(backendServer.URL))
|
||||
|
||||
retryListener := &countingRetryListener{}
|
||||
retry := NewRetry(test.maxRequestAttempts, loadBalancer, retryListener)
|
||||
@@ -154,17 +156,18 @@ func TestRetryWebsocket(t *testing.T) {
|
||||
t.Fatalf("Error creating load balancer: %s", err)
|
||||
}
|
||||
|
||||
basePort := 33444
|
||||
// out of range port
|
||||
basePort := 1133444
|
||||
for i := 0; i < test.amountFaultyEndpoints; i++ {
|
||||
// 192.0.2.0 is a non-routable IP for testing purposes.
|
||||
// See: https://stackoverflow.com/questions/528538/non-routable-ip-address/18436928#18436928
|
||||
// We only use the port specification here because the URL is used as identifier
|
||||
// in the load balancer and using the exact same URL would not add a new server.
|
||||
loadBalancer.UpsertServer(testhelpers.MustParseURL("http://192.0.2.0:" + string(basePort+i)))
|
||||
_ = loadBalancer.UpsertServer(testhelpers.MustParseURL("http://192.0.2.0:" + strconv.Itoa(basePort+i)))
|
||||
}
|
||||
|
||||
// add the functioning server to the end of the load balancer list
|
||||
loadBalancer.UpsertServer(testhelpers.MustParseURL(backendServer.URL))
|
||||
_ = loadBalancer.UpsertServer(testhelpers.MustParseURL(backendServer.URL))
|
||||
|
||||
retryListener := &countingRetryListener{}
|
||||
retry := NewRetry(test.maxRequestAttempts, loadBalancer, retryListener)
|
||||
|
@@ -44,7 +44,7 @@ type DistinguishedNameOptions struct {
|
||||
|
||||
// TLSClientHeaders is a middleware that helps setup a few tls info features.
|
||||
type TLSClientHeaders struct {
|
||||
Infos *TLSClientCertificateInfos // pass selected informations from the client certificate
|
||||
Infos *TLSClientCertificateInfos // pass selected information from the client certificate
|
||||
PEM bool // pass the sanitized pem to the backend in a specific header
|
||||
}
|
||||
|
||||
@@ -79,23 +79,14 @@ func newTLSClientInfos(infos *types.TLSClientCertificateInfos) *TLSClientCertifi
|
||||
}
|
||||
|
||||
// NewTLSClientHeaders constructs a new TLSClientHeaders instance from supplied frontend header struct.
|
||||
func NewTLSClientHeaders(frontend *types.Frontend) *TLSClientHeaders {
|
||||
if frontend == nil {
|
||||
func NewTLSClientHeaders(passTLSClientCert *types.TLSClientHeaders) *TLSClientHeaders {
|
||||
if passTLSClientCert == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var addPEM bool
|
||||
var infos *TLSClientCertificateInfos
|
||||
|
||||
if frontend.PassTLSClientCert != nil {
|
||||
conf := frontend.PassTLSClientCert
|
||||
addPEM = conf.PEM
|
||||
infos = newTLSClientInfos(conf.Infos)
|
||||
}
|
||||
|
||||
return &TLSClientHeaders{
|
||||
Infos: infos,
|
||||
PEM: addPEM,
|
||||
Infos: newTLSClientInfos(passTLSClientCert.Infos),
|
||||
PEM: passTLSClientCert.PEM,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +212,7 @@ func writePart(content *strings.Builder, entry string, prefix string) {
|
||||
}
|
||||
}
|
||||
|
||||
// getXForwardedTLSClientCertInfo Build a string with the wanted client certificates informations
|
||||
// getXForwardedTLSClientCertInfo Build a string with the wanted client certificates information
|
||||
// like Subject="DC=%s,C=%s,ST=%s,L=%s,O=%s,CN=%s",NB=%d,NA=%d,SAN=%s;
|
||||
func (s *TLSClientHeaders) getXForwardedTLSClientCertInfo(certs []*x509.Certificate) string {
|
||||
var headerValues []string
|
||||
@@ -268,8 +259,9 @@ func (s *TLSClientHeaders) getXForwardedTLSClientCertInfo(certs []*x509.Certific
|
||||
return strings.Join(headerValues, ";")
|
||||
}
|
||||
|
||||
// ModifyRequestHeaders set the wanted headers with the certificates informations
|
||||
// ModifyRequestHeaders set the wanted headers with the certificates information
|
||||
func (s *TLSClientHeaders) ModifyRequestHeaders(r *http.Request) {
|
||||
r.Header.Del(xForwardedTLSClientCert)
|
||||
if s.PEM {
|
||||
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
|
||||
r.Header.Set(xForwardedTLSClientCert, getXForwardedTLSClientCert(r.TLS.PeerCertificates))
|
||||
@@ -278,6 +270,7 @@ func (s *TLSClientHeaders) ModifyRequestHeaders(r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
r.Header.Del(xForwardedTLSClientCertInfos)
|
||||
if s.Infos != nil {
|
||||
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
|
||||
headerContent := s.getXForwardedTLSClientCertInfo(r.TLS.PeerCertificates)
|
||||
|
@@ -240,26 +240,6 @@ mxcl71pV8i3NDU3kgVi2440JYpoMveTlXPCV2svHNCw0X238YHsSW4b93yGJO0gI
|
||||
ML9n/4zmm1PMhzZHcEA72ZAq0tKCxpz10djg5v2qL5V+Oaz8TtTOZbPsxpiKMQ==
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
minimalCert = `-----BEGIN CERTIFICATE-----
|
||||
MIIDGTCCAgECCQCqLd75YLi2kDANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJG
|
||||
UjETMBEGA1UECAwKU29tZS1TdGF0ZTERMA8GA1UEBwwIVG91bG91c2UxITAfBgNV
|
||||
BAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xODA3MTgwODI4MTZaFw0x
|
||||
ODA4MTcwODI4MTZaMEUxCzAJBgNVBAYTAkZSMRMwEQYDVQQIDApTb21lLVN0YXRl
|
||||
MSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3
|
||||
DQEBAQUAA4IBDwAwggEKAoIBAQC/+frDMMTLQyXG34F68BPhQq0kzK4LIq9Y0/gl
|
||||
FjySZNn1C0QDWA1ubVCAcA6yY204I9cxcQDPNrhC7JlS5QA8Y5rhIBrqQlzZizAi
|
||||
Rj3NTrRjtGUtOScnHuJaWjLy03DWD+aMwb7q718xt5SEABmmUvLwQK+EjW2MeDwj
|
||||
y8/UEIpvrRDmdhGaqv7IFpIDkcIF7FowJ/hwDvx3PMc+z/JWK0ovzpvgbx69AVbw
|
||||
ZxCimeha65rOqVi+lEetD26le+WnOdYsdJ2IkmpPNTXGdfb15xuAc+gFXfMCh7Iw
|
||||
3Ynl6dZtZM/Ok2kiA7/OsmVnRKkWrtBfGYkI9HcNGb3zrk6nAgMBAAEwDQYJKoZI
|
||||
hvcNAQELBQADggEBAC/R+Yvhh1VUhcbK49olWsk/JKqfS3VIDQYZg1Eo+JCPbwgS
|
||||
I1BSYVfMcGzuJTX6ua3m/AHzGF3Tap4GhF4tX12jeIx4R4utnjj7/YKkTvuEM2f4
|
||||
xT56YqI7zalGScIB0iMeyNz1QcimRl+M/49au8ow9hNX8C2tcA2cwd/9OIj/6T8q
|
||||
SBRHc6ojvbqZSJCO0jziGDT1L3D+EDgTjED4nd77v/NRdP+egb0q3P0s4dnQ/5AV
|
||||
aQlQADUn61j3ScbGJ4NSeZFFvsl38jeRi/MEzp0bGgNBcPj6JHi7qbbauZcZfQ05
|
||||
jECvgAY7Nfd9mZ1KtyNaW31is+kag7NsvjxU/kM=
|
||||
-----END CERTIFICATE-----`
|
||||
)
|
||||
|
||||
func getCleanCertContents(certContents []string) string {
|
||||
@@ -303,10 +283,6 @@ func buildTLSWith(certContents []string) *tls.ConnectionState {
|
||||
return &tls.ConnectionState{PeerCertificates: peerCertificates}
|
||||
}
|
||||
|
||||
var myPassTLSClientCustomHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("bar"))
|
||||
})
|
||||
|
||||
func getExpectedSanitized(s string) string {
|
||||
return url.QueryEscape(strings.Replace(s, "\n", "", -1))
|
||||
}
|
||||
@@ -360,20 +336,13 @@ WqeUSNGYV//RunTeuRDAf5OxehERb1srzBXhRZ3cZdzXbgR/`),
|
||||
|
||||
}
|
||||
|
||||
func TestTlsClientheadersWithPEM(t *testing.T) {
|
||||
func TestTlsClientHeadersWithPEM(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
certContents []string // set the request TLS attribute if defined
|
||||
tlsClientCertHeaders *types.TLSClientHeaders
|
||||
expectedHeader string
|
||||
}{
|
||||
{
|
||||
desc: "No TLS, no option",
|
||||
},
|
||||
{
|
||||
desc: "TLS, no option",
|
||||
certContents: []string{minimalCheeseCrt},
|
||||
},
|
||||
{
|
||||
desc: "No TLS, with pem option true",
|
||||
tlsClientCertHeaders: &types.TLSClientHeaders{PEM: true},
|
||||
@@ -399,20 +368,24 @@ func TestTlsClientheadersWithPEM(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
tlsClientHeaders := NewTLSClientHeaders(&types.Frontend{PassTLSClientCert: test.tlsClientCertHeaders})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://example.com/foo", nil)
|
||||
|
||||
if test.certContents != nil && len(test.certContents) > 0 {
|
||||
req.TLS = buildTLSWith(test.certContents)
|
||||
}
|
||||
|
||||
tlsClientHeaders.ServeHTTP(res, req, myPassTLSClientCustomHandler)
|
||||
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tlsClientHeaders := NewTLSClientHeaders(test.tlsClientCertHeaders)
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://example.com/foo", nil)
|
||||
|
||||
if test.tlsClientCertHeaders != nil {
|
||||
req.Header.Set(xForwardedTLSClientCert, "Unsanitized HEADER")
|
||||
}
|
||||
|
||||
if test.certContents != nil && len(test.certContents) > 0 {
|
||||
req.TLS = buildTLSWith(test.certContents)
|
||||
}
|
||||
|
||||
tlsClientHeaders.ServeHTTP(res, req, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("bar"))
|
||||
}))
|
||||
|
||||
require.Equal(t, http.StatusOK, res.Code, "Http Status should be OK")
|
||||
require.Equal(t, "bar", res.Body.String(), "Should be the expected body")
|
||||
@@ -477,7 +450,7 @@ func TestGetSans(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestTlsClientheadersWithCertInfos(t *testing.T) {
|
||||
func TestTlsClientHeadersWithCertInfos(t *testing.T) {
|
||||
minimalCheeseCertAllInfos := `Subject="C=FR,ST=Some-State,O=Cheese",Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2",NB=1544094636,NA=1632568236,SAN=`
|
||||
completeCertAllInfos := `Subject="DC=org,DC=cheese,C=FR,C=US,ST=Cheese org state,ST=Cheese com state,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=*.cheese.com",Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2",NB=1544094616,NA=1607166616,SAN=*.cheese.org,*.cheese.net,*.cheese.com,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2`
|
||||
|
||||
@@ -487,13 +460,6 @@ func TestTlsClientheadersWithCertInfos(t *testing.T) {
|
||||
tlsClientCertHeaders *types.TLSClientHeaders
|
||||
expectedHeader string
|
||||
}{
|
||||
{
|
||||
desc: "No TLS, no option",
|
||||
},
|
||||
{
|
||||
desc: "TLS, no option",
|
||||
certContents: []string{minimalCert},
|
||||
},
|
||||
{
|
||||
desc: "No TLS, with pem option true",
|
||||
tlsClientCertHeaders: &types.TLSClientHeaders{
|
||||
@@ -627,20 +593,24 @@ func TestTlsClientheadersWithCertInfos(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, test := range testCases {
|
||||
tlsClientHeaders := NewTLSClientHeaders(&types.Frontend{PassTLSClientCert: test.tlsClientCertHeaders})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://example.com/foo", nil)
|
||||
|
||||
if test.certContents != nil && len(test.certContents) > 0 {
|
||||
req.TLS = buildTLSWith(test.certContents)
|
||||
}
|
||||
|
||||
tlsClientHeaders.ServeHTTP(res, req, myPassTLSClientCustomHandler)
|
||||
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tlsClientHeaders := NewTLSClientHeaders(test.tlsClientCertHeaders)
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://example.com/foo", nil)
|
||||
|
||||
if test.tlsClientCertHeaders != nil {
|
||||
req.Header.Set(xForwardedTLSClientCertInfos, "Unsanitized HEADER")
|
||||
}
|
||||
|
||||
if test.certContents != nil && len(test.certContents) > 0 {
|
||||
req.TLS = buildTLSWith(test.certContents)
|
||||
}
|
||||
|
||||
tlsClientHeaders.ServeHTTP(res, req, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("bar"))
|
||||
}))
|
||||
|
||||
require.Equal(t, http.StatusOK, res.Code, "Http Status should be OK")
|
||||
require.Equal(t, "bar", res.Body.String(), "Should be the expected body")
|
||||
@@ -664,45 +634,31 @@ func TestTlsClientheadersWithCertInfos(t *testing.T) {
|
||||
|
||||
func TestNewTLSClientHeadersFromStruct(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
frontend *types.Frontend
|
||||
expected *TLSClientHeaders
|
||||
desc string
|
||||
tlsClientHeaders *types.TLSClientHeaders
|
||||
expected *TLSClientHeaders
|
||||
}{
|
||||
{
|
||||
desc: "Without frontend",
|
||||
},
|
||||
{
|
||||
desc: "frontend without the option",
|
||||
frontend: &types.Frontend{},
|
||||
expected: &TLSClientHeaders{},
|
||||
},
|
||||
{
|
||||
desc: "frontend with the pem set false",
|
||||
frontend: &types.Frontend{
|
||||
PassTLSClientCert: &types.TLSClientHeaders{
|
||||
PEM: false,
|
||||
},
|
||||
desc: "TLS client headers with the pem set false",
|
||||
tlsClientHeaders: &types.TLSClientHeaders{
|
||||
PEM: false,
|
||||
},
|
||||
expected: &TLSClientHeaders{PEM: false},
|
||||
},
|
||||
{
|
||||
desc: "frontend with the pem set true",
|
||||
frontend: &types.Frontend{
|
||||
PassTLSClientCert: &types.TLSClientHeaders{
|
||||
PEM: true,
|
||||
},
|
||||
desc: "TLS client headers with the pem set true",
|
||||
tlsClientHeaders: &types.TLSClientHeaders{
|
||||
PEM: true,
|
||||
},
|
||||
expected: &TLSClientHeaders{PEM: true},
|
||||
},
|
||||
{
|
||||
desc: "frontend with the Infos with no flag",
|
||||
frontend: &types.Frontend{
|
||||
PassTLSClientCert: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
NotAfter: false,
|
||||
NotBefore: false,
|
||||
Sans: false,
|
||||
},
|
||||
desc: "TLS client headers with the Infos with no flag",
|
||||
tlsClientHeaders: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
NotAfter: false,
|
||||
NotBefore: false,
|
||||
Sans: false,
|
||||
},
|
||||
},
|
||||
expected: &TLSClientHeaders{
|
||||
@@ -711,14 +667,12 @@ func TestNewTLSClientHeadersFromStruct(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "frontend with the Infos basic",
|
||||
frontend: &types.Frontend{
|
||||
PassTLSClientCert: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
NotAfter: true,
|
||||
NotBefore: true,
|
||||
Sans: true,
|
||||
},
|
||||
desc: "TLS client headers with the Infos basic",
|
||||
tlsClientHeaders: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
NotAfter: true,
|
||||
NotBefore: true,
|
||||
Sans: true,
|
||||
},
|
||||
},
|
||||
expected: &TLSClientHeaders{
|
||||
@@ -731,12 +685,10 @@ func TestNewTLSClientHeadersFromStruct(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "frontend with the Infos NotAfter",
|
||||
frontend: &types.Frontend{
|
||||
PassTLSClientCert: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
NotAfter: true,
|
||||
},
|
||||
desc: "TLS client headers with the Infos NotAfter",
|
||||
tlsClientHeaders: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
NotAfter: true,
|
||||
},
|
||||
},
|
||||
expected: &TLSClientHeaders{
|
||||
@@ -747,12 +699,10 @@ func TestNewTLSClientHeadersFromStruct(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "frontend with the Infos NotBefore",
|
||||
frontend: &types.Frontend{
|
||||
PassTLSClientCert: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
NotBefore: true,
|
||||
},
|
||||
desc: "TLS client headers with the Infos NotBefore",
|
||||
tlsClientHeaders: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
NotBefore: true,
|
||||
},
|
||||
},
|
||||
expected: &TLSClientHeaders{
|
||||
@@ -763,12 +713,10 @@ func TestNewTLSClientHeadersFromStruct(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "frontend with the Infos Sans",
|
||||
frontend: &types.Frontend{
|
||||
PassTLSClientCert: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Sans: true,
|
||||
},
|
||||
desc: "TLS client headers with the Infos Sans",
|
||||
tlsClientHeaders: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Sans: true,
|
||||
},
|
||||
},
|
||||
expected: &TLSClientHeaders{
|
||||
@@ -779,13 +727,11 @@ func TestNewTLSClientHeadersFromStruct(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "frontend with the Infos Subject Organization",
|
||||
frontend: &types.Frontend{
|
||||
PassTLSClientCert: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Subject: &types.TLSCLientCertificateDNInfos{
|
||||
Organization: true,
|
||||
},
|
||||
desc: "TLS client headers with the Infos Subject Organization",
|
||||
tlsClientHeaders: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Subject: &types.TLSCLientCertificateDNInfos{
|
||||
Organization: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -799,13 +745,11 @@ func TestNewTLSClientHeadersFromStruct(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "frontend with the Infos Subject Country",
|
||||
frontend: &types.Frontend{
|
||||
PassTLSClientCert: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Subject: &types.TLSCLientCertificateDNInfos{
|
||||
Country: true,
|
||||
},
|
||||
desc: "TLS client headers with the Infos Subject Country",
|
||||
tlsClientHeaders: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Subject: &types.TLSCLientCertificateDNInfos{
|
||||
Country: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -819,13 +763,11 @@ func TestNewTLSClientHeadersFromStruct(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "frontend with the Infos Subject SerialNumber",
|
||||
frontend: &types.Frontend{
|
||||
PassTLSClientCert: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Subject: &types.TLSCLientCertificateDNInfos{
|
||||
SerialNumber: true,
|
||||
},
|
||||
desc: "TLS client headers with the Infos Subject SerialNumber",
|
||||
tlsClientHeaders: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Subject: &types.TLSCLientCertificateDNInfos{
|
||||
SerialNumber: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -839,13 +781,11 @@ func TestNewTLSClientHeadersFromStruct(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "frontend with the Infos Subject Province",
|
||||
frontend: &types.Frontend{
|
||||
PassTLSClientCert: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Subject: &types.TLSCLientCertificateDNInfos{
|
||||
Province: true,
|
||||
},
|
||||
desc: "TLS client headers with the Infos Subject Province",
|
||||
tlsClientHeaders: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Subject: &types.TLSCLientCertificateDNInfos{
|
||||
Province: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -859,13 +799,11 @@ func TestNewTLSClientHeadersFromStruct(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "frontend with the Infos Subject Locality",
|
||||
frontend: &types.Frontend{
|
||||
PassTLSClientCert: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Subject: &types.TLSCLientCertificateDNInfos{
|
||||
Locality: true,
|
||||
},
|
||||
desc: "TLS client headers with the Infos Subject Locality",
|
||||
tlsClientHeaders: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Subject: &types.TLSCLientCertificateDNInfos{
|
||||
Locality: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -879,13 +817,11 @@ func TestNewTLSClientHeadersFromStruct(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "frontend with the Infos Subject CommonName",
|
||||
frontend: &types.Frontend{
|
||||
PassTLSClientCert: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Subject: &types.TLSCLientCertificateDNInfos{
|
||||
CommonName: true,
|
||||
},
|
||||
desc: "TLS client headers with the Infos Subject CommonName",
|
||||
tlsClientHeaders: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Subject: &types.TLSCLientCertificateDNInfos{
|
||||
CommonName: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -899,19 +835,17 @@ func TestNewTLSClientHeadersFromStruct(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "frontend with the Infos Issuer",
|
||||
frontend: &types.Frontend{
|
||||
PassTLSClientCert: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Issuer: &types.TLSCLientCertificateDNInfos{
|
||||
CommonName: true,
|
||||
Country: true,
|
||||
DomainComponent: true,
|
||||
Locality: true,
|
||||
Organization: true,
|
||||
SerialNumber: true,
|
||||
Province: true,
|
||||
},
|
||||
desc: "TLS client headers with the Infos Issuer",
|
||||
tlsClientHeaders: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Issuer: &types.TLSCLientCertificateDNInfos{
|
||||
CommonName: true,
|
||||
Country: true,
|
||||
DomainComponent: true,
|
||||
Locality: true,
|
||||
Organization: true,
|
||||
SerialNumber: true,
|
||||
Province: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -931,12 +865,10 @@ func TestNewTLSClientHeadersFromStruct(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "frontend with the Sans Infos",
|
||||
frontend: &types.Frontend{
|
||||
PassTLSClientCert: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Sans: true,
|
||||
},
|
||||
desc: "TLS client headers with the Sans Infos",
|
||||
tlsClientHeaders: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
Sans: true,
|
||||
},
|
||||
},
|
||||
expected: &TLSClientHeaders{
|
||||
@@ -947,30 +879,28 @@ func TestNewTLSClientHeadersFromStruct(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "frontend with the Infos all",
|
||||
frontend: &types.Frontend{
|
||||
PassTLSClientCert: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
NotAfter: true,
|
||||
NotBefore: true,
|
||||
Subject: &types.TLSCLientCertificateDNInfos{
|
||||
CommonName: true,
|
||||
Country: true,
|
||||
Locality: true,
|
||||
Organization: true,
|
||||
Province: true,
|
||||
SerialNumber: true,
|
||||
},
|
||||
Issuer: &types.TLSCLientCertificateDNInfos{
|
||||
Country: true,
|
||||
DomainComponent: true,
|
||||
Locality: true,
|
||||
Organization: true,
|
||||
SerialNumber: true,
|
||||
Province: true,
|
||||
},
|
||||
Sans: true,
|
||||
desc: "TLS client headers with the Infos all",
|
||||
tlsClientHeaders: &types.TLSClientHeaders{
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
NotAfter: true,
|
||||
NotBefore: true,
|
||||
Subject: &types.TLSCLientCertificateDNInfos{
|
||||
CommonName: true,
|
||||
Country: true,
|
||||
Locality: true,
|
||||
Organization: true,
|
||||
Province: true,
|
||||
SerialNumber: true,
|
||||
},
|
||||
Issuer: &types.TLSCLientCertificateDNInfos{
|
||||
Country: true,
|
||||
DomainComponent: true,
|
||||
Locality: true,
|
||||
Organization: true,
|
||||
SerialNumber: true,
|
||||
Province: true,
|
||||
},
|
||||
Sans: true,
|
||||
},
|
||||
},
|
||||
expected: &TLSClientHeaders{
|
||||
@@ -1004,8 +934,7 @@ func TestNewTLSClientHeadersFromStruct(t *testing.T) {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.Equal(t, test.expected, NewTLSClientHeaders(test.frontend))
|
||||
require.Equal(t, test.expected, NewTLSClientHeaders(test.tlsClientHeaders))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -34,6 +34,7 @@ type Provider struct {
|
||||
Stale bool `description:"Use stale consistency for catalog reads" export:"true"`
|
||||
ExposedByDefault bool `description:"Expose Consul services by default" export:"true"`
|
||||
Prefix string `description:"Prefix used for Consul catalog tags" export:"true"`
|
||||
StrictChecks bool `description:"Keep a Consul node only if all checks status are passing" export:"true"`
|
||||
FrontEndRule string `description:"Frontend rule used for Consul services" export:"true"`
|
||||
TLS *types.ClientTLS `description:"Enable TLS support" export:"true"`
|
||||
client *api.Client
|
||||
@@ -301,6 +302,8 @@ func (p *Provider) watchHealthState(stopCh <-chan struct{}, watchCh chan<- map[s
|
||||
_, failing := currentFailing[key]
|
||||
if healthy.Status == "passing" && !failing {
|
||||
current[key] = append(current[key], healthy.Node)
|
||||
} else if !p.StrictChecks && healthy.Status == "warning" && !failing {
|
||||
current[key] = append(current[key], healthy.Node)
|
||||
} else if strings.HasPrefix(healthy.CheckID, "_service_maintenance") || strings.HasPrefix(healthy.CheckID, "_node_maintenance") {
|
||||
maintenance = append(maintenance, healthy.CheckID)
|
||||
} else {
|
||||
@@ -489,7 +492,8 @@ func getServiceAddresses(services []*api.CatalogService) []string {
|
||||
|
||||
func (p *Provider) healthyNodes(service string) (catalogUpdate, error) {
|
||||
health := p.client.Health()
|
||||
data, _, err := health.Service(service, "", true, &api.QueryOptions{AllowStale: p.Stale})
|
||||
// You can't filter with assigning passingOnly here, nodeFilter will do this later
|
||||
data, _, err := health.Service(service, "", false, &api.QueryOptions{AllowStale: p.Stale})
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("Failed to fetch details of %s", service)
|
||||
return catalogUpdate{}, err
|
||||
@@ -533,7 +537,8 @@ func (p *Provider) nodeFilter(service string, node *api.ServiceEntry) bool {
|
||||
log.Debugf("Service %v pruned by '%v' constraint", service, failingConstraint.String())
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
return p.hasPassingChecks(node)
|
||||
}
|
||||
|
||||
func (p *Provider) isServiceEnabled(node *api.ServiceEntry) bool {
|
||||
@@ -567,6 +572,11 @@ func (p *Provider) getConstraintTags(tags []string) []string {
|
||||
return values
|
||||
}
|
||||
|
||||
func (p *Provider) hasPassingChecks(node *api.ServiceEntry) bool {
|
||||
status := node.Checks.AggregatedStatus()
|
||||
return status == "passing" || !p.StrictChecks && status == "warning"
|
||||
}
|
||||
|
||||
func (p *Provider) generateFrontends(service *serviceUpdate) []*serviceUpdate {
|
||||
frontends := make([]*serviceUpdate, 0)
|
||||
// to support <prefix>.frontend.xxx
|
||||
|
@@ -28,7 +28,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// SwarmAPIVersion is a constant holding the version of the Provider API traefik will use
|
||||
// DockerAPIVersion is a constant holding the version of the Provider API traefik will use
|
||||
DockerAPIVersion = "1.24"
|
||||
// SwarmAPIVersion is a constant holding the version of the Provider API traefik will use.
|
||||
SwarmAPIVersion = "1.24"
|
||||
)
|
||||
|
||||
@@ -110,11 +112,10 @@ func (p *Provider) createClient() (client.APIClient, error) {
|
||||
"User-Agent": "Traefik " + version.Version,
|
||||
}
|
||||
|
||||
var apiVersion string
|
||||
apiVersion := DockerAPIVersion
|
||||
|
||||
if p.SwarmMode {
|
||||
apiVersion = SwarmAPIVersion
|
||||
} else {
|
||||
apiVersion = DockerAPIVersion
|
||||
}
|
||||
|
||||
return client.NewClient(p.Endpoint, apiVersion, httpClient, httpHeaders)
|
||||
|
@@ -1,8 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package docker
|
||||
|
||||
const (
|
||||
// DockerAPIVersion is a constant holding the version of the Provider API traefik will use
|
||||
DockerAPIVersion = "1.21"
|
||||
)
|
@@ -1,6 +0,0 @@
|
||||
package docker
|
||||
|
||||
const (
|
||||
// DockerAPIVersion is a constant holding the version of the Provider API traefik will use
|
||||
DockerAPIVersion string = "1.24"
|
||||
)
|
@@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: Endpoints
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
ports:
|
||||
- port: 8080
|
||||
name: http
|
||||
- port: 1111
|
||||
name: foo
|
@@ -0,0 +1,8 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
namespace: testing
|
||||
spec:
|
||||
backend:
|
||||
serviceName: service1
|
||||
servicePort: 80
|
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
spec:
|
||||
clusterIP: 10.0.0.1
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
- name: foo
|
||||
port: 1111
|
||||
targetPort: 1111
|
@@ -16,6 +16,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/provider"
|
||||
@@ -68,6 +69,7 @@ type Provider struct {
|
||||
LabelSelector string `description:"Kubernetes Ingress label selector to use" export:"true"`
|
||||
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for" export:"true"`
|
||||
IngressEndpoint *IngressEndpoint `description:"Kubernetes Ingress Endpoint"`
|
||||
ThrottleDuration flaeg.Duration `description:"Ingress refresh throttle duration"`
|
||||
lastConfiguration safe.Safe
|
||||
}
|
||||
|
||||
@@ -137,16 +139,29 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
throttleDuration := time.Duration(p.ThrottleDuration)
|
||||
throttledChan := throttleEvents(throttleDuration, stop, eventsChan)
|
||||
if throttledChan != nil {
|
||||
eventsChan = throttledChan
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
return nil
|
||||
case event := <-eventsChan:
|
||||
// Note that event is the *first* event that came in during this
|
||||
// throttling interval -- if we're hitting our throttle, we may have
|
||||
// dropped events. This is fine, because we don't treat different
|
||||
// event types differently. But if we do in the future, we'll need to
|
||||
// track more information about the dropped events.
|
||||
log.Debugf("Received Kubernetes event kind %T", event)
|
||||
templateObjects, err := p.loadIngresses(k8sClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(p.lastConfiguration.Get(), templateObjects) {
|
||||
log.Debugf("Skipping Kubernetes event kind %T", event)
|
||||
} else {
|
||||
@@ -156,6 +171,11 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||
Configuration: p.loadConfig(*templateObjects),
|
||||
}
|
||||
}
|
||||
|
||||
// If we're throttling, we sleep here for the throttle duration to
|
||||
// enforce that we don't refresh faster than our throttle. time.Sleep
|
||||
// returns immediately if p.ThrottleDuration is 0 (no throttle).
|
||||
time.Sleep(throttleDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -508,6 +528,10 @@ func (p *Provider) addGlobalBackend(cl Client, i *extensionsv1beta1.Ingress, tem
|
||||
|
||||
for _, port := range service.Spec.Ports {
|
||||
|
||||
if !equalPorts(port, i.Spec.Backend.ServicePort) {
|
||||
continue
|
||||
}
|
||||
|
||||
// We have to treat external-name service differently here b/c it doesn't have any endpoints
|
||||
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||
|
||||
@@ -595,6 +619,39 @@ func (p *Provider) addGlobalBackend(cl Client, i *extensionsv1beta1.Ingress, tem
|
||||
return nil
|
||||
}
|
||||
|
||||
func throttleEvents(throttleDuration time.Duration, stop chan bool, eventsChan <-chan interface{}) chan interface{} {
|
||||
if throttleDuration == 0 {
|
||||
return nil
|
||||
}
|
||||
// Create a buffered channel to hold the pending event (if we're delaying processing the event due to throttling)
|
||||
eventsChanBuffered := make(chan interface{}, 1)
|
||||
|
||||
// Run a goroutine that reads events from eventChan and does a
|
||||
// non-blocking write to pendingEvent. This guarantees that writing to
|
||||
// eventChan will never block, and that pendingEvent will have
|
||||
// something in it if there's been an event since we read from that channel.
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
case nextEvent := <-eventsChan:
|
||||
select {
|
||||
case eventsChanBuffered <- nextEvent:
|
||||
default:
|
||||
// We already have an event in eventsChanBuffered, so we'll
|
||||
// do a refresh as soon as our throttle allows us to. It's fine
|
||||
// to drop the event and keep whatever's in the buffer -- we
|
||||
// don't do different things for different events
|
||||
log.Debugf("Dropping event kind %T due to throttling", nextEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return eventsChanBuffered
|
||||
}
|
||||
|
||||
func getRuleForPath(pa extensionsv1beta1.HTTPIngressPath, i *extensionsv1beta1.Ingress) (string, error) {
|
||||
if len(pa.Path) == 0 {
|
||||
return "", nil
|
||||
@@ -936,12 +993,12 @@ func getForwardAuthConfig(i *extensionsv1beta1.Ingress, k8sClient Client) (*type
|
||||
}
|
||||
|
||||
authSecretName := getStringValue(i.Annotations, annotationKubernetesAuthForwardTLSSecret, "")
|
||||
if len(authSecretName) > 0 {
|
||||
authSecretCert, authSecretKey, err := loadAuthTLSSecret(i.Namespace, authSecretName, k8sClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load auth secret: %s", err)
|
||||
}
|
||||
authSecretCert, authSecretKey, err := loadAuthTLSSecret(i.Namespace, authSecretName, k8sClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load auth secret: %s", err)
|
||||
}
|
||||
|
||||
if authSecretCert != "" || authSecretKey != "" {
|
||||
forwardAuth.TLS = &types.ClientTLS{
|
||||
Cert: authSecretCert,
|
||||
Key: authSecretKey,
|
||||
@@ -949,10 +1006,20 @@ func getForwardAuthConfig(i *extensionsv1beta1.Ingress, k8sClient Client) (*type
|
||||
}
|
||||
}
|
||||
|
||||
if forwardAuth.TLS == nil && label.Has(i.Annotations, getAnnotationName(i.Annotations, annotationKubernetesAuthForwardTLSInsecure)) {
|
||||
forwardAuth.TLS = &types.ClientTLS{
|
||||
InsecureSkipVerify: getBoolValue(i.Annotations, annotationKubernetesAuthForwardTLSInsecure, false),
|
||||
}
|
||||
}
|
||||
|
||||
return forwardAuth, nil
|
||||
}
|
||||
|
||||
func loadAuthTLSSecret(namespace, secretName string, k8sClient Client) (string, string, error) {
|
||||
if len(secretName) == 0 {
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
secret, exists, err := k8sClient.GetSecret(namespace, secretName)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to fetch secret %q/%q: %s", namespace, secretName, err)
|
||||
|
@@ -168,6 +168,33 @@ func TestProvider_loadIngresses(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
desc: "loadGlobalIngressWithMultiplePortNumbers",
|
||||
fixtures: []string{
|
||||
filepath.Join("fixtures", "loadGlobalIngressWithMultiplePortNumbers_ingresses.yml"),
|
||||
filepath.Join("fixtures", "loadGlobalIngressWithMultiplePortNumbers_services.yml"),
|
||||
filepath.Join("fixtures", "loadGlobalIngressWithMultiplePortNumbers_endpoints.yml"),
|
||||
},
|
||||
expected: buildConfiguration(
|
||||
backends(
|
||||
backend("global-default-backend",
|
||||
lbMethod("wrr"),
|
||||
servers(
|
||||
server("http://10.10.0.1:8080", weight(1)),
|
||||
),
|
||||
),
|
||||
),
|
||||
frontends(
|
||||
frontend("global-default-backend",
|
||||
frontendName("global-default-frontend"),
|
||||
passHostHeader(),
|
||||
routes(
|
||||
route("/", "PathPrefix:/"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
desc: "loadGlobalIngressWithHttpsPortNames",
|
||||
fixtures: []string{
|
||||
|
@@ -5,7 +5,7 @@ if [ -z "${VALIDATE_UPSTREAM:-}" ]; then
|
||||
# are running more than one validate bundlescript
|
||||
|
||||
VALIDATE_REPO='https://github.com/containous/traefik.git'
|
||||
VALIDATE_BRANCH='master'
|
||||
VALIDATE_BRANCH='v1.7'
|
||||
|
||||
# Should not be needed for now O:)
|
||||
# if [ "$TRAVIS" = 'true' -a "$TRAVIS_PULL_REQUEST" != 'false' ]; then
|
||||
|
@@ -3,9 +3,8 @@ set -e
|
||||
|
||||
# List of bundles to create when no argument is passed
|
||||
DEFAULT_BUNDLES=(
|
||||
validate-gofmt
|
||||
validate-govet
|
||||
generate
|
||||
validate-gofmt
|
||||
binary
|
||||
|
||||
test-unit
|
||||
|
@@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source "$(dirname "$BASH_SOURCE")/.validate"
|
||||
|
||||
IFS=$'\n'
|
||||
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) )
|
||||
unset IFS
|
||||
|
||||
errors=()
|
||||
failedErrcheck=$(errcheck .)
|
||||
if [ "$failedErrcheck" ]; then
|
||||
errors+=( "$failedErrcheck" )
|
||||
fi
|
||||
|
||||
if [ ${#errors[@]} -eq 0 ]; then
|
||||
echo 'Congratulations! All Go source files have been errchecked.'
|
||||
else
|
||||
{
|
||||
echo "Errors from errcheck:"
|
||||
for err in "${errors[@]}"; do
|
||||
echo "$err"
|
||||
done
|
||||
echo
|
||||
echo 'Please fix the above errors. You can test via "errcheck" and commit the result.'
|
||||
echo
|
||||
} >&2
|
||||
false
|
||||
fi
|
@@ -1,31 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source "$(dirname "$BASH_SOURCE")/.validate"
|
||||
|
||||
IFS=$'\n'
|
||||
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) )
|
||||
unset IFS
|
||||
|
||||
errors=()
|
||||
for f in "${files[@]}"; do
|
||||
# we use "git show" here to validate that what's committed passes go vet
|
||||
failedVet=$(go tool vet -printf=false "$f")
|
||||
if [ "$failedVet" ]; then
|
||||
errors+=( "$failedVet" )
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#errors[@]} -eq 0 ]; then
|
||||
echo 'Congratulations! All Go source files have been vetted.'
|
||||
else
|
||||
{
|
||||
echo "Errors from govet:"
|
||||
for err in "${errors[@]}"; do
|
||||
echo "$err"
|
||||
done
|
||||
echo
|
||||
echo 'Please fix the above errors. You can test via "go vet" and commit the result.'
|
||||
echo
|
||||
} >&2
|
||||
false
|
||||
fi
|
@@ -85,6 +85,8 @@ func (h *hijackConnectionTracker) Shutdown(ctx context.Context) error {
|
||||
|
||||
// Close close all the connections in the tracked connections list
|
||||
func (h *hijackConnectionTracker) Close() {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
for conn := range h.conns {
|
||||
if err := conn.Close(); err != nil {
|
||||
log.Errorf("Error while closing Hijacked conn: %v", err)
|
||||
|
@@ -113,7 +113,7 @@ func (s *Server) buildMiddlewares(frontendName string, frontend *types.Frontend,
|
||||
}
|
||||
|
||||
// TLSClientHeaders
|
||||
tlsClientHeadersMiddleware := middlewares.NewTLSClientHeaders(frontend)
|
||||
tlsClientHeadersMiddleware := middlewares.NewTLSClientHeaders(frontend.PassTLSClientCert)
|
||||
if tlsClientHeadersMiddleware != nil {
|
||||
log.Debugf("Adding TLSClientHeaders middleware for frontend %s", frontendName)
|
||||
|
||||
|
@@ -59,7 +59,9 @@
|
||||
|
||||
{{if $frontend.Auth }}
|
||||
[frontends."{{ $frontendName }}".auth]
|
||||
headerField = "X-WebAuth-User"
|
||||
{{if $frontend.Auth.HeaderField }}
|
||||
headerField = "{{ $frontend.Auth.HeaderField }}"
|
||||
{{end}}
|
||||
|
||||
{{if $frontend.Auth.Basic }}
|
||||
[frontends."{{ $frontendName }}".auth.basic]
|
||||
|
@@ -16,36 +16,41 @@ import (
|
||||
var (
|
||||
// MinVersion Map of allowed TLS minimum versions
|
||||
MinVersion = map[string]uint16{
|
||||
`VersionTLS10`: tls.VersionTLS10,
|
||||
`VersionTLS11`: tls.VersionTLS11,
|
||||
`VersionTLS12`: tls.VersionTLS12,
|
||||
"VersionTLS10": tls.VersionTLS10,
|
||||
"VersionTLS11": tls.VersionTLS11,
|
||||
"VersionTLS12": tls.VersionTLS12,
|
||||
"VersionTLS13": tls.VersionTLS13,
|
||||
}
|
||||
|
||||
// CipherSuites Map of TLS CipherSuites from crypto/tls
|
||||
// Available CipherSuites defined at https://golang.org/pkg/crypto/tls/#pkg-constants
|
||||
CipherSuites = map[string]uint16{
|
||||
`TLS_RSA_WITH_RC4_128_SHA`: tls.TLS_RSA_WITH_RC4_128_SHA,
|
||||
`TLS_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
`TLS_RSA_WITH_AES_128_CBC_SHA`: tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
`TLS_RSA_WITH_AES_256_CBC_SHA`: tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
`TLS_RSA_WITH_AES_128_CBC_SHA256`: tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
`TLS_RSA_WITH_AES_128_GCM_SHA256`: tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
`TLS_RSA_WITH_AES_256_GCM_SHA384`: tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
`TLS_ECDHE_ECDSA_WITH_RC4_128_SHA`: tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||
`TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
`TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA`: tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
`TLS_ECDHE_RSA_WITH_RC4_128_SHA`: tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||
`TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
`TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
`TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256`: tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
`TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
`TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
`TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
`TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
`TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305`: tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
`TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305`: tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
|
||||
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
"TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
"TLS_AES_128_GCM_SHA256": tls.TLS_AES_128_GCM_SHA256,
|
||||
"TLS_AES_256_GCM_SHA384": tls.TLS_AES_256_GCM_SHA384,
|
||||
"TLS_CHACHA20_POLY1305_SHA256": tls.TLS_CHACHA20_POLY1305_SHA256,
|
||||
"TLS_FALLBACK_SCSV": tls.TLS_FALLBACK_SCSV,
|
||||
}
|
||||
)
|
||||
|
||||
|
@@ -2,6 +2,7 @@ package tls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -47,6 +48,11 @@ func (c CertificateStore) GetAllDomains() []string {
|
||||
allCerts = append(allCerts, domains)
|
||||
}
|
||||
}
|
||||
|
||||
// Get Default certificate
|
||||
if c.DefaultCertificate != nil {
|
||||
allCerts = append(allCerts, getCertificateDomains(c.DefaultCertificate)...)
|
||||
}
|
||||
return allCerts
|
||||
}
|
||||
|
||||
@@ -115,6 +121,27 @@ func (c CertificateStore) ResetCache() {
|
||||
}
|
||||
}
|
||||
|
||||
func getCertificateDomains(cert *tls.Certificate) []string {
|
||||
if cert == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var names []string
|
||||
if len(x509Cert.Subject.CommonName) > 0 {
|
||||
names = append(names, x509Cert.Subject.CommonName)
|
||||
}
|
||||
for _, san := range x509Cert.DNSNames {
|
||||
names = append(names, san)
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
// MatchDomain return true if a domain match the cert domain
|
||||
func MatchDomain(domain string, certDomain string) bool {
|
||||
if domain == certDomain {
|
||||
|
@@ -13,6 +13,90 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetAllDomains(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
staticCert string
|
||||
dynamicCert string
|
||||
defaultCert string
|
||||
expectedDomains []string
|
||||
}{
|
||||
{
|
||||
desc: "Empty Store, returns no domains",
|
||||
staticCert: "",
|
||||
dynamicCert: "",
|
||||
defaultCert: "",
|
||||
expectedDomains: nil,
|
||||
},
|
||||
{
|
||||
desc: "Static cert domains",
|
||||
staticCert: "snitest.com",
|
||||
dynamicCert: "",
|
||||
defaultCert: "",
|
||||
expectedDomains: []string{"snitest.com"},
|
||||
},
|
||||
{
|
||||
desc: "Dynamic cert domains",
|
||||
staticCert: "",
|
||||
dynamicCert: "snitest.com",
|
||||
defaultCert: "",
|
||||
expectedDomains: []string{"snitest.com"},
|
||||
},
|
||||
{
|
||||
desc: "Default cert domains",
|
||||
staticCert: "",
|
||||
dynamicCert: "",
|
||||
defaultCert: "snitest.com",
|
||||
expectedDomains: []string{"snitest.com"},
|
||||
},
|
||||
{
|
||||
desc: "All domains",
|
||||
staticCert: "www.snitest.com",
|
||||
dynamicCert: "*.snitest.com",
|
||||
defaultCert: "snitest.com",
|
||||
expectedDomains: []string{"www.snitest.com", "*.snitest.com", "snitest.com"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
staticMap := map[string]*tls.Certificate{}
|
||||
if test.staticCert != "" {
|
||||
cert, err := loadTestCert(test.staticCert, false)
|
||||
require.NoError(t, err)
|
||||
staticMap[strings.ToLower(test.staticCert)] = cert
|
||||
}
|
||||
|
||||
dynamicMap := map[string]*tls.Certificate{}
|
||||
if test.dynamicCert != "" {
|
||||
cert, err := loadTestCert(test.dynamicCert, false)
|
||||
require.NoError(t, err)
|
||||
dynamicMap[strings.ToLower(test.dynamicCert)] = cert
|
||||
}
|
||||
|
||||
var defaultCert *tls.Certificate
|
||||
if test.defaultCert != "" {
|
||||
cert, err := loadTestCert(test.defaultCert, false)
|
||||
require.NoError(t, err)
|
||||
defaultCert = cert
|
||||
}
|
||||
|
||||
store := &CertificateStore{
|
||||
DynamicCerts: safe.New(dynamicMap),
|
||||
StaticCerts: safe.New(staticMap),
|
||||
DefaultCertificate: defaultCert,
|
||||
CertCache: cache.New(1*time.Hour, 10*time.Minute),
|
||||
}
|
||||
|
||||
actual := store.GetAllDomains()
|
||||
assert.Equal(t, test.expectedDomains, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBestCertificate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
@@ -116,15 +200,15 @@ func TestGetBestCertificate(t *testing.T) {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
staticMap := map[string]*tls.Certificate{}
|
||||
dynamicMap := map[string]*tls.Certificate{}
|
||||
|
||||
staticMap := map[string]*tls.Certificate{}
|
||||
if test.staticCert != "" {
|
||||
cert, err := loadTestCert(test.staticCert, test.uppercase)
|
||||
require.NoError(t, err)
|
||||
staticMap[strings.ToLower(test.staticCert)] = cert
|
||||
}
|
||||
|
||||
dynamicMap := map[string]*tls.Certificate{}
|
||||
if test.dynamicCert != "" {
|
||||
cert, err := loadTestCert(test.dynamicCert, test.uppercase)
|
||||
require.NoError(t, err)
|
||||
|
@@ -85,7 +85,7 @@ func derCert(privKey *rsa.PrivateKey, expiration time.Time, domain string) ([]by
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: expiration,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement | x509.KeyUsageDataEncipherment,
|
||||
BasicConstraintsValid: true,
|
||||
DNSNames: []string{domain},
|
||||
}
|
||||
|
@@ -408,14 +408,14 @@ type Users []string
|
||||
|
||||
// Basic HTTP basic authentication
|
||||
type Basic struct {
|
||||
Users `json:"-" mapstructure:","`
|
||||
Users `json:"-" mapstructure:"," dynamodbav:"users,omitempty"`
|
||||
UsersFile string `json:"usersFile,omitempty"`
|
||||
RemoveHeader bool `json:"removeHeader,omitempty"`
|
||||
}
|
||||
|
||||
// Digest HTTP authentication
|
||||
type Digest struct {
|
||||
Users `json:"-" mapstructure:","`
|
||||
Users `json:"-" mapstructure:"," dynamodbav:"users,omitempty"`
|
||||
UsersFile string `json:"usersFile,omitempty"`
|
||||
RemoveHeader bool `json:"removeHeader,omitempty"`
|
||||
}
|
||||
@@ -511,7 +511,7 @@ type ClientTLS struct {
|
||||
CA string `description:"TLS CA" json:"ca,omitempty"`
|
||||
CAOptional bool `description:"TLS CA.Optional" json:"caOptional,omitempty"`
|
||||
Cert string `description:"TLS cert" json:"cert,omitempty"`
|
||||
Key string `description:"TLS key" json:"-"`
|
||||
Key string `description:"TLS key" json:"-" dynamodbav:"key,omitempty"`
|
||||
InsecureSkipVerify bool `description:"TLS insecure skip verify" json:"insecureSkipVerify,omitempty"`
|
||||
}
|
||||
|
||||
|
201
vendor/contrib.go.opencensus.io/exporter/ocagent/LICENSE
generated
vendored
Normal file
201
vendor/contrib.go.opencensus.io/exporter/ocagent/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
38
vendor/contrib.go.opencensus.io/exporter/ocagent/common.go
generated
vendored
Normal file
38
vendor/contrib.go.opencensus.io/exporter/ocagent/common.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ocagent
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
var randSrc = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
// retries function fn upto n times, if fn returns an error lest it returns nil early.
|
||||
// It applies exponential backoff in units of (1<<n) + jitter microsends.
|
||||
func nTriesWithExponentialBackoff(nTries int64, timeBaseUnit time.Duration, fn func() error) (err error) {
|
||||
for i := int64(0); i < nTries; i++ {
|
||||
err = fn()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
// Backoff for a time period with a pseudo-random jitter
|
||||
jitter := time.Duration(randSrc.Float64()*100) * time.Microsecond
|
||||
ts := jitter + ((1 << uint64(i)) * timeBaseUnit)
|
||||
<-time.After(ts)
|
||||
}
|
||||
return err
|
||||
}
|
97
vendor/contrib.go.opencensus.io/exporter/ocagent/connection.go
generated
vendored
Normal file
97
vendor/contrib.go.opencensus.io/exporter/ocagent/connection.go
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ocagent
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
sDisconnected int32 = 5 + iota
|
||||
sConnected
|
||||
)
|
||||
|
||||
func (ae *Exporter) setStateDisconnected() {
|
||||
atomic.StoreInt32(&ae.connectionState, sDisconnected)
|
||||
select {
|
||||
case ae.disconnectedCh <- true:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (ae *Exporter) setStateConnected() {
|
||||
atomic.StoreInt32(&ae.connectionState, sConnected)
|
||||
}
|
||||
|
||||
func (ae *Exporter) connected() bool {
|
||||
return atomic.LoadInt32(&ae.connectionState) == sConnected
|
||||
}
|
||||
|
||||
const defaultConnReattemptPeriod = 10 * time.Second
|
||||
|
||||
func (ae *Exporter) indefiniteBackgroundConnection() error {
|
||||
defer func() {
|
||||
ae.backgroundConnectionDoneCh <- true
|
||||
}()
|
||||
|
||||
connReattemptPeriod := ae.reconnectionPeriod
|
||||
if connReattemptPeriod <= 0 {
|
||||
connReattemptPeriod = defaultConnReattemptPeriod
|
||||
}
|
||||
|
||||
// No strong seeding required, nano time can
|
||||
// already help with pseudo uniqueness.
|
||||
rng := rand.New(rand.NewSource(time.Now().UnixNano() + rand.Int63n(1024)))
|
||||
|
||||
// maxJitter: 1 + (70% of the connectionReattemptPeriod)
|
||||
maxJitter := int64(1 + 0.7*float64(connReattemptPeriod))
|
||||
|
||||
for {
|
||||
// Otherwise these will be the normal scenarios to enable
|
||||
// reconnections if we trip out.
|
||||
// 1. If we've stopped, return entirely
|
||||
// 2. Otherwise block until we are disconnected, and
|
||||
// then retry connecting
|
||||
select {
|
||||
case <-ae.stopCh:
|
||||
return errStopped
|
||||
|
||||
case <-ae.disconnectedCh:
|
||||
// Normal scenario that we'll wait for
|
||||
}
|
||||
|
||||
if err := ae.connect(); err == nil {
|
||||
ae.setStateConnected()
|
||||
} else {
|
||||
ae.setStateDisconnected()
|
||||
}
|
||||
|
||||
// Apply some jitter to avoid lockstep retrials of other
|
||||
// agent-exporters. Lockstep retrials could result in an
|
||||
// innocent DDOS, by clogging the machine's resources and network.
|
||||
jitter := time.Duration(rng.Int63n(maxJitter))
|
||||
<-time.After(connReattemptPeriod + jitter)
|
||||
}
|
||||
}
|
||||
|
||||
func (ae *Exporter) connect() error {
|
||||
cc, err := ae.dialToAgent()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ae.enableConnectionStreams(cc)
|
||||
}
|
46
vendor/contrib.go.opencensus.io/exporter/ocagent/nodeinfo.go
generated
vendored
Normal file
46
vendor/contrib.go.opencensus.io/exporter/ocagent/nodeinfo.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ocagent
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1"
|
||||
"go.opencensus.io"
|
||||
)
|
||||
|
||||
// NodeWithStartTime creates a node using nodeName and derives:
|
||||
// Hostname from the environment
|
||||
// Pid from the current process
|
||||
// StartTimestamp from the start time of this process
|
||||
// Language and library information.
|
||||
func NodeWithStartTime(nodeName string) *commonpb.Node {
|
||||
return &commonpb.Node{
|
||||
Identifier: &commonpb.ProcessIdentifier{
|
||||
HostName: os.Getenv("HOSTNAME"),
|
||||
Pid: uint32(os.Getpid()),
|
||||
StartTimestamp: timeToTimestamp(startTime),
|
||||
},
|
||||
LibraryInfo: &commonpb.LibraryInfo{
|
||||
Language: commonpb.LibraryInfo_GO_LANG,
|
||||
ExporterVersion: Version,
|
||||
CoreLibraryVersion: opencensus.Version(),
|
||||
},
|
||||
ServiceInfo: &commonpb.ServiceInfo{
|
||||
Name: nodeName,
|
||||
},
|
||||
Attributes: make(map[string]string),
|
||||
}
|
||||
}
|
496
vendor/contrib.go.opencensus.io/exporter/ocagent/ocagent.go
generated
vendored
Normal file
496
vendor/contrib.go.opencensus.io/exporter/ocagent/ocagent.go
generated
vendored
Normal file
@@ -0,0 +1,496 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ocagent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"google.golang.org/api/support/bundler"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
"go.opencensus.io/plugin/ocgrpc"
|
||||
"go.opencensus.io/resource"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/trace"
|
||||
|
||||
commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1"
|
||||
agentmetricspb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/metrics/v1"
|
||||
agenttracepb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/trace/v1"
|
||||
metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
|
||||
resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1"
|
||||
tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1"
|
||||
)
|
||||
|
||||
var startupMu sync.Mutex
|
||||
var startTime time.Time
|
||||
|
||||
func init() {
|
||||
startupMu.Lock()
|
||||
startTime = time.Now()
|
||||
startupMu.Unlock()
|
||||
}
|
||||
|
||||
var _ trace.Exporter = (*Exporter)(nil)
|
||||
var _ view.Exporter = (*Exporter)(nil)
|
||||
|
||||
type Exporter struct {
|
||||
connectionState int32
|
||||
|
||||
// mu protects the non-atomic and non-channel variables
|
||||
mu sync.RWMutex
|
||||
// senderMu protects the concurrent unsafe traceExporter client
|
||||
senderMu sync.RWMutex
|
||||
started bool
|
||||
stopped bool
|
||||
agentAddress string
|
||||
serviceName string
|
||||
canDialInsecure bool
|
||||
traceExporter agenttracepb.TraceService_ExportClient
|
||||
metricsExporter agentmetricspb.MetricsService_ExportClient
|
||||
nodeInfo *commonpb.Node
|
||||
grpcClientConn *grpc.ClientConn
|
||||
reconnectionPeriod time.Duration
|
||||
resource *resourcepb.Resource
|
||||
compressor string
|
||||
headers map[string]string
|
||||
|
||||
startOnce sync.Once
|
||||
stopCh chan bool
|
||||
disconnectedCh chan bool
|
||||
|
||||
backgroundConnectionDoneCh chan bool
|
||||
|
||||
traceBundler *bundler.Bundler
|
||||
|
||||
// viewDataBundler is the bundler to enable conversion
|
||||
// from OpenCensus-Go view.Data to metricspb.Metric.
|
||||
// Please do not confuse it with metricsBundler!
|
||||
viewDataBundler *bundler.Bundler
|
||||
|
||||
clientTransportCredentials credentials.TransportCredentials
|
||||
}
|
||||
|
||||
func NewExporter(opts ...ExporterOption) (*Exporter, error) {
|
||||
exp, err := NewUnstartedExporter(opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := exp.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return exp, nil
|
||||
}
|
||||
|
||||
const spanDataBufferSize = 300
|
||||
|
||||
func NewUnstartedExporter(opts ...ExporterOption) (*Exporter, error) {
|
||||
e := new(Exporter)
|
||||
for _, opt := range opts {
|
||||
opt.withExporter(e)
|
||||
}
|
||||
traceBundler := bundler.NewBundler((*trace.SpanData)(nil), func(bundle interface{}) {
|
||||
e.uploadTraces(bundle.([]*trace.SpanData))
|
||||
})
|
||||
traceBundler.DelayThreshold = 2 * time.Second
|
||||
traceBundler.BundleCountThreshold = spanDataBufferSize
|
||||
e.traceBundler = traceBundler
|
||||
|
||||
viewDataBundler := bundler.NewBundler((*view.Data)(nil), func(bundle interface{}) {
|
||||
e.uploadViewData(bundle.([]*view.Data))
|
||||
})
|
||||
viewDataBundler.DelayThreshold = 2 * time.Second
|
||||
viewDataBundler.BundleCountThreshold = 500 // TODO: (@odeke-em) make this configurable.
|
||||
e.viewDataBundler = viewDataBundler
|
||||
e.nodeInfo = NodeWithStartTime(e.serviceName)
|
||||
e.resource = resourceProtoFromEnv()
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
const (
|
||||
maxInitialConfigRetries = 10
|
||||
maxInitialTracesRetries = 10
|
||||
)
|
||||
|
||||
var (
|
||||
errAlreadyStarted = errors.New("already started")
|
||||
errNotStarted = errors.New("not started")
|
||||
errStopped = errors.New("stopped")
|
||||
errNoConnection = errors.New("no active connection")
|
||||
)
|
||||
|
||||
// Start dials to the agent, establishing a connection to it. It also
|
||||
// initiates the Config and Trace services by sending over the initial
|
||||
// messages that consist of the node identifier. Start invokes a background
|
||||
// connector that will reattempt connections to the agent periodically
|
||||
// if the connection dies.
|
||||
func (ae *Exporter) Start() error {
|
||||
var err = errAlreadyStarted
|
||||
ae.startOnce.Do(func() {
|
||||
ae.mu.Lock()
|
||||
defer ae.mu.Unlock()
|
||||
|
||||
ae.started = true
|
||||
ae.disconnectedCh = make(chan bool, 1)
|
||||
ae.stopCh = make(chan bool)
|
||||
ae.backgroundConnectionDoneCh = make(chan bool)
|
||||
|
||||
ae.setStateDisconnected()
|
||||
go ae.indefiniteBackgroundConnection()
|
||||
|
||||
err = nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ae *Exporter) prepareAgentAddress() string {
|
||||
if ae.agentAddress != "" {
|
||||
return ae.agentAddress
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", DefaultAgentHost, DefaultAgentPort)
|
||||
}
|
||||
|
||||
func (ae *Exporter) enableConnectionStreams(cc *grpc.ClientConn) error {
|
||||
ae.mu.RLock()
|
||||
started := ae.started
|
||||
nodeInfo := ae.nodeInfo
|
||||
ae.mu.RUnlock()
|
||||
|
||||
if !started {
|
||||
return errNotStarted
|
||||
}
|
||||
|
||||
ae.mu.Lock()
|
||||
// If the previous clientConn was non-nil, close it
|
||||
if ae.grpcClientConn != nil {
|
||||
_ = ae.grpcClientConn.Close()
|
||||
}
|
||||
ae.grpcClientConn = cc
|
||||
ae.mu.Unlock()
|
||||
|
||||
if err := ae.createTraceServiceConnection(ae.grpcClientConn, nodeInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ae.createMetricsServiceConnection(ae.grpcClientConn, nodeInfo)
|
||||
}
|
||||
|
||||
func (ae *Exporter) createTraceServiceConnection(cc *grpc.ClientConn, node *commonpb.Node) error {
|
||||
// Initiate the trace service by sending over node identifier info.
|
||||
traceSvcClient := agenttracepb.NewTraceServiceClient(cc)
|
||||
ctx := context.Background()
|
||||
if len(ae.headers) > 0 {
|
||||
ctx = metadata.NewOutgoingContext(ctx, metadata.New(ae.headers))
|
||||
}
|
||||
traceExporter, err := traceSvcClient.Export(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Exporter.Start:: TraceServiceClient: %v", err)
|
||||
}
|
||||
|
||||
firstTraceMessage := &agenttracepb.ExportTraceServiceRequest{
|
||||
Node: node,
|
||||
Resource: ae.resource,
|
||||
}
|
||||
if err := traceExporter.Send(firstTraceMessage); err != nil {
|
||||
return fmt.Errorf("Exporter.Start:: Failed to initiate the Config service: %v", err)
|
||||
}
|
||||
|
||||
ae.mu.Lock()
|
||||
ae.traceExporter = traceExporter
|
||||
ae.mu.Unlock()
|
||||
|
||||
// Initiate the config service by sending over node identifier info.
|
||||
configStream, err := traceSvcClient.Config(context.Background())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Exporter.Start:: ConfigStream: %v", err)
|
||||
}
|
||||
firstCfgMessage := &agenttracepb.CurrentLibraryConfig{Node: node}
|
||||
if err := configStream.Send(firstCfgMessage); err != nil {
|
||||
return fmt.Errorf("Exporter.Start:: Failed to initiate the Config service: %v", err)
|
||||
}
|
||||
|
||||
// In the background, handle trace configurations that are beamed down
|
||||
// by the agent, but also reply to it with the applied configuration.
|
||||
go ae.handleConfigStreaming(configStream)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ae *Exporter) createMetricsServiceConnection(cc *grpc.ClientConn, node *commonpb.Node) error {
|
||||
metricsSvcClient := agentmetricspb.NewMetricsServiceClient(cc)
|
||||
metricsExporter, err := metricsSvcClient.Export(context.Background())
|
||||
if err != nil {
|
||||
return fmt.Errorf("MetricsExporter: failed to start the service client: %v", err)
|
||||
}
|
||||
// Initiate the metrics service by sending over the first message just containing the Node and Resource.
|
||||
firstMetricsMessage := &agentmetricspb.ExportMetricsServiceRequest{
|
||||
Node: node,
|
||||
Resource: ae.resource,
|
||||
}
|
||||
if err := metricsExporter.Send(firstMetricsMessage); err != nil {
|
||||
return fmt.Errorf("MetricsExporter:: failed to send the first message: %v", err)
|
||||
}
|
||||
|
||||
ae.mu.Lock()
|
||||
ae.metricsExporter = metricsExporter
|
||||
ae.mu.Unlock()
|
||||
|
||||
// With that we are good to go and can start sending metrics
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ae *Exporter) dialToAgent() (*grpc.ClientConn, error) {
|
||||
addr := ae.prepareAgentAddress()
|
||||
var dialOpts []grpc.DialOption
|
||||
if ae.clientTransportCredentials != nil {
|
||||
dialOpts = append(dialOpts, grpc.WithTransportCredentials(ae.clientTransportCredentials))
|
||||
} else if ae.canDialInsecure {
|
||||
dialOpts = append(dialOpts, grpc.WithInsecure())
|
||||
}
|
||||
if ae.compressor != "" {
|
||||
dialOpts = append(dialOpts, grpc.WithDefaultCallOptions(grpc.UseCompressor(ae.compressor)))
|
||||
}
|
||||
dialOpts = append(dialOpts, grpc.WithStatsHandler(&ocgrpc.ClientHandler{}))
|
||||
|
||||
ctx := context.Background()
|
||||
if len(ae.headers) > 0 {
|
||||
ctx = metadata.NewOutgoingContext(ctx, metadata.New(ae.headers))
|
||||
}
|
||||
return grpc.DialContext(ctx, addr, dialOpts...)
|
||||
}
|
||||
|
||||
func (ae *Exporter) handleConfigStreaming(configStream agenttracepb.TraceService_ConfigClient) error {
|
||||
// Note: We haven't yet implemented configuration sending so we
|
||||
// should NOT be changing connection states within this function for now.
|
||||
for {
|
||||
recv, err := configStream.Recv()
|
||||
if err != nil {
|
||||
// TODO: Check if this is a transient error or exponential backoff-able.
|
||||
return err
|
||||
}
|
||||
cfg := recv.Config
|
||||
if cfg == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise now apply the trace configuration sent down from the agent
|
||||
if psamp := cfg.GetProbabilitySampler(); psamp != nil {
|
||||
trace.ApplyConfig(trace.Config{DefaultSampler: trace.ProbabilitySampler(psamp.SamplingProbability)})
|
||||
} else if csamp := cfg.GetConstantSampler(); csamp != nil {
|
||||
alwaysSample := csamp.Decision == tracepb.ConstantSampler_ALWAYS_ON
|
||||
if alwaysSample {
|
||||
trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
|
||||
} else {
|
||||
trace.ApplyConfig(trace.Config{DefaultSampler: trace.NeverSample()})
|
||||
}
|
||||
} else { // TODO: Add the rate limiting sampler here
|
||||
}
|
||||
|
||||
// Then finally send back to upstream the newly applied configuration
|
||||
err = configStream.Send(&agenttracepb.CurrentLibraryConfig{Config: &tracepb.TraceConfig{Sampler: cfg.Sampler}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop shuts down all the connections and resources
|
||||
// related to the exporter.
|
||||
func (ae *Exporter) Stop() error {
|
||||
ae.mu.RLock()
|
||||
cc := ae.grpcClientConn
|
||||
started := ae.started
|
||||
stopped := ae.stopped
|
||||
ae.mu.RUnlock()
|
||||
|
||||
if !started {
|
||||
return errNotStarted
|
||||
}
|
||||
if stopped {
|
||||
// TODO: tell the user that we've already stopped, so perhaps a sentinel error?
|
||||
return nil
|
||||
}
|
||||
|
||||
ae.Flush()
|
||||
|
||||
// Now close the underlying gRPC connection.
|
||||
var err error
|
||||
if cc != nil {
|
||||
err = cc.Close()
|
||||
}
|
||||
|
||||
// At this point we can change the state variables: started and stopped
|
||||
ae.mu.Lock()
|
||||
ae.started = false
|
||||
ae.stopped = true
|
||||
ae.mu.Unlock()
|
||||
close(ae.stopCh)
|
||||
|
||||
// Ensure that the backgroundConnector returns
|
||||
<-ae.backgroundConnectionDoneCh
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ae *Exporter) ExportSpan(sd *trace.SpanData) {
|
||||
if sd == nil {
|
||||
return
|
||||
}
|
||||
_ = ae.traceBundler.Add(sd, 1)
|
||||
}
|
||||
|
||||
func (ae *Exporter) ExportTraceServiceRequest(batch *agenttracepb.ExportTraceServiceRequest) error {
|
||||
if batch == nil || len(batch.Spans) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ae.stopCh:
|
||||
return errStopped
|
||||
|
||||
default:
|
||||
if !ae.connected() {
|
||||
return errNoConnection
|
||||
}
|
||||
|
||||
ae.senderMu.Lock()
|
||||
err := ae.traceExporter.Send(batch)
|
||||
ae.senderMu.Unlock()
|
||||
if err != nil {
|
||||
ae.setStateDisconnected()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (ae *Exporter) ExportView(vd *view.Data) {
|
||||
if vd == nil {
|
||||
return
|
||||
}
|
||||
_ = ae.viewDataBundler.Add(vd, 1)
|
||||
}
|
||||
|
||||
func ocSpanDataToPbSpans(sdl []*trace.SpanData) []*tracepb.Span {
|
||||
if len(sdl) == 0 {
|
||||
return nil
|
||||
}
|
||||
protoSpans := make([]*tracepb.Span, 0, len(sdl))
|
||||
for _, sd := range sdl {
|
||||
if sd != nil {
|
||||
protoSpans = append(protoSpans, ocSpanToProtoSpan(sd))
|
||||
}
|
||||
}
|
||||
return protoSpans
|
||||
}
|
||||
|
||||
func (ae *Exporter) uploadTraces(sdl []*trace.SpanData) {
|
||||
select {
|
||||
case <-ae.stopCh:
|
||||
return
|
||||
|
||||
default:
|
||||
if !ae.connected() {
|
||||
return
|
||||
}
|
||||
|
||||
protoSpans := ocSpanDataToPbSpans(sdl)
|
||||
if len(protoSpans) == 0 {
|
||||
return
|
||||
}
|
||||
ae.senderMu.Lock()
|
||||
err := ae.traceExporter.Send(&agenttracepb.ExportTraceServiceRequest{
|
||||
Spans: protoSpans,
|
||||
})
|
||||
ae.senderMu.Unlock()
|
||||
if err != nil {
|
||||
ae.setStateDisconnected()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ocViewDataToPbMetrics(vdl []*view.Data) []*metricspb.Metric {
|
||||
if len(vdl) == 0 {
|
||||
return nil
|
||||
}
|
||||
metrics := make([]*metricspb.Metric, 0, len(vdl))
|
||||
for _, vd := range vdl {
|
||||
if vd != nil {
|
||||
vmetric, err := viewDataToMetric(vd)
|
||||
// TODO: (@odeke-em) somehow report this error, if it is non-nil.
|
||||
if err == nil && vmetric != nil {
|
||||
metrics = append(metrics, vmetric)
|
||||
}
|
||||
}
|
||||
}
|
||||
return metrics
|
||||
}
|
||||
|
||||
func (ae *Exporter) uploadViewData(vdl []*view.Data) {
|
||||
select {
|
||||
case <-ae.stopCh:
|
||||
return
|
||||
|
||||
default:
|
||||
if !ae.connected() {
|
||||
return
|
||||
}
|
||||
|
||||
protoMetrics := ocViewDataToPbMetrics(vdl)
|
||||
if len(protoMetrics) == 0 {
|
||||
return
|
||||
}
|
||||
err := ae.metricsExporter.Send(&agentmetricspb.ExportMetricsServiceRequest{
|
||||
Metrics: protoMetrics,
|
||||
// TODO:(@odeke-em)
|
||||
// a) Figure out how to derive a Node from the environment
|
||||
// b) Figure out how to derive a Resource from the environment
|
||||
// or better letting users of the exporter configure it.
|
||||
})
|
||||
if err != nil {
|
||||
ae.setStateDisconnected()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ae *Exporter) Flush() {
|
||||
ae.traceBundler.Flush()
|
||||
ae.viewDataBundler.Flush()
|
||||
}
|
||||
|
||||
func resourceProtoFromEnv() *resourcepb.Resource {
|
||||
rs, _ := resource.FromEnv(context.Background())
|
||||
if rs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rprs := &resourcepb.Resource{
|
||||
Type: rs.Type,
|
||||
}
|
||||
if rs.Labels != nil {
|
||||
rprs.Labels = make(map[string]string)
|
||||
for k, v := range rs.Labels {
|
||||
rprs.Labels[k] = v
|
||||
}
|
||||
}
|
||||
return rprs
|
||||
}
|
128
vendor/contrib.go.opencensus.io/exporter/ocagent/options.go
generated
vendored
Normal file
128
vendor/contrib.go.opencensus.io/exporter/ocagent/options.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ocagent
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultAgentPort uint16 = 55678
|
||||
DefaultAgentHost string = "localhost"
|
||||
)
|
||||
|
||||
type ExporterOption interface {
|
||||
withExporter(e *Exporter)
|
||||
}
|
||||
|
||||
type insecureGrpcConnection int
|
||||
|
||||
var _ ExporterOption = (*insecureGrpcConnection)(nil)
|
||||
|
||||
func (igc *insecureGrpcConnection) withExporter(e *Exporter) {
|
||||
e.canDialInsecure = true
|
||||
}
|
||||
|
||||
// WithInsecure disables client transport security for the exporter's gRPC connection
|
||||
// just like grpc.WithInsecure() https://godoc.org/google.golang.org/grpc#WithInsecure
|
||||
// does. Note, by default, client security is required unless WithInsecure is used.
|
||||
func WithInsecure() ExporterOption { return new(insecureGrpcConnection) }
|
||||
|
||||
type addressSetter string
|
||||
|
||||
func (as addressSetter) withExporter(e *Exporter) {
|
||||
e.agentAddress = string(as)
|
||||
}
|
||||
|
||||
var _ ExporterOption = (*addressSetter)(nil)
|
||||
|
||||
// WithAddress allows one to set the address that the exporter will
|
||||
// connect to the agent on. If unset, it will instead try to use
|
||||
// connect to DefaultAgentHost:DefaultAgentPort
|
||||
func WithAddress(addr string) ExporterOption {
|
||||
return addressSetter(addr)
|
||||
}
|
||||
|
||||
type serviceNameSetter string
|
||||
|
||||
func (sns serviceNameSetter) withExporter(e *Exporter) {
|
||||
e.serviceName = string(sns)
|
||||
}
|
||||
|
||||
var _ ExporterOption = (*serviceNameSetter)(nil)
|
||||
|
||||
// WithServiceName allows one to set/override the service name
|
||||
// that the exporter will report to the agent.
|
||||
func WithServiceName(serviceName string) ExporterOption {
|
||||
return serviceNameSetter(serviceName)
|
||||
}
|
||||
|
||||
type reconnectionPeriod time.Duration
|
||||
|
||||
func (rp reconnectionPeriod) withExporter(e *Exporter) {
|
||||
e.reconnectionPeriod = time.Duration(rp)
|
||||
}
|
||||
|
||||
func WithReconnectionPeriod(rp time.Duration) ExporterOption {
|
||||
return reconnectionPeriod(rp)
|
||||
}
|
||||
|
||||
type compressorSetter string
|
||||
|
||||
func (c compressorSetter) withExporter(e *Exporter) {
|
||||
e.compressor = string(c)
|
||||
}
|
||||
|
||||
// UseCompressor will set the compressor for the gRPC client to use when sending requests.
|
||||
// It is the responsibility of the caller to ensure that the compressor set has been registered
|
||||
// with google.golang.org/grpc/encoding. This can be done by encoding.RegisterCompressor. Some
|
||||
// compressors auto-register on import, such as gzip, which can be registered by calling
|
||||
// `import _ "google.golang.org/grpc/encoding/gzip"`
|
||||
func UseCompressor(compressorName string) ExporterOption {
|
||||
return compressorSetter(compressorName)
|
||||
}
|
||||
|
||||
type headerSetter map[string]string
|
||||
|
||||
func (h headerSetter) withExporter(e *Exporter) {
|
||||
e.headers = map[string]string(h)
|
||||
}
|
||||
|
||||
// WithHeaders will send the provided headers when the gRPC stream connection
|
||||
// is instantiated
|
||||
func WithHeaders(headers map[string]string) ExporterOption {
|
||||
return headerSetter(headers)
|
||||
}
|
||||
|
||||
type clientCredentials struct {
|
||||
credentials.TransportCredentials
|
||||
}
|
||||
|
||||
var _ ExporterOption = (*clientCredentials)(nil)
|
||||
|
||||
// WithTLSCredentials allows the connection to use TLS credentials
|
||||
// when talking to the server. It takes in grpc.TransportCredentials instead
|
||||
// of say a Certificate file or a tls.Certificate, because the retrieving
|
||||
// these credentials can be done in many ways e.g. plain file, in code tls.Config
|
||||
// or by certificate rotation, so it is up to the caller to decide what to use.
|
||||
func WithTLSCredentials(creds credentials.TransportCredentials) ExporterOption {
|
||||
return &clientCredentials{TransportCredentials: creds}
|
||||
}
|
||||
|
||||
func (cc *clientCredentials) withExporter(e *Exporter) {
|
||||
e.clientTransportCredentials = cc.TransportCredentials
|
||||
}
|
248
vendor/contrib.go.opencensus.io/exporter/ocagent/transform_spans.go
generated
vendored
Normal file
248
vendor/contrib.go.opencensus.io/exporter/ocagent/transform_spans.go
generated
vendored
Normal file
@@ -0,0 +1,248 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ocagent
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"go.opencensus.io/trace"
|
||||
"go.opencensus.io/trace/tracestate"
|
||||
|
||||
tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1"
|
||||
"github.com/golang/protobuf/ptypes/timestamp"
|
||||
)
|
||||
|
||||
const (
|
||||
maxAnnotationEventsPerSpan = 32
|
||||
maxMessageEventsPerSpan = 128
|
||||
)
|
||||
|
||||
func ocSpanToProtoSpan(sd *trace.SpanData) *tracepb.Span {
|
||||
if sd == nil {
|
||||
return nil
|
||||
}
|
||||
var namePtr *tracepb.TruncatableString
|
||||
if sd.Name != "" {
|
||||
namePtr = &tracepb.TruncatableString{Value: sd.Name}
|
||||
}
|
||||
return &tracepb.Span{
|
||||
TraceId: sd.TraceID[:],
|
||||
SpanId: sd.SpanID[:],
|
||||
ParentSpanId: sd.ParentSpanID[:],
|
||||
Status: ocStatusToProtoStatus(sd.Status),
|
||||
StartTime: timeToTimestamp(sd.StartTime),
|
||||
EndTime: timeToTimestamp(sd.EndTime),
|
||||
Links: ocLinksToProtoLinks(sd.Links),
|
||||
Kind: ocSpanKindToProtoSpanKind(sd.SpanKind),
|
||||
Name: namePtr,
|
||||
Attributes: ocAttributesToProtoAttributes(sd.Attributes),
|
||||
TimeEvents: ocTimeEventsToProtoTimeEvents(sd.Annotations, sd.MessageEvents),
|
||||
Tracestate: ocTracestateToProtoTracestate(sd.Tracestate),
|
||||
}
|
||||
}
|
||||
|
||||
var blankStatus trace.Status
|
||||
|
||||
func ocStatusToProtoStatus(status trace.Status) *tracepb.Status {
|
||||
if status == blankStatus {
|
||||
return nil
|
||||
}
|
||||
return &tracepb.Status{
|
||||
Code: status.Code,
|
||||
Message: status.Message,
|
||||
}
|
||||
}
|
||||
|
||||
func ocLinksToProtoLinks(links []trace.Link) *tracepb.Span_Links {
|
||||
if len(links) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sl := make([]*tracepb.Span_Link, 0, len(links))
|
||||
for _, ocLink := range links {
|
||||
// This redefinition is necessary to prevent ocLink.*ID[:] copies
|
||||
// being reused -- in short we need a new ocLink per iteration.
|
||||
ocLink := ocLink
|
||||
|
||||
sl = append(sl, &tracepb.Span_Link{
|
||||
TraceId: ocLink.TraceID[:],
|
||||
SpanId: ocLink.SpanID[:],
|
||||
Type: ocLinkTypeToProtoLinkType(ocLink.Type),
|
||||
})
|
||||
}
|
||||
|
||||
return &tracepb.Span_Links{
|
||||
Link: sl,
|
||||
}
|
||||
}
|
||||
|
||||
func ocLinkTypeToProtoLinkType(oct trace.LinkType) tracepb.Span_Link_Type {
|
||||
switch oct {
|
||||
case trace.LinkTypeChild:
|
||||
return tracepb.Span_Link_CHILD_LINKED_SPAN
|
||||
case trace.LinkTypeParent:
|
||||
return tracepb.Span_Link_PARENT_LINKED_SPAN
|
||||
default:
|
||||
return tracepb.Span_Link_TYPE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func ocAttributesToProtoAttributes(attrs map[string]interface{}) *tracepb.Span_Attributes {
|
||||
if len(attrs) == 0 {
|
||||
return nil
|
||||
}
|
||||
outMap := make(map[string]*tracepb.AttributeValue)
|
||||
for k, v := range attrs {
|
||||
switch v := v.(type) {
|
||||
case bool:
|
||||
outMap[k] = &tracepb.AttributeValue{Value: &tracepb.AttributeValue_BoolValue{BoolValue: v}}
|
||||
|
||||
case int:
|
||||
outMap[k] = &tracepb.AttributeValue{Value: &tracepb.AttributeValue_IntValue{IntValue: int64(v)}}
|
||||
|
||||
case int64:
|
||||
outMap[k] = &tracepb.AttributeValue{Value: &tracepb.AttributeValue_IntValue{IntValue: v}}
|
||||
|
||||
case string:
|
||||
outMap[k] = &tracepb.AttributeValue{
|
||||
Value: &tracepb.AttributeValue_StringValue{
|
||||
StringValue: &tracepb.TruncatableString{Value: v},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
return &tracepb.Span_Attributes{
|
||||
AttributeMap: outMap,
|
||||
}
|
||||
}
|
||||
|
||||
// This code is mostly copied from
|
||||
// https://github.com/census-ecosystem/opencensus-go-exporter-stackdriver/blob/master/trace_proto.go#L46
|
||||
func ocTimeEventsToProtoTimeEvents(as []trace.Annotation, es []trace.MessageEvent) *tracepb.Span_TimeEvents {
|
||||
if len(as) == 0 && len(es) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
timeEvents := &tracepb.Span_TimeEvents{}
|
||||
var annotations, droppedAnnotationsCount int
|
||||
var messageEvents, droppedMessageEventsCount int
|
||||
|
||||
// Transform annotations
|
||||
for i, a := range as {
|
||||
if annotations >= maxAnnotationEventsPerSpan {
|
||||
droppedAnnotationsCount = len(as) - i
|
||||
break
|
||||
}
|
||||
annotations++
|
||||
timeEvents.TimeEvent = append(timeEvents.TimeEvent,
|
||||
&tracepb.Span_TimeEvent{
|
||||
Time: timeToTimestamp(a.Time),
|
||||
Value: transformAnnotationToTimeEvent(&a),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Transform message events
|
||||
for i, e := range es {
|
||||
if messageEvents >= maxMessageEventsPerSpan {
|
||||
droppedMessageEventsCount = len(es) - i
|
||||
break
|
||||
}
|
||||
messageEvents++
|
||||
timeEvents.TimeEvent = append(timeEvents.TimeEvent,
|
||||
&tracepb.Span_TimeEvent{
|
||||
Time: timeToTimestamp(e.Time),
|
||||
Value: transformMessageEventToTimeEvent(&e),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Process dropped counter
|
||||
timeEvents.DroppedAnnotationsCount = clip32(droppedAnnotationsCount)
|
||||
timeEvents.DroppedMessageEventsCount = clip32(droppedMessageEventsCount)
|
||||
|
||||
return timeEvents
|
||||
}
|
||||
|
||||
func transformAnnotationToTimeEvent(a *trace.Annotation) *tracepb.Span_TimeEvent_Annotation_ {
|
||||
return &tracepb.Span_TimeEvent_Annotation_{
|
||||
Annotation: &tracepb.Span_TimeEvent_Annotation{
|
||||
Description: &tracepb.TruncatableString{Value: a.Message},
|
||||
Attributes: ocAttributesToProtoAttributes(a.Attributes),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func transformMessageEventToTimeEvent(e *trace.MessageEvent) *tracepb.Span_TimeEvent_MessageEvent_ {
|
||||
return &tracepb.Span_TimeEvent_MessageEvent_{
|
||||
MessageEvent: &tracepb.Span_TimeEvent_MessageEvent{
|
||||
Type: tracepb.Span_TimeEvent_MessageEvent_Type(e.EventType),
|
||||
Id: uint64(e.MessageID),
|
||||
UncompressedSize: uint64(e.UncompressedByteSize),
|
||||
CompressedSize: uint64(e.CompressedByteSize),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// clip32 clips an int to the range of an int32.
|
||||
func clip32(x int) int32 {
|
||||
if x < math.MinInt32 {
|
||||
return math.MinInt32
|
||||
}
|
||||
if x > math.MaxInt32 {
|
||||
return math.MaxInt32
|
||||
}
|
||||
return int32(x)
|
||||
}
|
||||
|
||||
func timeToTimestamp(t time.Time) *timestamp.Timestamp {
|
||||
nanoTime := t.UnixNano()
|
||||
return ×tamp.Timestamp{
|
||||
Seconds: nanoTime / 1e9,
|
||||
Nanos: int32(nanoTime % 1e9),
|
||||
}
|
||||
}
|
||||
|
||||
func ocSpanKindToProtoSpanKind(kind int) tracepb.Span_SpanKind {
|
||||
switch kind {
|
||||
case trace.SpanKindClient:
|
||||
return tracepb.Span_CLIENT
|
||||
case trace.SpanKindServer:
|
||||
return tracepb.Span_SERVER
|
||||
default:
|
||||
return tracepb.Span_SPAN_KIND_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func ocTracestateToProtoTracestate(ts *tracestate.Tracestate) *tracepb.Span_Tracestate {
|
||||
if ts == nil {
|
||||
return nil
|
||||
}
|
||||
return &tracepb.Span_Tracestate{
|
||||
Entries: ocTracestateEntriesToProtoTracestateEntries(ts.Entries()),
|
||||
}
|
||||
}
|
||||
|
||||
func ocTracestateEntriesToProtoTracestateEntries(entries []tracestate.Entry) []*tracepb.Span_Tracestate_Entry {
|
||||
protoEntries := make([]*tracepb.Span_Tracestate_Entry, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
protoEntries = append(protoEntries, &tracepb.Span_Tracestate_Entry{
|
||||
Key: entry.Key,
|
||||
Value: entry.Value,
|
||||
})
|
||||
}
|
||||
return protoEntries
|
||||
}
|
274
vendor/contrib.go.opencensus.io/exporter/ocagent/transform_stats_to_metrics.go
generated
vendored
Normal file
274
vendor/contrib.go.opencensus.io/exporter/ocagent/transform_stats_to_metrics.go
generated
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ocagent
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/tag"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/timestamp"
|
||||
|
||||
metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
errNilMeasure = errors.New("expecting a non-nil stats.Measure")
|
||||
errNilView = errors.New("expecting a non-nil view.View")
|
||||
errNilViewData = errors.New("expecting a non-nil view.Data")
|
||||
)
|
||||
|
||||
func viewDataToMetric(vd *view.Data) (*metricspb.Metric, error) {
|
||||
if vd == nil {
|
||||
return nil, errNilViewData
|
||||
}
|
||||
|
||||
descriptor, err := viewToMetricDescriptor(vd.View)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
timeseries, err := viewDataToTimeseries(vd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metric := &metricspb.Metric{
|
||||
MetricDescriptor: descriptor,
|
||||
Timeseries: timeseries,
|
||||
}
|
||||
return metric, nil
|
||||
}
|
||||
|
||||
func viewToMetricDescriptor(v *view.View) (*metricspb.MetricDescriptor, error) {
|
||||
if v == nil {
|
||||
return nil, errNilView
|
||||
}
|
||||
if v.Measure == nil {
|
||||
return nil, errNilMeasure
|
||||
}
|
||||
|
||||
desc := &metricspb.MetricDescriptor{
|
||||
Name: stringOrCall(v.Name, v.Measure.Name),
|
||||
Description: stringOrCall(v.Description, v.Measure.Description),
|
||||
Unit: v.Measure.Unit(),
|
||||
Type: aggregationToMetricDescriptorType(v),
|
||||
LabelKeys: tagKeysToLabelKeys(v.TagKeys),
|
||||
}
|
||||
return desc, nil
|
||||
}
|
||||
|
||||
func stringOrCall(first string, call func() string) string {
|
||||
if first != "" {
|
||||
return first
|
||||
}
|
||||
return call()
|
||||
}
|
||||
|
||||
type measureType uint
|
||||
|
||||
const (
|
||||
measureUnknown measureType = iota
|
||||
measureInt64
|
||||
measureFloat64
|
||||
)
|
||||
|
||||
func measureTypeFromMeasure(m stats.Measure) measureType {
|
||||
switch m.(type) {
|
||||
default:
|
||||
return measureUnknown
|
||||
case *stats.Float64Measure:
|
||||
return measureFloat64
|
||||
case *stats.Int64Measure:
|
||||
return measureInt64
|
||||
}
|
||||
}
|
||||
|
||||
func aggregationToMetricDescriptorType(v *view.View) metricspb.MetricDescriptor_Type {
|
||||
if v == nil || v.Aggregation == nil {
|
||||
return metricspb.MetricDescriptor_UNSPECIFIED
|
||||
}
|
||||
if v.Measure == nil {
|
||||
return metricspb.MetricDescriptor_UNSPECIFIED
|
||||
}
|
||||
|
||||
switch v.Aggregation.Type {
|
||||
case view.AggTypeCount:
|
||||
// Cumulative on int64
|
||||
return metricspb.MetricDescriptor_CUMULATIVE_INT64
|
||||
|
||||
case view.AggTypeDistribution:
|
||||
// Cumulative types
|
||||
return metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION
|
||||
|
||||
case view.AggTypeLastValue:
|
||||
// Gauge types
|
||||
switch measureTypeFromMeasure(v.Measure) {
|
||||
case measureFloat64:
|
||||
return metricspb.MetricDescriptor_GAUGE_DOUBLE
|
||||
case measureInt64:
|
||||
return metricspb.MetricDescriptor_GAUGE_INT64
|
||||
}
|
||||
|
||||
case view.AggTypeSum:
|
||||
// Cumulative types
|
||||
switch measureTypeFromMeasure(v.Measure) {
|
||||
case measureFloat64:
|
||||
return metricspb.MetricDescriptor_CUMULATIVE_DOUBLE
|
||||
case measureInt64:
|
||||
return metricspb.MetricDescriptor_CUMULATIVE_INT64
|
||||
}
|
||||
}
|
||||
|
||||
// For all other cases, return unspecified.
|
||||
return metricspb.MetricDescriptor_UNSPECIFIED
|
||||
}
|
||||
|
||||
func tagKeysToLabelKeys(tagKeys []tag.Key) []*metricspb.LabelKey {
|
||||
labelKeys := make([]*metricspb.LabelKey, 0, len(tagKeys))
|
||||
for _, tagKey := range tagKeys {
|
||||
labelKeys = append(labelKeys, &metricspb.LabelKey{
|
||||
Key: tagKey.Name(),
|
||||
})
|
||||
}
|
||||
return labelKeys
|
||||
}
|
||||
|
||||
func viewDataToTimeseries(vd *view.Data) ([]*metricspb.TimeSeries, error) {
|
||||
if vd == nil || len(vd.Rows) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Given that view.Data only contains Start, End
|
||||
// the timestamps for all the row data will be the exact same
|
||||
// per aggregation. However, the values will differ.
|
||||
// Each row has its own tags.
|
||||
startTimestamp := timeToProtoTimestamp(vd.Start)
|
||||
endTimestamp := timeToProtoTimestamp(vd.End)
|
||||
|
||||
mType := measureTypeFromMeasure(vd.View.Measure)
|
||||
timeseries := make([]*metricspb.TimeSeries, 0, len(vd.Rows))
|
||||
// It is imperative that the ordering of "LabelValues" matches those
|
||||
// of the Label keys in the metric descriptor.
|
||||
for _, row := range vd.Rows {
|
||||
labelValues := labelValuesFromTags(row.Tags)
|
||||
point := rowToPoint(vd.View, row, endTimestamp, mType)
|
||||
timeseries = append(timeseries, &metricspb.TimeSeries{
|
||||
StartTimestamp: startTimestamp,
|
||||
LabelValues: labelValues,
|
||||
Points: []*metricspb.Point{point},
|
||||
})
|
||||
}
|
||||
|
||||
if len(timeseries) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return timeseries, nil
|
||||
}
|
||||
|
||||
func timeToProtoTimestamp(t time.Time) *timestamp.Timestamp {
|
||||
unixNano := t.UnixNano()
|
||||
return ×tamp.Timestamp{
|
||||
Seconds: int64(unixNano / 1e9),
|
||||
Nanos: int32(unixNano % 1e9),
|
||||
}
|
||||
}
|
||||
|
||||
func rowToPoint(v *view.View, row *view.Row, endTimestamp *timestamp.Timestamp, mType measureType) *metricspb.Point {
|
||||
pt := &metricspb.Point{
|
||||
Timestamp: endTimestamp,
|
||||
}
|
||||
|
||||
switch data := row.Data.(type) {
|
||||
case *view.CountData:
|
||||
pt.Value = &metricspb.Point_Int64Value{Int64Value: data.Value}
|
||||
|
||||
case *view.DistributionData:
|
||||
pt.Value = &metricspb.Point_DistributionValue{
|
||||
DistributionValue: &metricspb.DistributionValue{
|
||||
Count: data.Count,
|
||||
Sum: float64(data.Count) * data.Mean, // because Mean := Sum/Count
|
||||
// TODO: Add Exemplar
|
||||
Buckets: bucketsToProtoBuckets(data.CountPerBucket),
|
||||
BucketOptions: &metricspb.DistributionValue_BucketOptions{
|
||||
Type: &metricspb.DistributionValue_BucketOptions_Explicit_{
|
||||
Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{
|
||||
Bounds: v.Aggregation.Buckets,
|
||||
},
|
||||
},
|
||||
},
|
||||
SumOfSquaredDeviation: data.SumOfSquaredDev,
|
||||
}}
|
||||
|
||||
case *view.LastValueData:
|
||||
setPointValue(pt, data.Value, mType)
|
||||
|
||||
case *view.SumData:
|
||||
setPointValue(pt, data.Value, mType)
|
||||
}
|
||||
|
||||
return pt
|
||||
}
|
||||
|
||||
// Not returning anything from this function because metricspb.Point.is_Value is an unexported
|
||||
// interface hence we just have to set its value by pointer.
|
||||
func setPointValue(pt *metricspb.Point, value float64, mType measureType) {
|
||||
if mType == measureInt64 {
|
||||
pt.Value = &metricspb.Point_Int64Value{Int64Value: int64(value)}
|
||||
} else {
|
||||
pt.Value = &metricspb.Point_DoubleValue{DoubleValue: value}
|
||||
}
|
||||
}
|
||||
|
||||
func bucketsToProtoBuckets(countPerBucket []int64) []*metricspb.DistributionValue_Bucket {
|
||||
distBuckets := make([]*metricspb.DistributionValue_Bucket, len(countPerBucket))
|
||||
for i := 0; i < len(countPerBucket); i++ {
|
||||
count := countPerBucket[i]
|
||||
|
||||
distBuckets[i] = &metricspb.DistributionValue_Bucket{
|
||||
Count: count,
|
||||
}
|
||||
}
|
||||
|
||||
return distBuckets
|
||||
}
|
||||
|
||||
func labelValuesFromTags(tags []tag.Tag) []*metricspb.LabelValue {
|
||||
if len(tags) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
labelValues := make([]*metricspb.LabelValue, 0, len(tags))
|
||||
for _, tag_ := range tags {
|
||||
labelValues = append(labelValues, &metricspb.LabelValue{
|
||||
Value: tag_.Value,
|
||||
|
||||
// It is imperative that we set the "HasValue" attribute,
|
||||
// in order to distinguish missing a label from the empty string.
|
||||
// https://godoc.org/github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1#LabelValue.HasValue
|
||||
//
|
||||
// OpenCensus-Go uses non-pointers for tags as seen by this function's arguments,
|
||||
// so the best case that we can use to distinguish missing labels/tags from the
|
||||
// empty string is by checking if the Tag.Key.Name() != "" to indicate that we have
|
||||
// a value.
|
||||
HasValue: tag_.Key.Name() != "",
|
||||
})
|
||||
}
|
||||
return labelValues
|
||||
}
|
17
vendor/contrib.go.opencensus.io/exporter/ocagent/version.go
generated
vendored
Normal file
17
vendor/contrib.go.opencensus.io/exporter/ocagent/version.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ocagent
|
||||
|
||||
const Version = "0.0.1"
|
340
vendor/github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2017-09-01/dns/models.go
generated
vendored
340
vendor/github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2017-09-01/dns/models.go
generated
vendored
@@ -18,13 +18,18 @@ package dns
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/Azure/go-autorest/tracing"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// The package's fully qualified name.
|
||||
const fqdn = "github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2017-09-01/dns"
|
||||
|
||||
// RecordType enumerates the values for record type.
|
||||
type RecordType string
|
||||
|
||||
@@ -68,6 +73,18 @@ type ARecord struct {
|
||||
Ipv4Address *string `json:"ipv4Address,omitempty"`
|
||||
}
|
||||
|
||||
// AzureEntityResource the resource model definition for a Azure Resource Manager resource with an etag.
|
||||
type AzureEntityResource struct {
|
||||
// Etag - READ-ONLY; Resource Etag.
|
||||
Etag *string `json:"etag,omitempty"`
|
||||
// ID - READ-ONLY; Fully qualified resource Id for the resource. Ex - /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}
|
||||
ID *string `json:"id,omitempty"`
|
||||
// Name - READ-ONLY; The name of the resource
|
||||
Name *string `json:"name,omitempty"`
|
||||
// Type - READ-ONLY; The type of the resource. Ex- Microsoft.Compute/virtualMachines or Microsoft.Storage/storageAccounts.
|
||||
Type *string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// CaaRecord a CAA record.
|
||||
type CaaRecord struct {
|
||||
// Flags - The flags for this CAA record as an integer between 0 and 255.
|
||||
@@ -116,6 +133,17 @@ type NsRecord struct {
|
||||
Nsdname *string `json:"nsdname,omitempty"`
|
||||
}
|
||||
|
||||
// ProxyResource the resource model definition for a ARM proxy resource. It will have everything other than
|
||||
// required location and tags
|
||||
type ProxyResource struct {
|
||||
// ID - READ-ONLY; Fully qualified resource Id for the resource. Ex - /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}
|
||||
ID *string `json:"id,omitempty"`
|
||||
// Name - READ-ONLY; The name of the resource
|
||||
Name *string `json:"name,omitempty"`
|
||||
// Type - READ-ONLY; The type of the resource. Ex- Microsoft.Compute/virtualMachines or Microsoft.Storage/storageAccounts.
|
||||
Type *string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// PtrRecord a PTR record.
|
||||
type PtrRecord struct {
|
||||
// Ptrdname - The PTR target domain name for this PTR record.
|
||||
@@ -125,11 +153,11 @@ type PtrRecord struct {
|
||||
// RecordSet describes a DNS record set (a collection of DNS records with the same name and type).
|
||||
type RecordSet struct {
|
||||
autorest.Response `json:"-"`
|
||||
// ID - The ID of the record set.
|
||||
// ID - READ-ONLY; The ID of the record set.
|
||||
ID *string `json:"id,omitempty"`
|
||||
// Name - The name of the record set.
|
||||
// Name - READ-ONLY; The name of the record set.
|
||||
Name *string `json:"name,omitempty"`
|
||||
// Type - The type of the record set.
|
||||
// Type - READ-ONLY; The type of the record set.
|
||||
Type *string `json:"type,omitempty"`
|
||||
// Etag - The etag of the record set.
|
||||
Etag *string `json:"etag,omitempty"`
|
||||
@@ -140,15 +168,6 @@ type RecordSet struct {
|
||||
// MarshalJSON is the custom marshaler for RecordSet.
|
||||
func (rs RecordSet) MarshalJSON() ([]byte, error) {
|
||||
objectMap := make(map[string]interface{})
|
||||
if rs.ID != nil {
|
||||
objectMap["id"] = rs.ID
|
||||
}
|
||||
if rs.Name != nil {
|
||||
objectMap["name"] = rs.Name
|
||||
}
|
||||
if rs.Type != nil {
|
||||
objectMap["type"] = rs.Type
|
||||
}
|
||||
if rs.Etag != nil {
|
||||
objectMap["etag"] = rs.Etag
|
||||
}
|
||||
@@ -223,7 +242,7 @@ type RecordSetListResult struct {
|
||||
autorest.Response `json:"-"`
|
||||
// Value - Information about the record sets in the response.
|
||||
Value *[]RecordSet `json:"value,omitempty"`
|
||||
// NextLink - The continuation token for the next page of results.
|
||||
// NextLink - READ-ONLY; The continuation token for the next page of results.
|
||||
NextLink *string `json:"nextLink,omitempty"`
|
||||
}
|
||||
|
||||
@@ -233,14 +252,24 @@ type RecordSetListResultIterator struct {
|
||||
page RecordSetListResultPage
|
||||
}
|
||||
|
||||
// Next advances to the next value. If there was an error making
|
||||
// NextWithContext advances to the next value. If there was an error making
|
||||
// the request the iterator does not advance and the error is returned.
|
||||
func (iter *RecordSetListResultIterator) Next() error {
|
||||
func (iter *RecordSetListResultIterator) NextWithContext(ctx context.Context) (err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetListResultIterator.NextWithContext")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if iter.Response().Response.Response != nil {
|
||||
sc = iter.Response().Response.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
iter.i++
|
||||
if iter.i < len(iter.page.Values()) {
|
||||
return nil
|
||||
}
|
||||
err := iter.page.Next()
|
||||
err = iter.page.NextWithContext(ctx)
|
||||
if err != nil {
|
||||
iter.i--
|
||||
return err
|
||||
@@ -249,6 +278,13 @@ func (iter *RecordSetListResultIterator) Next() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Next advances to the next value. If there was an error making
|
||||
// the request the iterator does not advance and the error is returned.
|
||||
// Deprecated: Use NextWithContext() instead.
|
||||
func (iter *RecordSetListResultIterator) Next() error {
|
||||
return iter.NextWithContext(context.Background())
|
||||
}
|
||||
|
||||
// NotDone returns true if the enumeration should be started or is not yet complete.
|
||||
func (iter RecordSetListResultIterator) NotDone() bool {
|
||||
return iter.page.NotDone() && iter.i < len(iter.page.Values())
|
||||
@@ -268,6 +304,11 @@ func (iter RecordSetListResultIterator) Value() RecordSet {
|
||||
return iter.page.Values()[iter.i]
|
||||
}
|
||||
|
||||
// Creates a new instance of the RecordSetListResultIterator type.
|
||||
func NewRecordSetListResultIterator(page RecordSetListResultPage) RecordSetListResultIterator {
|
||||
return RecordSetListResultIterator{page: page}
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the ListResult contains no values.
|
||||
func (rslr RecordSetListResult) IsEmpty() bool {
|
||||
return rslr.Value == nil || len(*rslr.Value) == 0
|
||||
@@ -275,11 +316,11 @@ func (rslr RecordSetListResult) IsEmpty() bool {
|
||||
|
||||
// recordSetListResultPreparer prepares a request to retrieve the next set of results.
|
||||
// It returns nil if no more results exist.
|
||||
func (rslr RecordSetListResult) recordSetListResultPreparer() (*http.Request, error) {
|
||||
func (rslr RecordSetListResult) recordSetListResultPreparer(ctx context.Context) (*http.Request, error) {
|
||||
if rslr.NextLink == nil || len(to.String(rslr.NextLink)) < 1 {
|
||||
return nil, nil
|
||||
}
|
||||
return autorest.Prepare(&http.Request{},
|
||||
return autorest.Prepare((&http.Request{}).WithContext(ctx),
|
||||
autorest.AsJSON(),
|
||||
autorest.AsGet(),
|
||||
autorest.WithBaseURL(to.String(rslr.NextLink)))
|
||||
@@ -287,14 +328,24 @@ func (rslr RecordSetListResult) recordSetListResultPreparer() (*http.Request, er
|
||||
|
||||
// RecordSetListResultPage contains a page of RecordSet values.
|
||||
type RecordSetListResultPage struct {
|
||||
fn func(RecordSetListResult) (RecordSetListResult, error)
|
||||
fn func(context.Context, RecordSetListResult) (RecordSetListResult, error)
|
||||
rslr RecordSetListResult
|
||||
}
|
||||
|
||||
// Next advances to the next page of values. If there was an error making
|
||||
// NextWithContext advances to the next page of values. If there was an error making
|
||||
// the request the page does not advance and the error is returned.
|
||||
func (page *RecordSetListResultPage) Next() error {
|
||||
next, err := page.fn(page.rslr)
|
||||
func (page *RecordSetListResultPage) NextWithContext(ctx context.Context) (err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetListResultPage.NextWithContext")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if page.Response().Response.Response != nil {
|
||||
sc = page.Response().Response.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
next, err := page.fn(ctx, page.rslr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -302,6 +353,13 @@ func (page *RecordSetListResultPage) Next() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Next advances to the next page of values. If there was an error making
|
||||
// the request the page does not advance and the error is returned.
|
||||
// Deprecated: Use NextWithContext() instead.
|
||||
func (page *RecordSetListResultPage) Next() error {
|
||||
return page.NextWithContext(context.Background())
|
||||
}
|
||||
|
||||
// NotDone returns true if the page enumeration should be started or is not yet complete.
|
||||
func (page RecordSetListResultPage) NotDone() bool {
|
||||
return !page.rslr.IsEmpty()
|
||||
@@ -320,13 +378,18 @@ func (page RecordSetListResultPage) Values() []RecordSet {
|
||||
return *page.rslr.Value
|
||||
}
|
||||
|
||||
// Creates a new instance of the RecordSetListResultPage type.
|
||||
func NewRecordSetListResultPage(getNextPage func(context.Context, RecordSetListResult) (RecordSetListResult, error)) RecordSetListResultPage {
|
||||
return RecordSetListResultPage{fn: getNextPage}
|
||||
}
|
||||
|
||||
// RecordSetProperties represents the properties of the records in the record set.
|
||||
type RecordSetProperties struct {
|
||||
// Metadata - The metadata attached to the record set.
|
||||
Metadata map[string]*string `json:"metadata"`
|
||||
// TTL - The TTL (time-to-live) of the records in the record set.
|
||||
TTL *int64 `json:"TTL,omitempty"`
|
||||
// Fqdn - Fully qualified domain name of the record set.
|
||||
// Fqdn - READ-ONLY; Fully qualified domain name of the record set.
|
||||
Fqdn *string `json:"fqdn,omitempty"`
|
||||
// ARecords - The list of A records in the record set.
|
||||
ARecords *[]ARecord `json:"ARecords,omitempty"`
|
||||
@@ -359,9 +422,6 @@ func (rsp RecordSetProperties) MarshalJSON() ([]byte, error) {
|
||||
if rsp.TTL != nil {
|
||||
objectMap["TTL"] = rsp.TTL
|
||||
}
|
||||
if rsp.Fqdn != nil {
|
||||
objectMap["fqdn"] = rsp.Fqdn
|
||||
}
|
||||
if rsp.ARecords != nil {
|
||||
objectMap["ARecords"] = rsp.ARecords
|
||||
}
|
||||
@@ -401,39 +461,14 @@ type RecordSetUpdateParameters struct {
|
||||
RecordSet *RecordSet `json:"RecordSet,omitempty"`
|
||||
}
|
||||
|
||||
// Resource common properties of an Azure Resource Manager resource
|
||||
// Resource ...
|
||||
type Resource struct {
|
||||
// ID - Resource ID.
|
||||
// ID - READ-ONLY; Fully qualified resource Id for the resource. Ex - /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}
|
||||
ID *string `json:"id,omitempty"`
|
||||
// Name - Resource name.
|
||||
// Name - READ-ONLY; The name of the resource
|
||||
Name *string `json:"name,omitempty"`
|
||||
// Type - Resource type.
|
||||
// Type - READ-ONLY; The type of the resource. Ex- Microsoft.Compute/virtualMachines or Microsoft.Storage/storageAccounts.
|
||||
Type *string `json:"type,omitempty"`
|
||||
// Location - Resource location.
|
||||
Location *string `json:"location,omitempty"`
|
||||
// Tags - Resource tags.
|
||||
Tags map[string]*string `json:"tags"`
|
||||
}
|
||||
|
||||
// MarshalJSON is the custom marshaler for Resource.
|
||||
func (r Resource) MarshalJSON() ([]byte, error) {
|
||||
objectMap := make(map[string]interface{})
|
||||
if r.ID != nil {
|
||||
objectMap["id"] = r.ID
|
||||
}
|
||||
if r.Name != nil {
|
||||
objectMap["name"] = r.Name
|
||||
}
|
||||
if r.Type != nil {
|
||||
objectMap["type"] = r.Type
|
||||
}
|
||||
if r.Location != nil {
|
||||
objectMap["location"] = r.Location
|
||||
}
|
||||
if r.Tags != nil {
|
||||
objectMap["tags"] = r.Tags
|
||||
}
|
||||
return json.Marshal(objectMap)
|
||||
}
|
||||
|
||||
// SoaRecord an SOA record.
|
||||
@@ -472,6 +507,32 @@ type SubResource struct {
|
||||
ID *string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
// TrackedResource the resource model definition for a ARM tracked top level resource
|
||||
type TrackedResource struct {
|
||||
// Tags - Resource tags.
|
||||
Tags map[string]*string `json:"tags"`
|
||||
// Location - The geo-location where the resource lives
|
||||
Location *string `json:"location,omitempty"`
|
||||
// ID - READ-ONLY; Fully qualified resource Id for the resource. Ex - /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}
|
||||
ID *string `json:"id,omitempty"`
|
||||
// Name - READ-ONLY; The name of the resource
|
||||
Name *string `json:"name,omitempty"`
|
||||
// Type - READ-ONLY; The type of the resource. Ex- Microsoft.Compute/virtualMachines or Microsoft.Storage/storageAccounts.
|
||||
Type *string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// MarshalJSON is the custom marshaler for TrackedResource.
|
||||
func (tr TrackedResource) MarshalJSON() ([]byte, error) {
|
||||
objectMap := make(map[string]interface{})
|
||||
if tr.Tags != nil {
|
||||
objectMap["tags"] = tr.Tags
|
||||
}
|
||||
if tr.Location != nil {
|
||||
objectMap["location"] = tr.Location
|
||||
}
|
||||
return json.Marshal(objectMap)
|
||||
}
|
||||
|
||||
// TxtRecord a TXT record.
|
||||
type TxtRecord struct {
|
||||
// Value - The text value of this TXT record.
|
||||
@@ -485,16 +546,16 @@ type Zone struct {
|
||||
Etag *string `json:"etag,omitempty"`
|
||||
// ZoneProperties - The properties of the zone.
|
||||
*ZoneProperties `json:"properties,omitempty"`
|
||||
// ID - Resource ID.
|
||||
ID *string `json:"id,omitempty"`
|
||||
// Name - Resource name.
|
||||
Name *string `json:"name,omitempty"`
|
||||
// Type - Resource type.
|
||||
Type *string `json:"type,omitempty"`
|
||||
// Location - Resource location.
|
||||
Location *string `json:"location,omitempty"`
|
||||
// Tags - Resource tags.
|
||||
Tags map[string]*string `json:"tags"`
|
||||
// Location - The geo-location where the resource lives
|
||||
Location *string `json:"location,omitempty"`
|
||||
// ID - READ-ONLY; Fully qualified resource Id for the resource. Ex - /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}
|
||||
ID *string `json:"id,omitempty"`
|
||||
// Name - READ-ONLY; The name of the resource
|
||||
Name *string `json:"name,omitempty"`
|
||||
// Type - READ-ONLY; The type of the resource. Ex- Microsoft.Compute/virtualMachines or Microsoft.Storage/storageAccounts.
|
||||
Type *string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// MarshalJSON is the custom marshaler for Zone.
|
||||
@@ -506,21 +567,12 @@ func (z Zone) MarshalJSON() ([]byte, error) {
|
||||
if z.ZoneProperties != nil {
|
||||
objectMap["properties"] = z.ZoneProperties
|
||||
}
|
||||
if z.ID != nil {
|
||||
objectMap["id"] = z.ID
|
||||
}
|
||||
if z.Name != nil {
|
||||
objectMap["name"] = z.Name
|
||||
}
|
||||
if z.Type != nil {
|
||||
objectMap["type"] = z.Type
|
||||
if z.Tags != nil {
|
||||
objectMap["tags"] = z.Tags
|
||||
}
|
||||
if z.Location != nil {
|
||||
objectMap["location"] = z.Location
|
||||
}
|
||||
if z.Tags != nil {
|
||||
objectMap["tags"] = z.Tags
|
||||
}
|
||||
return json.Marshal(objectMap)
|
||||
}
|
||||
|
||||
@@ -551,6 +603,24 @@ func (z *Zone) UnmarshalJSON(body []byte) error {
|
||||
}
|
||||
z.ZoneProperties = &zoneProperties
|
||||
}
|
||||
case "tags":
|
||||
if v != nil {
|
||||
var tags map[string]*string
|
||||
err = json.Unmarshal(*v, &tags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
z.Tags = tags
|
||||
}
|
||||
case "location":
|
||||
if v != nil {
|
||||
var location string
|
||||
err = json.Unmarshal(*v, &location)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
z.Location = &location
|
||||
}
|
||||
case "id":
|
||||
if v != nil {
|
||||
var ID string
|
||||
@@ -578,24 +648,6 @@ func (z *Zone) UnmarshalJSON(body []byte) error {
|
||||
}
|
||||
z.Type = &typeVar
|
||||
}
|
||||
case "location":
|
||||
if v != nil {
|
||||
var location string
|
||||
err = json.Unmarshal(*v, &location)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
z.Location = &location
|
||||
}
|
||||
case "tags":
|
||||
if v != nil {
|
||||
var tags map[string]*string
|
||||
err = json.Unmarshal(*v, &tags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
z.Tags = tags
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -607,7 +659,7 @@ type ZoneListResult struct {
|
||||
autorest.Response `json:"-"`
|
||||
// Value - Information about the DNS zones.
|
||||
Value *[]Zone `json:"value,omitempty"`
|
||||
// NextLink - The continuation token for the next page of results.
|
||||
// NextLink - READ-ONLY; The continuation token for the next page of results.
|
||||
NextLink *string `json:"nextLink,omitempty"`
|
||||
}
|
||||
|
||||
@@ -617,14 +669,24 @@ type ZoneListResultIterator struct {
|
||||
page ZoneListResultPage
|
||||
}
|
||||
|
||||
// Next advances to the next value. If there was an error making
|
||||
// NextWithContext advances to the next value. If there was an error making
|
||||
// the request the iterator does not advance and the error is returned.
|
||||
func (iter *ZoneListResultIterator) Next() error {
|
||||
func (iter *ZoneListResultIterator) NextWithContext(ctx context.Context) (err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/ZoneListResultIterator.NextWithContext")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if iter.Response().Response.Response != nil {
|
||||
sc = iter.Response().Response.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
iter.i++
|
||||
if iter.i < len(iter.page.Values()) {
|
||||
return nil
|
||||
}
|
||||
err := iter.page.Next()
|
||||
err = iter.page.NextWithContext(ctx)
|
||||
if err != nil {
|
||||
iter.i--
|
||||
return err
|
||||
@@ -633,6 +695,13 @@ func (iter *ZoneListResultIterator) Next() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Next advances to the next value. If there was an error making
|
||||
// the request the iterator does not advance and the error is returned.
|
||||
// Deprecated: Use NextWithContext() instead.
|
||||
func (iter *ZoneListResultIterator) Next() error {
|
||||
return iter.NextWithContext(context.Background())
|
||||
}
|
||||
|
||||
// NotDone returns true if the enumeration should be started or is not yet complete.
|
||||
func (iter ZoneListResultIterator) NotDone() bool {
|
||||
return iter.page.NotDone() && iter.i < len(iter.page.Values())
|
||||
@@ -652,6 +721,11 @@ func (iter ZoneListResultIterator) Value() Zone {
|
||||
return iter.page.Values()[iter.i]
|
||||
}
|
||||
|
||||
// Creates a new instance of the ZoneListResultIterator type.
|
||||
func NewZoneListResultIterator(page ZoneListResultPage) ZoneListResultIterator {
|
||||
return ZoneListResultIterator{page: page}
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the ListResult contains no values.
|
||||
func (zlr ZoneListResult) IsEmpty() bool {
|
||||
return zlr.Value == nil || len(*zlr.Value) == 0
|
||||
@@ -659,11 +733,11 @@ func (zlr ZoneListResult) IsEmpty() bool {
|
||||
|
||||
// zoneListResultPreparer prepares a request to retrieve the next set of results.
|
||||
// It returns nil if no more results exist.
|
||||
func (zlr ZoneListResult) zoneListResultPreparer() (*http.Request, error) {
|
||||
func (zlr ZoneListResult) zoneListResultPreparer(ctx context.Context) (*http.Request, error) {
|
||||
if zlr.NextLink == nil || len(to.String(zlr.NextLink)) < 1 {
|
||||
return nil, nil
|
||||
}
|
||||
return autorest.Prepare(&http.Request{},
|
||||
return autorest.Prepare((&http.Request{}).WithContext(ctx),
|
||||
autorest.AsJSON(),
|
||||
autorest.AsGet(),
|
||||
autorest.WithBaseURL(to.String(zlr.NextLink)))
|
||||
@@ -671,14 +745,24 @@ func (zlr ZoneListResult) zoneListResultPreparer() (*http.Request, error) {
|
||||
|
||||
// ZoneListResultPage contains a page of Zone values.
|
||||
type ZoneListResultPage struct {
|
||||
fn func(ZoneListResult) (ZoneListResult, error)
|
||||
fn func(context.Context, ZoneListResult) (ZoneListResult, error)
|
||||
zlr ZoneListResult
|
||||
}
|
||||
|
||||
// Next advances to the next page of values. If there was an error making
|
||||
// NextWithContext advances to the next page of values. If there was an error making
|
||||
// the request the page does not advance and the error is returned.
|
||||
func (page *ZoneListResultPage) Next() error {
|
||||
next, err := page.fn(page.zlr)
|
||||
func (page *ZoneListResultPage) NextWithContext(ctx context.Context) (err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/ZoneListResultPage.NextWithContext")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if page.Response().Response.Response != nil {
|
||||
sc = page.Response().Response.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
next, err := page.fn(ctx, page.zlr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -686,6 +770,13 @@ func (page *ZoneListResultPage) Next() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Next advances to the next page of values. If there was an error making
|
||||
// the request the page does not advance and the error is returned.
|
||||
// Deprecated: Use NextWithContext() instead.
|
||||
func (page *ZoneListResultPage) Next() error {
|
||||
return page.NextWithContext(context.Background())
|
||||
}
|
||||
|
||||
// NotDone returns true if the page enumeration should be started or is not yet complete.
|
||||
func (page ZoneListResultPage) NotDone() bool {
|
||||
return !page.zlr.IsEmpty()
|
||||
@@ -704,60 +795,39 @@ func (page ZoneListResultPage) Values() []Zone {
|
||||
return *page.zlr.Value
|
||||
}
|
||||
|
||||
// Creates a new instance of the ZoneListResultPage type.
|
||||
func NewZoneListResultPage(getNextPage func(context.Context, ZoneListResult) (ZoneListResult, error)) ZoneListResultPage {
|
||||
return ZoneListResultPage{fn: getNextPage}
|
||||
}
|
||||
|
||||
// ZoneProperties represents the properties of the zone.
|
||||
type ZoneProperties struct {
|
||||
// MaxNumberOfRecordSets - The maximum number of record sets that can be created in this DNS zone. This is a read-only property and any attempt to set this value will be ignored.
|
||||
// MaxNumberOfRecordSets - READ-ONLY; The maximum number of record sets that can be created in this DNS zone. This is a read-only property and any attempt to set this value will be ignored.
|
||||
MaxNumberOfRecordSets *int64 `json:"maxNumberOfRecordSets,omitempty"`
|
||||
// NumberOfRecordSets - The current number of record sets in this DNS zone. This is a read-only property and any attempt to set this value will be ignored.
|
||||
// NumberOfRecordSets - READ-ONLY; The current number of record sets in this DNS zone. This is a read-only property and any attempt to set this value will be ignored.
|
||||
NumberOfRecordSets *int64 `json:"numberOfRecordSets,omitempty"`
|
||||
// NameServers - The name servers for this DNS zone. This is a read-only property and any attempt to set this value will be ignored.
|
||||
// NameServers - READ-ONLY; The name servers for this DNS zone. This is a read-only property and any attempt to set this value will be ignored.
|
||||
NameServers *[]string `json:"nameServers,omitempty"`
|
||||
}
|
||||
|
||||
// ZonesDeleteFuture an abstraction for monitoring and retrieving the results of a long-running operation.
|
||||
type ZonesDeleteFuture struct {
|
||||
azure.Future
|
||||
req *http.Request
|
||||
}
|
||||
|
||||
// Result returns the result of the asynchronous operation.
|
||||
// If the operation has not completed it will return an error.
|
||||
func (future ZonesDeleteFuture) Result(client ZonesClient) (ar autorest.Response, err error) {
|
||||
func (future *ZonesDeleteFuture) Result(client ZonesClient) (ar autorest.Response, err error) {
|
||||
var done bool
|
||||
done, err = future.Done(client)
|
||||
done, err = future.DoneWithContext(context.Background(), client)
|
||||
if err != nil {
|
||||
err = autorest.NewErrorWithError(err, "dns.ZonesDeleteFuture", "Result", future.Response(), "Polling failure")
|
||||
return
|
||||
}
|
||||
if !done {
|
||||
return ar, azure.NewAsyncOpIncompleteError("dns.ZonesDeleteFuture")
|
||||
}
|
||||
if future.PollingMethod() == azure.PollingLocation {
|
||||
ar, err = client.DeleteResponder(future.Response())
|
||||
if err != nil {
|
||||
err = autorest.NewErrorWithError(err, "dns.ZonesDeleteFuture", "Result", future.Response(), "Failure responding to request")
|
||||
}
|
||||
err = azure.NewAsyncOpIncompleteError("dns.ZonesDeleteFuture")
|
||||
return
|
||||
}
|
||||
var req *http.Request
|
||||
var resp *http.Response
|
||||
if future.PollingURL() != "" {
|
||||
req, err = http.NewRequest(http.MethodGet, future.PollingURL(), nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
req = autorest.ChangeToGet(future.req)
|
||||
}
|
||||
resp, err = autorest.SendWithSender(client, req,
|
||||
autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...))
|
||||
if err != nil {
|
||||
err = autorest.NewErrorWithError(err, "dns.ZonesDeleteFuture", "Result", resp, "Failure sending request")
|
||||
return
|
||||
}
|
||||
ar, err = client.DeleteResponder(resp)
|
||||
if err != nil {
|
||||
err = autorest.NewErrorWithError(err, "dns.ZonesDeleteFuture", "Result", resp, "Failure responding to request")
|
||||
}
|
||||
ar.Response = future.Response()
|
||||
return
|
||||
}
|
||||
|
235
vendor/github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2017-09-01/dns/recordsets.go
generated
vendored
235
vendor/github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2017-09-01/dns/recordsets.go
generated
vendored
@@ -21,6 +21,8 @@ import (
|
||||
"context"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/Azure/go-autorest/autorest/validation"
|
||||
"github.com/Azure/go-autorest/tracing"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@@ -40,15 +42,38 @@ func NewRecordSetsClientWithBaseURI(baseURI string, subscriptionID string) Recor
|
||||
}
|
||||
|
||||
// CreateOrUpdate creates or updates a record set within a DNS zone.
|
||||
//
|
||||
// resourceGroupName is the name of the resource group. zoneName is the name of the DNS zone (without a terminating
|
||||
// dot). relativeRecordSetName is the name of the record set, relative to the name of the zone. recordType is the
|
||||
// type of DNS record in this record set. Record sets of type SOA can be updated but not created (they are created
|
||||
// when the DNS zone is created). parameters is parameters supplied to the CreateOrUpdate operation. ifMatch is the
|
||||
// etag of the record set. Omit this value to always overwrite the current record set. Specify the last-seen etag
|
||||
// value to prevent accidentally overwritting any concurrent changes. ifNoneMatch is set to '*' to allow a new
|
||||
// record set to be created, but to prevent updating an existing record set. Other values will be ignored.
|
||||
// Parameters:
|
||||
// resourceGroupName - the name of the resource group. The name is case insensitive.
|
||||
// zoneName - the name of the DNS zone (without a terminating dot).
|
||||
// relativeRecordSetName - the name of the record set, relative to the name of the zone.
|
||||
// recordType - the type of DNS record in this record set. Record sets of type SOA can be updated but not
|
||||
// created (they are created when the DNS zone is created).
|
||||
// parameters - parameters supplied to the CreateOrUpdate operation.
|
||||
// ifMatch - the etag of the record set. Omit this value to always overwrite the current record set. Specify
|
||||
// the last-seen etag value to prevent accidentally overwriting any concurrent changes.
|
||||
// ifNoneMatch - set to '*' to allow a new record set to be created, but to prevent updating an existing record
|
||||
// set. Other values will be ignored.
|
||||
func (client RecordSetsClient) CreateOrUpdate(ctx context.Context, resourceGroupName string, zoneName string, relativeRecordSetName string, recordType RecordType, parameters RecordSet, ifMatch string, ifNoneMatch string) (result RecordSet, err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetsClient.CreateOrUpdate")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if result.Response.Response != nil {
|
||||
sc = result.Response.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
if err := validation.Validate([]validation.Validation{
|
||||
{TargetValue: resourceGroupName,
|
||||
Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}},
|
||||
{TargetValue: client.SubscriptionID,
|
||||
Constraints: []validation.Constraint{{Target: "client.SubscriptionID", Name: validation.MinLength, Rule: 1, Chain: nil}}}}); err != nil {
|
||||
return result, validation.NewError("dns.RecordSetsClient", "CreateOrUpdate", err.Error())
|
||||
}
|
||||
|
||||
req, err := client.CreateOrUpdatePreparer(ctx, resourceGroupName, zoneName, relativeRecordSetName, recordType, parameters, ifMatch, ifNoneMatch)
|
||||
if err != nil {
|
||||
err = autorest.NewErrorWithError(err, "dns.RecordSetsClient", "CreateOrUpdate", nil, "Failure preparing request")
|
||||
@@ -85,6 +110,9 @@ func (client RecordSetsClient) CreateOrUpdatePreparer(ctx context.Context, resou
|
||||
"api-version": APIVersion,
|
||||
}
|
||||
|
||||
parameters.ID = nil
|
||||
parameters.Name = nil
|
||||
parameters.Type = nil
|
||||
preparer := autorest.CreatePreparer(
|
||||
autorest.AsContentType("application/json; charset=utf-8"),
|
||||
autorest.AsPut(),
|
||||
@@ -124,13 +152,35 @@ func (client RecordSetsClient) CreateOrUpdateResponder(resp *http.Response) (res
|
||||
}
|
||||
|
||||
// Delete deletes a record set from a DNS zone. This operation cannot be undone.
|
||||
//
|
||||
// resourceGroupName is the name of the resource group. zoneName is the name of the DNS zone (without a terminating
|
||||
// dot). relativeRecordSetName is the name of the record set, relative to the name of the zone. recordType is the
|
||||
// type of DNS record in this record set. Record sets of type SOA cannot be deleted (they are deleted when the DNS
|
||||
// zone is deleted). ifMatch is the etag of the record set. Omit this value to always delete the current record
|
||||
// set. Specify the last-seen etag value to prevent accidentally deleting any concurrent changes.
|
||||
// Parameters:
|
||||
// resourceGroupName - the name of the resource group. The name is case insensitive.
|
||||
// zoneName - the name of the DNS zone (without a terminating dot).
|
||||
// relativeRecordSetName - the name of the record set, relative to the name of the zone.
|
||||
// recordType - the type of DNS record in this record set. Record sets of type SOA cannot be deleted (they are
|
||||
// deleted when the DNS zone is deleted).
|
||||
// ifMatch - the etag of the record set. Omit this value to always delete the current record set. Specify the
|
||||
// last-seen etag value to prevent accidentally deleting any concurrent changes.
|
||||
func (client RecordSetsClient) Delete(ctx context.Context, resourceGroupName string, zoneName string, relativeRecordSetName string, recordType RecordType, ifMatch string) (result autorest.Response, err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetsClient.Delete")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if result.Response != nil {
|
||||
sc = result.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
if err := validation.Validate([]validation.Validation{
|
||||
{TargetValue: resourceGroupName,
|
||||
Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}},
|
||||
{TargetValue: client.SubscriptionID,
|
||||
Constraints: []validation.Constraint{{Target: "client.SubscriptionID", Name: validation.MinLength, Rule: 1, Chain: nil}}}}); err != nil {
|
||||
return result, validation.NewError("dns.RecordSetsClient", "Delete", err.Error())
|
||||
}
|
||||
|
||||
req, err := client.DeletePreparer(ctx, resourceGroupName, zoneName, relativeRecordSetName, recordType, ifMatch)
|
||||
if err != nil {
|
||||
err = autorest.NewErrorWithError(err, "dns.RecordSetsClient", "Delete", nil, "Failure preparing request")
|
||||
@@ -199,11 +249,32 @@ func (client RecordSetsClient) DeleteResponder(resp *http.Response) (result auto
|
||||
}
|
||||
|
||||
// Get gets a record set.
|
||||
//
|
||||
// resourceGroupName is the name of the resource group. zoneName is the name of the DNS zone (without a terminating
|
||||
// dot). relativeRecordSetName is the name of the record set, relative to the name of the zone. recordType is the
|
||||
// type of DNS record in this record set.
|
||||
// Parameters:
|
||||
// resourceGroupName - the name of the resource group. The name is case insensitive.
|
||||
// zoneName - the name of the DNS zone (without a terminating dot).
|
||||
// relativeRecordSetName - the name of the record set, relative to the name of the zone.
|
||||
// recordType - the type of DNS record in this record set.
|
||||
func (client RecordSetsClient) Get(ctx context.Context, resourceGroupName string, zoneName string, relativeRecordSetName string, recordType RecordType) (result RecordSet, err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetsClient.Get")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if result.Response.Response != nil {
|
||||
sc = result.Response.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
if err := validation.Validate([]validation.Validation{
|
||||
{TargetValue: resourceGroupName,
|
||||
Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}},
|
||||
{TargetValue: client.SubscriptionID,
|
||||
Constraints: []validation.Constraint{{Target: "client.SubscriptionID", Name: validation.MinLength, Rule: 1, Chain: nil}}}}); err != nil {
|
||||
return result, validation.NewError("dns.RecordSetsClient", "Get", err.Error())
|
||||
}
|
||||
|
||||
req, err := client.GetPreparer(ctx, resourceGroupName, zoneName, relativeRecordSetName, recordType)
|
||||
if err != nil {
|
||||
err = autorest.NewErrorWithError(err, "dns.RecordSetsClient", "Get", nil, "Failure preparing request")
|
||||
@@ -269,13 +340,34 @@ func (client RecordSetsClient) GetResponder(resp *http.Response) (result RecordS
|
||||
}
|
||||
|
||||
// ListByDNSZone lists all record sets in a DNS zone.
|
||||
//
|
||||
// resourceGroupName is the name of the resource group. zoneName is the name of the DNS zone (without a terminating
|
||||
// dot). top is the maximum number of record sets to return. If not specified, returns up to 100 record sets.
|
||||
// recordsetnamesuffix is the suffix label of the record set name that has to be used to filter the record set
|
||||
// Parameters:
|
||||
// resourceGroupName - the name of the resource group. The name is case insensitive.
|
||||
// zoneName - the name of the DNS zone (without a terminating dot).
|
||||
// top - the maximum number of record sets to return. If not specified, returns up to 100 record sets.
|
||||
// recordsetnamesuffix - the suffix label of the record set name that has to be used to filter the record set
|
||||
// enumerations. If this parameter is specified, Enumeration will return only records that end with
|
||||
// .<recordSetNameSuffix>
|
||||
func (client RecordSetsClient) ListByDNSZone(ctx context.Context, resourceGroupName string, zoneName string, top *int32, recordsetnamesuffix string) (result RecordSetListResultPage, err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetsClient.ListByDNSZone")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if result.rslr.Response.Response != nil {
|
||||
sc = result.rslr.Response.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
if err := validation.Validate([]validation.Validation{
|
||||
{TargetValue: resourceGroupName,
|
||||
Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}},
|
||||
{TargetValue: client.SubscriptionID,
|
||||
Constraints: []validation.Constraint{{Target: "client.SubscriptionID", Name: validation.MinLength, Rule: 1, Chain: nil}}}}); err != nil {
|
||||
return result, validation.NewError("dns.RecordSetsClient", "ListByDNSZone", err.Error())
|
||||
}
|
||||
|
||||
result.fn = client.listByDNSZoneNextResults
|
||||
req, err := client.ListByDNSZonePreparer(ctx, resourceGroupName, zoneName, top, recordsetnamesuffix)
|
||||
if err != nil {
|
||||
@@ -346,8 +438,8 @@ func (client RecordSetsClient) ListByDNSZoneResponder(resp *http.Response) (resu
|
||||
}
|
||||
|
||||
// listByDNSZoneNextResults retrieves the next set of results, if any.
|
||||
func (client RecordSetsClient) listByDNSZoneNextResults(lastResults RecordSetListResult) (result RecordSetListResult, err error) {
|
||||
req, err := lastResults.recordSetListResultPreparer()
|
||||
func (client RecordSetsClient) listByDNSZoneNextResults(ctx context.Context, lastResults RecordSetListResult) (result RecordSetListResult, err error) {
|
||||
req, err := lastResults.recordSetListResultPreparer(ctx)
|
||||
if err != nil {
|
||||
return result, autorest.NewErrorWithError(err, "dns.RecordSetsClient", "listByDNSZoneNextResults", nil, "Failure preparing next results request")
|
||||
}
|
||||
@@ -368,18 +460,50 @@ func (client RecordSetsClient) listByDNSZoneNextResults(lastResults RecordSetLis
|
||||
|
||||
// ListByDNSZoneComplete enumerates all values, automatically crossing page boundaries as required.
|
||||
func (client RecordSetsClient) ListByDNSZoneComplete(ctx context.Context, resourceGroupName string, zoneName string, top *int32, recordsetnamesuffix string) (result RecordSetListResultIterator, err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetsClient.ListByDNSZone")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if result.Response().Response.Response != nil {
|
||||
sc = result.page.Response().Response.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
result.page, err = client.ListByDNSZone(ctx, resourceGroupName, zoneName, top, recordsetnamesuffix)
|
||||
return
|
||||
}
|
||||
|
||||
// ListByType lists the record sets of a specified type in a DNS zone.
|
||||
//
|
||||
// resourceGroupName is the name of the resource group. zoneName is the name of the DNS zone (without a terminating
|
||||
// dot). recordType is the type of record sets to enumerate. top is the maximum number of record sets to return. If
|
||||
// not specified, returns up to 100 record sets. recordsetnamesuffix is the suffix label of the record set name
|
||||
// that has to be used to filter the record set enumerations. If this parameter is specified, Enumeration will
|
||||
// return only records that end with .<recordSetNameSuffix>
|
||||
// Parameters:
|
||||
// resourceGroupName - the name of the resource group. The name is case insensitive.
|
||||
// zoneName - the name of the DNS zone (without a terminating dot).
|
||||
// recordType - the type of record sets to enumerate.
|
||||
// top - the maximum number of record sets to return. If not specified, returns up to 100 record sets.
|
||||
// recordsetnamesuffix - the suffix label of the record set name that has to be used to filter the record set
|
||||
// enumerations. If this parameter is specified, Enumeration will return only records that end with
|
||||
// .<recordSetNameSuffix>
|
||||
func (client RecordSetsClient) ListByType(ctx context.Context, resourceGroupName string, zoneName string, recordType RecordType, top *int32, recordsetnamesuffix string) (result RecordSetListResultPage, err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetsClient.ListByType")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if result.rslr.Response.Response != nil {
|
||||
sc = result.rslr.Response.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
if err := validation.Validate([]validation.Validation{
|
||||
{TargetValue: resourceGroupName,
|
||||
Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}},
|
||||
{TargetValue: client.SubscriptionID,
|
||||
Constraints: []validation.Constraint{{Target: "client.SubscriptionID", Name: validation.MinLength, Rule: 1, Chain: nil}}}}); err != nil {
|
||||
return result, validation.NewError("dns.RecordSetsClient", "ListByType", err.Error())
|
||||
}
|
||||
|
||||
result.fn = client.listByTypeNextResults
|
||||
req, err := client.ListByTypePreparer(ctx, resourceGroupName, zoneName, recordType, top, recordsetnamesuffix)
|
||||
if err != nil {
|
||||
@@ -451,8 +575,8 @@ func (client RecordSetsClient) ListByTypeResponder(resp *http.Response) (result
|
||||
}
|
||||
|
||||
// listByTypeNextResults retrieves the next set of results, if any.
|
||||
func (client RecordSetsClient) listByTypeNextResults(lastResults RecordSetListResult) (result RecordSetListResult, err error) {
|
||||
req, err := lastResults.recordSetListResultPreparer()
|
||||
func (client RecordSetsClient) listByTypeNextResults(ctx context.Context, lastResults RecordSetListResult) (result RecordSetListResult, err error) {
|
||||
req, err := lastResults.recordSetListResultPreparer(ctx)
|
||||
if err != nil {
|
||||
return result, autorest.NewErrorWithError(err, "dns.RecordSetsClient", "listByTypeNextResults", nil, "Failure preparing next results request")
|
||||
}
|
||||
@@ -473,18 +597,50 @@ func (client RecordSetsClient) listByTypeNextResults(lastResults RecordSetListRe
|
||||
|
||||
// ListByTypeComplete enumerates all values, automatically crossing page boundaries as required.
|
||||
func (client RecordSetsClient) ListByTypeComplete(ctx context.Context, resourceGroupName string, zoneName string, recordType RecordType, top *int32, recordsetnamesuffix string) (result RecordSetListResultIterator, err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetsClient.ListByType")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if result.Response().Response.Response != nil {
|
||||
sc = result.page.Response().Response.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
result.page, err = client.ListByType(ctx, resourceGroupName, zoneName, recordType, top, recordsetnamesuffix)
|
||||
return
|
||||
}
|
||||
|
||||
// Update updates a record set within a DNS zone.
|
||||
//
|
||||
// resourceGroupName is the name of the resource group. zoneName is the name of the DNS zone (without a terminating
|
||||
// dot). relativeRecordSetName is the name of the record set, relative to the name of the zone. recordType is the
|
||||
// type of DNS record in this record set. parameters is parameters supplied to the Update operation. ifMatch is the
|
||||
// etag of the record set. Omit this value to always overwrite the current record set. Specify the last-seen etag
|
||||
// value to prevent accidentally overwritting concurrent changes.
|
||||
// Parameters:
|
||||
// resourceGroupName - the name of the resource group. The name is case insensitive.
|
||||
// zoneName - the name of the DNS zone (without a terminating dot).
|
||||
// relativeRecordSetName - the name of the record set, relative to the name of the zone.
|
||||
// recordType - the type of DNS record in this record set.
|
||||
// parameters - parameters supplied to the Update operation.
|
||||
// ifMatch - the etag of the record set. Omit this value to always overwrite the current record set. Specify
|
||||
// the last-seen etag value to prevent accidentally overwriting concurrent changes.
|
||||
func (client RecordSetsClient) Update(ctx context.Context, resourceGroupName string, zoneName string, relativeRecordSetName string, recordType RecordType, parameters RecordSet, ifMatch string) (result RecordSet, err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetsClient.Update")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if result.Response.Response != nil {
|
||||
sc = result.Response.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
if err := validation.Validate([]validation.Validation{
|
||||
{TargetValue: resourceGroupName,
|
||||
Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}},
|
||||
{TargetValue: client.SubscriptionID,
|
||||
Constraints: []validation.Constraint{{Target: "client.SubscriptionID", Name: validation.MinLength, Rule: 1, Chain: nil}}}}); err != nil {
|
||||
return result, validation.NewError("dns.RecordSetsClient", "Update", err.Error())
|
||||
}
|
||||
|
||||
req, err := client.UpdatePreparer(ctx, resourceGroupName, zoneName, relativeRecordSetName, recordType, parameters, ifMatch)
|
||||
if err != nil {
|
||||
err = autorest.NewErrorWithError(err, "dns.RecordSetsClient", "Update", nil, "Failure preparing request")
|
||||
@@ -521,6 +677,9 @@ func (client RecordSetsClient) UpdatePreparer(ctx context.Context, resourceGroup
|
||||
"api-version": APIVersion,
|
||||
}
|
||||
|
||||
parameters.ID = nil
|
||||
parameters.Name = nil
|
||||
parameters.Type = nil
|
||||
preparer := autorest.CreatePreparer(
|
||||
autorest.AsContentType("application/json; charset=utf-8"),
|
||||
autorest.AsPatch(),
|
||||
|
175
vendor/github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2017-09-01/dns/zones.go
generated
vendored
175
vendor/github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2017-09-01/dns/zones.go
generated
vendored
@@ -21,6 +21,8 @@ import (
|
||||
"context"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/Azure/go-autorest/autorest/validation"
|
||||
"github.com/Azure/go-autorest/tracing"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@@ -40,13 +42,35 @@ func NewZonesClientWithBaseURI(baseURI string, subscriptionID string) ZonesClien
|
||||
}
|
||||
|
||||
// CreateOrUpdate creates or updates a DNS zone. Does not modify DNS records within the zone.
|
||||
//
|
||||
// resourceGroupName is the name of the resource group. zoneName is the name of the DNS zone (without a terminating
|
||||
// dot). parameters is parameters supplied to the CreateOrUpdate operation. ifMatch is the etag of the DNS zone.
|
||||
// Omit this value to always overwrite the current zone. Specify the last-seen etag value to prevent accidentally
|
||||
// overwritting any concurrent changes. ifNoneMatch is set to '*' to allow a new DNS zone to be created, but to
|
||||
// prevent updating an existing zone. Other values will be ignored.
|
||||
// Parameters:
|
||||
// resourceGroupName - the name of the resource group. The name is case insensitive.
|
||||
// zoneName - the name of the DNS zone (without a terminating dot).
|
||||
// parameters - parameters supplied to the CreateOrUpdate operation.
|
||||
// ifMatch - the etag of the DNS zone. Omit this value to always overwrite the current zone. Specify the
|
||||
// last-seen etag value to prevent accidentally overwriting any concurrent changes.
|
||||
// ifNoneMatch - set to '*' to allow a new DNS zone to be created, but to prevent updating an existing zone.
|
||||
// Other values will be ignored.
|
||||
func (client ZonesClient) CreateOrUpdate(ctx context.Context, resourceGroupName string, zoneName string, parameters Zone, ifMatch string, ifNoneMatch string) (result Zone, err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/ZonesClient.CreateOrUpdate")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if result.Response.Response != nil {
|
||||
sc = result.Response.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
if err := validation.Validate([]validation.Validation{
|
||||
{TargetValue: resourceGroupName,
|
||||
Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}},
|
||||
{TargetValue: client.SubscriptionID,
|
||||
Constraints: []validation.Constraint{{Target: "client.SubscriptionID", Name: validation.MinLength, Rule: 1, Chain: nil}}}}); err != nil {
|
||||
return result, validation.NewError("dns.ZonesClient", "CreateOrUpdate", err.Error())
|
||||
}
|
||||
|
||||
req, err := client.CreateOrUpdatePreparer(ctx, resourceGroupName, zoneName, parameters, ifMatch, ifNoneMatch)
|
||||
if err != nil {
|
||||
err = autorest.NewErrorWithError(err, "dns.ZonesClient", "CreateOrUpdate", nil, "Failure preparing request")
|
||||
@@ -121,11 +145,32 @@ func (client ZonesClient) CreateOrUpdateResponder(resp *http.Response) (result Z
|
||||
|
||||
// Delete deletes a DNS zone. WARNING: All DNS records in the zone will also be deleted. This operation cannot be
|
||||
// undone.
|
||||
//
|
||||
// resourceGroupName is the name of the resource group. zoneName is the name of the DNS zone (without a terminating
|
||||
// dot). ifMatch is the etag of the DNS zone. Omit this value to always delete the current zone. Specify the
|
||||
// last-seen etag value to prevent accidentally deleting any concurrent changes.
|
||||
// Parameters:
|
||||
// resourceGroupName - the name of the resource group. The name is case insensitive.
|
||||
// zoneName - the name of the DNS zone (without a terminating dot).
|
||||
// ifMatch - the etag of the DNS zone. Omit this value to always delete the current zone. Specify the last-seen
|
||||
// etag value to prevent accidentally deleting any concurrent changes.
|
||||
func (client ZonesClient) Delete(ctx context.Context, resourceGroupName string, zoneName string, ifMatch string) (result ZonesDeleteFuture, err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/ZonesClient.Delete")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if result.Response() != nil {
|
||||
sc = result.Response().StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
if err := validation.Validate([]validation.Validation{
|
||||
{TargetValue: resourceGroupName,
|
||||
Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}},
|
||||
{TargetValue: client.SubscriptionID,
|
||||
Constraints: []validation.Constraint{{Target: "client.SubscriptionID", Name: validation.MinLength, Rule: 1, Chain: nil}}}}); err != nil {
|
||||
return result, validation.NewError("dns.ZonesClient", "Delete", err.Error())
|
||||
}
|
||||
|
||||
req, err := client.DeletePreparer(ctx, resourceGroupName, zoneName, ifMatch)
|
||||
if err != nil {
|
||||
err = autorest.NewErrorWithError(err, "dns.ZonesClient", "Delete", nil, "Failure preparing request")
|
||||
@@ -169,15 +214,13 @@ func (client ZonesClient) DeletePreparer(ctx context.Context, resourceGroupName
|
||||
// DeleteSender sends the Delete request. The method will close the
|
||||
// http.Response Body if it receives an error.
|
||||
func (client ZonesClient) DeleteSender(req *http.Request) (future ZonesDeleteFuture, err error) {
|
||||
sender := autorest.DecorateSender(client, azure.DoRetryWithRegistration(client.Client))
|
||||
future.Future = azure.NewFuture(req)
|
||||
future.req = req
|
||||
_, err = future.Done(sender)
|
||||
var resp *http.Response
|
||||
resp, err = autorest.SendWithSender(client, req,
|
||||
azure.DoRetryWithRegistration(client.Client))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = autorest.Respond(future.Response(),
|
||||
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusAccepted, http.StatusNoContent))
|
||||
future.Future, err = azure.NewFutureFromResponse(resp)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -194,10 +237,30 @@ func (client ZonesClient) DeleteResponder(resp *http.Response) (result autorest.
|
||||
}
|
||||
|
||||
// Get gets a DNS zone. Retrieves the zone properties, but not the record sets within the zone.
|
||||
//
|
||||
// resourceGroupName is the name of the resource group. zoneName is the name of the DNS zone (without a terminating
|
||||
// dot).
|
||||
// Parameters:
|
||||
// resourceGroupName - the name of the resource group. The name is case insensitive.
|
||||
// zoneName - the name of the DNS zone (without a terminating dot).
|
||||
func (client ZonesClient) Get(ctx context.Context, resourceGroupName string, zoneName string) (result Zone, err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/ZonesClient.Get")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if result.Response.Response != nil {
|
||||
sc = result.Response.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
if err := validation.Validate([]validation.Validation{
|
||||
{TargetValue: resourceGroupName,
|
||||
Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}},
|
||||
{TargetValue: client.SubscriptionID,
|
||||
Constraints: []validation.Constraint{{Target: "client.SubscriptionID", Name: validation.MinLength, Rule: 1, Chain: nil}}}}); err != nil {
|
||||
return result, validation.NewError("dns.ZonesClient", "Get", err.Error())
|
||||
}
|
||||
|
||||
req, err := client.GetPreparer(ctx, resourceGroupName, zoneName)
|
||||
if err != nil {
|
||||
err = autorest.NewErrorWithError(err, "dns.ZonesClient", "Get", nil, "Failure preparing request")
|
||||
@@ -261,9 +324,25 @@ func (client ZonesClient) GetResponder(resp *http.Response) (result Zone, err er
|
||||
}
|
||||
|
||||
// List lists the DNS zones in all resource groups in a subscription.
|
||||
//
|
||||
// top is the maximum number of DNS zones to return. If not specified, returns up to 100 zones.
|
||||
// Parameters:
|
||||
// top - the maximum number of DNS zones to return. If not specified, returns up to 100 zones.
|
||||
func (client ZonesClient) List(ctx context.Context, top *int32) (result ZoneListResultPage, err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/ZonesClient.List")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if result.zlr.Response.Response != nil {
|
||||
sc = result.zlr.Response.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
if err := validation.Validate([]validation.Validation{
|
||||
{TargetValue: client.SubscriptionID,
|
||||
Constraints: []validation.Constraint{{Target: "client.SubscriptionID", Name: validation.MinLength, Rule: 1, Chain: nil}}}}); err != nil {
|
||||
return result, validation.NewError("dns.ZonesClient", "List", err.Error())
|
||||
}
|
||||
|
||||
result.fn = client.listNextResults
|
||||
req, err := client.ListPreparer(ctx, top)
|
||||
if err != nil {
|
||||
@@ -329,8 +408,8 @@ func (client ZonesClient) ListResponder(resp *http.Response) (result ZoneListRes
|
||||
}
|
||||
|
||||
// listNextResults retrieves the next set of results, if any.
|
||||
func (client ZonesClient) listNextResults(lastResults ZoneListResult) (result ZoneListResult, err error) {
|
||||
req, err := lastResults.zoneListResultPreparer()
|
||||
func (client ZonesClient) listNextResults(ctx context.Context, lastResults ZoneListResult) (result ZoneListResult, err error) {
|
||||
req, err := lastResults.zoneListResultPreparer(ctx)
|
||||
if err != nil {
|
||||
return result, autorest.NewErrorWithError(err, "dns.ZonesClient", "listNextResults", nil, "Failure preparing next results request")
|
||||
}
|
||||
@@ -351,15 +430,45 @@ func (client ZonesClient) listNextResults(lastResults ZoneListResult) (result Zo
|
||||
|
||||
// ListComplete enumerates all values, automatically crossing page boundaries as required.
|
||||
func (client ZonesClient) ListComplete(ctx context.Context, top *int32) (result ZoneListResultIterator, err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/ZonesClient.List")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if result.Response().Response.Response != nil {
|
||||
sc = result.page.Response().Response.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
result.page, err = client.List(ctx, top)
|
||||
return
|
||||
}
|
||||
|
||||
// ListByResourceGroup lists the DNS zones within a resource group.
|
||||
//
|
||||
// resourceGroupName is the name of the resource group. top is the maximum number of record sets to return. If not
|
||||
// specified, returns up to 100 record sets.
|
||||
// Parameters:
|
||||
// resourceGroupName - the name of the resource group. The name is case insensitive.
|
||||
// top - the maximum number of record sets to return. If not specified, returns up to 100 record sets.
|
||||
func (client ZonesClient) ListByResourceGroup(ctx context.Context, resourceGroupName string, top *int32) (result ZoneListResultPage, err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/ZonesClient.ListByResourceGroup")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if result.zlr.Response.Response != nil {
|
||||
sc = result.zlr.Response.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
if err := validation.Validate([]validation.Validation{
|
||||
{TargetValue: resourceGroupName,
|
||||
Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil},
|
||||
{Target: "resourceGroupName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}},
|
||||
{TargetValue: client.SubscriptionID,
|
||||
Constraints: []validation.Constraint{{Target: "client.SubscriptionID", Name: validation.MinLength, Rule: 1, Chain: nil}}}}); err != nil {
|
||||
return result, validation.NewError("dns.ZonesClient", "ListByResourceGroup", err.Error())
|
||||
}
|
||||
|
||||
result.fn = client.listByResourceGroupNextResults
|
||||
req, err := client.ListByResourceGroupPreparer(ctx, resourceGroupName, top)
|
||||
if err != nil {
|
||||
@@ -426,8 +535,8 @@ func (client ZonesClient) ListByResourceGroupResponder(resp *http.Response) (res
|
||||
}
|
||||
|
||||
// listByResourceGroupNextResults retrieves the next set of results, if any.
|
||||
func (client ZonesClient) listByResourceGroupNextResults(lastResults ZoneListResult) (result ZoneListResult, err error) {
|
||||
req, err := lastResults.zoneListResultPreparer()
|
||||
func (client ZonesClient) listByResourceGroupNextResults(ctx context.Context, lastResults ZoneListResult) (result ZoneListResult, err error) {
|
||||
req, err := lastResults.zoneListResultPreparer(ctx)
|
||||
if err != nil {
|
||||
return result, autorest.NewErrorWithError(err, "dns.ZonesClient", "listByResourceGroupNextResults", nil, "Failure preparing next results request")
|
||||
}
|
||||
@@ -448,6 +557,16 @@ func (client ZonesClient) listByResourceGroupNextResults(lastResults ZoneListRes
|
||||
|
||||
// ListByResourceGroupComplete enumerates all values, automatically crossing page boundaries as required.
|
||||
func (client ZonesClient) ListByResourceGroupComplete(ctx context.Context, resourceGroupName string, top *int32) (result ZoneListResultIterator, err error) {
|
||||
if tracing.IsEnabled() {
|
||||
ctx = tracing.StartSpan(ctx, fqdn+"/ZonesClient.ListByResourceGroup")
|
||||
defer func() {
|
||||
sc := -1
|
||||
if result.Response().Response.Response != nil {
|
||||
sc = result.page.Response().Response.Response.StatusCode
|
||||
}
|
||||
tracing.EndSpan(ctx, sc, err)
|
||||
}()
|
||||
}
|
||||
result.page, err = client.ListByResourceGroup(ctx, resourceGroupName, top)
|
||||
return
|
||||
}
|
||||
|
2
vendor/github.com/Azure/azure-sdk-for-go/version/version.go
generated
vendored
2
vendor/github.com/Azure/azure-sdk-for-go/version/version.go
generated
vendored
@@ -18,4 +18,4 @@ package version
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
// Number contains the semantic version of this SDK.
|
||||
const Number = "v15.0.1"
|
||||
const Number = "v31.2.0"
|
||||
|
88
vendor/github.com/Azure/go-autorest/autorest/adal/config.go
generated
vendored
88
vendor/github.com/Azure/go-autorest/autorest/adal/config.go
generated
vendored
@@ -15,21 +15,22 @@ package adal
|
||||
// limitations under the License.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const (
|
||||
activeDirectoryAPIVersion = "1.0"
|
||||
activeDirectoryEndpointTemplate = "%s/oauth2/%s%s"
|
||||
)
|
||||
|
||||
// OAuthConfig represents the endpoints needed
|
||||
// in OAuth operations
|
||||
type OAuthConfig struct {
|
||||
AuthorityEndpoint url.URL
|
||||
AuthorizeEndpoint url.URL
|
||||
TokenEndpoint url.URL
|
||||
DeviceCodeEndpoint url.URL
|
||||
AuthorityEndpoint url.URL `json:"authorityEndpoint"`
|
||||
AuthorizeEndpoint url.URL `json:"authorizeEndpoint"`
|
||||
TokenEndpoint url.URL `json:"tokenEndpoint"`
|
||||
DeviceCodeEndpoint url.URL `json:"deviceCodeEndpoint"`
|
||||
}
|
||||
|
||||
// IsZero returns true if the OAuthConfig object is zero-initialized.
|
||||
@@ -46,11 +47,24 @@ func validateStringParam(param, name string) error {
|
||||
|
||||
// NewOAuthConfig returns an OAuthConfig with tenant specific urls
|
||||
func NewOAuthConfig(activeDirectoryEndpoint, tenantID string) (*OAuthConfig, error) {
|
||||
apiVer := "1.0"
|
||||
return NewOAuthConfigWithAPIVersion(activeDirectoryEndpoint, tenantID, &apiVer)
|
||||
}
|
||||
|
||||
// NewOAuthConfigWithAPIVersion returns an OAuthConfig with tenant specific urls.
|
||||
// If apiVersion is not nil the "api-version" query parameter will be appended to the endpoint URLs with the specified value.
|
||||
func NewOAuthConfigWithAPIVersion(activeDirectoryEndpoint, tenantID string, apiVersion *string) (*OAuthConfig, error) {
|
||||
if err := validateStringParam(activeDirectoryEndpoint, "activeDirectoryEndpoint"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
api := ""
|
||||
// it's legal for tenantID to be empty so don't validate it
|
||||
const activeDirectoryEndpointTemplate = "%s/oauth2/%s?api-version=%s"
|
||||
if apiVersion != nil {
|
||||
if err := validateStringParam(*apiVersion, "apiVersion"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
api = fmt.Sprintf("?api-version=%s", *apiVersion)
|
||||
}
|
||||
u, err := url.Parse(activeDirectoryEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -59,15 +73,15 @@ func NewOAuthConfig(activeDirectoryEndpoint, tenantID string) (*OAuthConfig, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authorizeURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "authorize", activeDirectoryAPIVersion))
|
||||
authorizeURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "authorize", api))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokenURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "token", activeDirectoryAPIVersion))
|
||||
tokenURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "token", api))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deviceCodeURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "devicecode", activeDirectoryAPIVersion))
|
||||
deviceCodeURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "devicecode", api))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -79,3 +93,59 @@ func NewOAuthConfig(activeDirectoryEndpoint, tenantID string) (*OAuthConfig, err
|
||||
DeviceCodeEndpoint: *deviceCodeURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MultiTenantOAuthConfig provides endpoints for primary and aulixiary tenant IDs.
|
||||
type MultiTenantOAuthConfig interface {
|
||||
PrimaryTenant() *OAuthConfig
|
||||
AuxiliaryTenants() []*OAuthConfig
|
||||
}
|
||||
|
||||
// OAuthOptions contains optional OAuthConfig creation arguments.
|
||||
type OAuthOptions struct {
|
||||
APIVersion string
|
||||
}
|
||||
|
||||
func (c OAuthOptions) apiVersion() string {
|
||||
if c.APIVersion != "" {
|
||||
return fmt.Sprintf("?api-version=%s", c.APIVersion)
|
||||
}
|
||||
return "1.0"
|
||||
}
|
||||
|
||||
// NewMultiTenantOAuthConfig creates an object that support multitenant OAuth configuration.
|
||||
// See https://docs.microsoft.com/en-us/azure/azure-resource-manager/authenticate-multi-tenant for more information.
|
||||
func NewMultiTenantOAuthConfig(activeDirectoryEndpoint, primaryTenantID string, auxiliaryTenantIDs []string, options OAuthOptions) (MultiTenantOAuthConfig, error) {
|
||||
if len(auxiliaryTenantIDs) == 0 || len(auxiliaryTenantIDs) > 3 {
|
||||
return nil, errors.New("must specify one to three auxiliary tenants")
|
||||
}
|
||||
mtCfg := multiTenantOAuthConfig{
|
||||
cfgs: make([]*OAuthConfig, len(auxiliaryTenantIDs)+1),
|
||||
}
|
||||
apiVer := options.apiVersion()
|
||||
pri, err := NewOAuthConfigWithAPIVersion(activeDirectoryEndpoint, primaryTenantID, &apiVer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create OAuthConfig for primary tenant: %v", err)
|
||||
}
|
||||
mtCfg.cfgs[0] = pri
|
||||
for i := range auxiliaryTenantIDs {
|
||||
aux, err := NewOAuthConfig(activeDirectoryEndpoint, auxiliaryTenantIDs[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create OAuthConfig for tenant '%s': %v", auxiliaryTenantIDs[i], err)
|
||||
}
|
||||
mtCfg.cfgs[i+1] = aux
|
||||
}
|
||||
return mtCfg, nil
|
||||
}
|
||||
|
||||
type multiTenantOAuthConfig struct {
|
||||
// first config in the slice is the primary tenant
|
||||
cfgs []*OAuthConfig
|
||||
}
|
||||
|
||||
func (m multiTenantOAuthConfig) PrimaryTenant() *OAuthConfig {
|
||||
return m.cfgs[0]
|
||||
}
|
||||
|
||||
func (m multiTenantOAuthConfig) AuxiliaryTenants() []*OAuthConfig {
|
||||
return m.cfgs[1:]
|
||||
}
|
||||
|
2
vendor/github.com/Azure/go-autorest/autorest/adal/sender.go
generated
vendored
2
vendor/github.com/Azure/go-autorest/autorest/adal/sender.go
generated
vendored
@@ -38,7 +38,7 @@ func (sf SenderFunc) Do(r *http.Request) (*http.Response, error) {
|
||||
return sf(r)
|
||||
}
|
||||
|
||||
// SendDecorator takes and possibily decorates, by wrapping, a Sender. Decorators may affect the
|
||||
// SendDecorator takes and possibly decorates, by wrapping, a Sender. Decorators may affect the
|
||||
// http.Request and pass it along or, first, pass the http.Request along then react to the
|
||||
// http.Response result.
|
||||
type SendDecorator func(Sender) Sender
|
||||
|
577
vendor/github.com/Azure/go-autorest/autorest/adal/token.go
generated
vendored
577
vendor/github.com/Azure/go-autorest/autorest/adal/token.go
generated
vendored
@@ -15,22 +15,26 @@ package adal
|
||||
// limitations under the License.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/date"
|
||||
"github.com/Azure/go-autorest/tracing"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
@@ -57,6 +61,9 @@ const (
|
||||
|
||||
// msiEndpoint is the well known endpoint for getting MSI authentications tokens
|
||||
msiEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token"
|
||||
|
||||
// the default number of attempts to refresh an MSI authentication token
|
||||
defaultMaxMSIRefreshAttempts = 5
|
||||
)
|
||||
|
||||
// OAuthTokenProvider is an interface which should be implemented by an access token retriever
|
||||
@@ -64,6 +71,12 @@ type OAuthTokenProvider interface {
|
||||
OAuthToken() string
|
||||
}
|
||||
|
||||
// MultitenantOAuthTokenProvider provides tokens used for multi-tenant authorization.
|
||||
type MultitenantOAuthTokenProvider interface {
|
||||
PrimaryOAuthToken() string
|
||||
AuxiliaryOAuthTokens() []string
|
||||
}
|
||||
|
||||
// TokenRefreshError is an interface used by errors returned during token refresh.
|
||||
type TokenRefreshError interface {
|
||||
error
|
||||
@@ -77,23 +90,39 @@ type Refresher interface {
|
||||
EnsureFresh() error
|
||||
}
|
||||
|
||||
// RefresherWithContext is an interface for token refresh functionality
|
||||
type RefresherWithContext interface {
|
||||
RefreshWithContext(ctx context.Context) error
|
||||
RefreshExchangeWithContext(ctx context.Context, resource string) error
|
||||
EnsureFreshWithContext(ctx context.Context) error
|
||||
}
|
||||
|
||||
// TokenRefreshCallback is the type representing callbacks that will be called after
|
||||
// a successful token refresh
|
||||
type TokenRefreshCallback func(Token) error
|
||||
|
||||
// Token encapsulates the access token used to authorize Azure requests.
|
||||
// https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-oauth2-client-creds-grant-flow#service-to-service-access-token-response
|
||||
type Token struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
|
||||
ExpiresIn string `json:"expires_in"`
|
||||
ExpiresOn string `json:"expires_on"`
|
||||
NotBefore string `json:"not_before"`
|
||||
ExpiresIn json.Number `json:"expires_in"`
|
||||
ExpiresOn json.Number `json:"expires_on"`
|
||||
NotBefore json.Number `json:"not_before"`
|
||||
|
||||
Resource string `json:"resource"`
|
||||
Type string `json:"token_type"`
|
||||
}
|
||||
|
||||
func newToken() Token {
|
||||
return Token{
|
||||
ExpiresIn: "0",
|
||||
ExpiresOn: "0",
|
||||
NotBefore: "0",
|
||||
}
|
||||
}
|
||||
|
||||
// IsZero returns true if the token object is zero-initialized.
|
||||
func (t Token) IsZero() bool {
|
||||
return t == Token{}
|
||||
@@ -101,12 +130,12 @@ func (t Token) IsZero() bool {
|
||||
|
||||
// Expires returns the time.Time when the Token expires.
|
||||
func (t Token) Expires() time.Time {
|
||||
s, err := strconv.Atoi(t.ExpiresOn)
|
||||
s, err := t.ExpiresOn.Float64()
|
||||
if err != nil {
|
||||
s = -3600
|
||||
}
|
||||
|
||||
expiration := date.NewUnixTimeFromSeconds(float64(s))
|
||||
expiration := date.NewUnixTimeFromSeconds(s)
|
||||
|
||||
return time.Time(expiration).UTC()
|
||||
}
|
||||
@@ -127,6 +156,12 @@ func (t *Token) OAuthToken() string {
|
||||
return t.AccessToken
|
||||
}
|
||||
|
||||
// ServicePrincipalSecret is an interface that allows various secret mechanism to fill the form
|
||||
// that is submitted when acquiring an oAuth token.
|
||||
type ServicePrincipalSecret interface {
|
||||
SetAuthenticationValues(spt *ServicePrincipalToken, values *url.Values) error
|
||||
}
|
||||
|
||||
// ServicePrincipalNoSecret represents a secret type that contains no secret
|
||||
// meaning it is not valid for fetching a fresh token. This is used by Manual
|
||||
type ServicePrincipalNoSecret struct {
|
||||
@@ -138,15 +173,19 @@ func (noSecret *ServicePrincipalNoSecret) SetAuthenticationValues(spt *ServicePr
|
||||
return fmt.Errorf("Manually created ServicePrincipalToken does not contain secret material to retrieve a new access token")
|
||||
}
|
||||
|
||||
// ServicePrincipalSecret is an interface that allows various secret mechanism to fill the form
|
||||
// that is submitted when acquiring an oAuth token.
|
||||
type ServicePrincipalSecret interface {
|
||||
SetAuthenticationValues(spt *ServicePrincipalToken, values *url.Values) error
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (noSecret ServicePrincipalNoSecret) MarshalJSON() ([]byte, error) {
|
||||
type tokenType struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
return json.Marshal(tokenType{
|
||||
Type: "ServicePrincipalNoSecret",
|
||||
})
|
||||
}
|
||||
|
||||
// ServicePrincipalTokenSecret implements ServicePrincipalSecret for client_secret type authorization.
|
||||
type ServicePrincipalTokenSecret struct {
|
||||
ClientSecret string
|
||||
ClientSecret string `json:"value"`
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
|
||||
@@ -156,49 +195,24 @@ func (tokenSecret *ServicePrincipalTokenSecret) SetAuthenticationValues(spt *Ser
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (tokenSecret ServicePrincipalTokenSecret) MarshalJSON() ([]byte, error) {
|
||||
type tokenType struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
return json.Marshal(tokenType{
|
||||
Type: "ServicePrincipalTokenSecret",
|
||||
Value: tokenSecret.ClientSecret,
|
||||
})
|
||||
}
|
||||
|
||||
// ServicePrincipalCertificateSecret implements ServicePrincipalSecret for generic RSA cert auth with signed JWTs.
|
||||
type ServicePrincipalCertificateSecret struct {
|
||||
Certificate *x509.Certificate
|
||||
PrivateKey *rsa.PrivateKey
|
||||
}
|
||||
|
||||
// ServicePrincipalMSISecret implements ServicePrincipalSecret for machines running the MSI Extension.
|
||||
type ServicePrincipalMSISecret struct {
|
||||
}
|
||||
|
||||
// ServicePrincipalUsernamePasswordSecret implements ServicePrincipalSecret for username and password auth.
|
||||
type ServicePrincipalUsernamePasswordSecret struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// ServicePrincipalAuthorizationCodeSecret implements ServicePrincipalSecret for authorization code auth.
|
||||
type ServicePrincipalAuthorizationCodeSecret struct {
|
||||
ClientSecret string
|
||||
AuthorizationCode string
|
||||
RedirectURI string
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
|
||||
func (secret *ServicePrincipalAuthorizationCodeSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
|
||||
v.Set("code", secret.AuthorizationCode)
|
||||
v.Set("client_secret", secret.ClientSecret)
|
||||
v.Set("redirect_uri", secret.RedirectURI)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
|
||||
func (secret *ServicePrincipalUsernamePasswordSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
|
||||
v.Set("username", secret.Username)
|
||||
v.Set("password", secret.Password)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
|
||||
func (msiSecret *ServicePrincipalMSISecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignJwt returns the JWT signed with the certificate's private key.
|
||||
func (secret *ServicePrincipalCertificateSecret) SignJwt(spt *ServicePrincipalToken) (string, error) {
|
||||
hasher := sha1.New()
|
||||
@@ -218,10 +232,12 @@ func (secret *ServicePrincipalCertificateSecret) SignJwt(spt *ServicePrincipalTo
|
||||
|
||||
token := jwt.New(jwt.SigningMethodRS256)
|
||||
token.Header["x5t"] = thumbprint
|
||||
x5c := []string{base64.StdEncoding.EncodeToString(secret.Certificate.Raw)}
|
||||
token.Header["x5c"] = x5c
|
||||
token.Claims = jwt.MapClaims{
|
||||
"aud": spt.oauthConfig.TokenEndpoint.String(),
|
||||
"iss": spt.clientID,
|
||||
"sub": spt.clientID,
|
||||
"aud": spt.inner.OauthConfig.TokenEndpoint.String(),
|
||||
"iss": spt.inner.ClientID,
|
||||
"sub": spt.inner.ClientID,
|
||||
"jti": base64.URLEncoding.EncodeToString(jti),
|
||||
"nbf": time.Now().Unix(),
|
||||
"exp": time.Now().Add(time.Hour * 24).Unix(),
|
||||
@@ -244,19 +260,156 @@ func (secret *ServicePrincipalCertificateSecret) SetAuthenticationValues(spt *Se
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (secret ServicePrincipalCertificateSecret) MarshalJSON() ([]byte, error) {
|
||||
return nil, errors.New("marshalling ServicePrincipalCertificateSecret is not supported")
|
||||
}
|
||||
|
||||
// ServicePrincipalMSISecret implements ServicePrincipalSecret for machines running the MSI Extension.
|
||||
type ServicePrincipalMSISecret struct {
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
|
||||
func (msiSecret *ServicePrincipalMSISecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (msiSecret ServicePrincipalMSISecret) MarshalJSON() ([]byte, error) {
|
||||
return nil, errors.New("marshalling ServicePrincipalMSISecret is not supported")
|
||||
}
|
||||
|
||||
// ServicePrincipalUsernamePasswordSecret implements ServicePrincipalSecret for username and password auth.
|
||||
type ServicePrincipalUsernamePasswordSecret struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
|
||||
func (secret *ServicePrincipalUsernamePasswordSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
|
||||
v.Set("username", secret.Username)
|
||||
v.Set("password", secret.Password)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (secret ServicePrincipalUsernamePasswordSecret) MarshalJSON() ([]byte, error) {
|
||||
type tokenType struct {
|
||||
Type string `json:"type"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
return json.Marshal(tokenType{
|
||||
Type: "ServicePrincipalUsernamePasswordSecret",
|
||||
Username: secret.Username,
|
||||
Password: secret.Password,
|
||||
})
|
||||
}
|
||||
|
||||
// ServicePrincipalAuthorizationCodeSecret implements ServicePrincipalSecret for authorization code auth.
|
||||
type ServicePrincipalAuthorizationCodeSecret struct {
|
||||
ClientSecret string `json:"value"`
|
||||
AuthorizationCode string `json:"authCode"`
|
||||
RedirectURI string `json:"redirect"`
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
|
||||
func (secret *ServicePrincipalAuthorizationCodeSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
|
||||
v.Set("code", secret.AuthorizationCode)
|
||||
v.Set("client_secret", secret.ClientSecret)
|
||||
v.Set("redirect_uri", secret.RedirectURI)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (secret ServicePrincipalAuthorizationCodeSecret) MarshalJSON() ([]byte, error) {
|
||||
type tokenType struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
AuthCode string `json:"authCode"`
|
||||
Redirect string `json:"redirect"`
|
||||
}
|
||||
return json.Marshal(tokenType{
|
||||
Type: "ServicePrincipalAuthorizationCodeSecret",
|
||||
Value: secret.ClientSecret,
|
||||
AuthCode: secret.AuthorizationCode,
|
||||
Redirect: secret.RedirectURI,
|
||||
})
|
||||
}
|
||||
|
||||
// ServicePrincipalToken encapsulates a Token created for a Service Principal.
|
||||
type ServicePrincipalToken struct {
|
||||
token Token
|
||||
secret ServicePrincipalSecret
|
||||
oauthConfig OAuthConfig
|
||||
clientID string
|
||||
resource string
|
||||
autoRefresh bool
|
||||
refreshLock *sync.RWMutex
|
||||
refreshWithin time.Duration
|
||||
sender Sender
|
||||
|
||||
inner servicePrincipalToken
|
||||
refreshLock *sync.RWMutex
|
||||
sender Sender
|
||||
refreshCallbacks []TokenRefreshCallback
|
||||
// MaxMSIRefreshAttempts is the maximum number of attempts to refresh an MSI token.
|
||||
MaxMSIRefreshAttempts int
|
||||
}
|
||||
|
||||
// MarshalTokenJSON returns the marshalled inner token.
|
||||
func (spt ServicePrincipalToken) MarshalTokenJSON() ([]byte, error) {
|
||||
return json.Marshal(spt.inner.Token)
|
||||
}
|
||||
|
||||
// SetRefreshCallbacks replaces any existing refresh callbacks with the specified callbacks.
|
||||
func (spt *ServicePrincipalToken) SetRefreshCallbacks(callbacks []TokenRefreshCallback) {
|
||||
spt.refreshCallbacks = callbacks
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (spt ServicePrincipalToken) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(spt.inner)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (spt *ServicePrincipalToken) UnmarshalJSON(data []byte) error {
|
||||
// need to determine the token type
|
||||
raw := map[string]interface{}{}
|
||||
err := json.Unmarshal(data, &raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secret := raw["secret"].(map[string]interface{})
|
||||
switch secret["type"] {
|
||||
case "ServicePrincipalNoSecret":
|
||||
spt.inner.Secret = &ServicePrincipalNoSecret{}
|
||||
case "ServicePrincipalTokenSecret":
|
||||
spt.inner.Secret = &ServicePrincipalTokenSecret{}
|
||||
case "ServicePrincipalCertificateSecret":
|
||||
return errors.New("unmarshalling ServicePrincipalCertificateSecret is not supported")
|
||||
case "ServicePrincipalMSISecret":
|
||||
return errors.New("unmarshalling ServicePrincipalMSISecret is not supported")
|
||||
case "ServicePrincipalUsernamePasswordSecret":
|
||||
spt.inner.Secret = &ServicePrincipalUsernamePasswordSecret{}
|
||||
case "ServicePrincipalAuthorizationCodeSecret":
|
||||
spt.inner.Secret = &ServicePrincipalAuthorizationCodeSecret{}
|
||||
default:
|
||||
return fmt.Errorf("unrecognized token type '%s'", secret["type"])
|
||||
}
|
||||
err = json.Unmarshal(data, &spt.inner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Don't override the refreshLock or the sender if those have been already set.
|
||||
if spt.refreshLock == nil {
|
||||
spt.refreshLock = &sync.RWMutex{}
|
||||
}
|
||||
if spt.sender == nil {
|
||||
spt.sender = &http.Client{Transport: tracing.Transport}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// internal type used for marshalling/unmarshalling
|
||||
type servicePrincipalToken struct {
|
||||
Token Token `json:"token"`
|
||||
Secret ServicePrincipalSecret `json:"secret"`
|
||||
OauthConfig OAuthConfig `json:"oauth"`
|
||||
ClientID string `json:"clientID"`
|
||||
Resource string `json:"resource"`
|
||||
AutoRefresh bool `json:"autoRefresh"`
|
||||
RefreshWithin time.Duration `json:"refreshWithin"`
|
||||
}
|
||||
|
||||
func validateOAuthConfig(oac OAuthConfig) error {
|
||||
@@ -281,14 +434,17 @@ func NewServicePrincipalTokenWithSecret(oauthConfig OAuthConfig, id string, reso
|
||||
return nil, fmt.Errorf("parameter 'secret' cannot be nil")
|
||||
}
|
||||
spt := &ServicePrincipalToken{
|
||||
oauthConfig: oauthConfig,
|
||||
secret: secret,
|
||||
clientID: id,
|
||||
resource: resource,
|
||||
autoRefresh: true,
|
||||
inner: servicePrincipalToken{
|
||||
Token: newToken(),
|
||||
OauthConfig: oauthConfig,
|
||||
Secret: secret,
|
||||
ClientID: id,
|
||||
Resource: resource,
|
||||
AutoRefresh: true,
|
||||
RefreshWithin: defaultRefresh,
|
||||
},
|
||||
refreshLock: &sync.RWMutex{},
|
||||
refreshWithin: defaultRefresh,
|
||||
sender: &http.Client{},
|
||||
sender: &http.Client{Transport: tracing.Transport},
|
||||
refreshCallbacks: callbacks,
|
||||
}
|
||||
return spt, nil
|
||||
@@ -318,7 +474,39 @@ func NewServicePrincipalTokenFromManualToken(oauthConfig OAuthConfig, clientID s
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spt.token = token
|
||||
spt.inner.Token = token
|
||||
|
||||
return spt, nil
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenFromManualTokenSecret creates a ServicePrincipalToken using the supplied token and secret
|
||||
func NewServicePrincipalTokenFromManualTokenSecret(oauthConfig OAuthConfig, clientID string, resource string, token Token, secret ServicePrincipalSecret, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
if err := validateOAuthConfig(oauthConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(clientID, "clientID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(resource, "resource"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if secret == nil {
|
||||
return nil, fmt.Errorf("parameter 'secret' cannot be nil")
|
||||
}
|
||||
if token.IsZero() {
|
||||
return nil, fmt.Errorf("parameter 'token' cannot be zero-initialized")
|
||||
}
|
||||
spt, err := NewServicePrincipalTokenWithSecret(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
resource,
|
||||
secret,
|
||||
callbacks...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spt.inner.Token = token
|
||||
|
||||
return spt, nil
|
||||
}
|
||||
@@ -486,20 +674,24 @@ func newServicePrincipalTokenFromMSI(msiEndpoint, resource string, userAssignedI
|
||||
msiEndpointURL.RawQuery = v.Encode()
|
||||
|
||||
spt := &ServicePrincipalToken{
|
||||
oauthConfig: OAuthConfig{
|
||||
TokenEndpoint: *msiEndpointURL,
|
||||
inner: servicePrincipalToken{
|
||||
Token: newToken(),
|
||||
OauthConfig: OAuthConfig{
|
||||
TokenEndpoint: *msiEndpointURL,
|
||||
},
|
||||
Secret: &ServicePrincipalMSISecret{},
|
||||
Resource: resource,
|
||||
AutoRefresh: true,
|
||||
RefreshWithin: defaultRefresh,
|
||||
},
|
||||
secret: &ServicePrincipalMSISecret{},
|
||||
resource: resource,
|
||||
autoRefresh: true,
|
||||
refreshLock: &sync.RWMutex{},
|
||||
refreshWithin: defaultRefresh,
|
||||
sender: &http.Client{},
|
||||
refreshCallbacks: callbacks,
|
||||
refreshLock: &sync.RWMutex{},
|
||||
sender: &http.Client{Transport: tracing.Transport},
|
||||
refreshCallbacks: callbacks,
|
||||
MaxMSIRefreshAttempts: defaultMaxMSIRefreshAttempts,
|
||||
}
|
||||
|
||||
if userAssignedID != nil {
|
||||
spt.clientID = *userAssignedID
|
||||
spt.inner.ClientID = *userAssignedID
|
||||
}
|
||||
|
||||
return spt, nil
|
||||
@@ -528,12 +720,18 @@ func newTokenRefreshError(message string, resp *http.Response) TokenRefreshError
|
||||
// EnsureFresh will refresh the token if it will expire within the refresh window (as set by
|
||||
// RefreshWithin) and autoRefresh flag is on. This method is safe for concurrent use.
|
||||
func (spt *ServicePrincipalToken) EnsureFresh() error {
|
||||
if spt.autoRefresh && spt.token.WillExpireIn(spt.refreshWithin) {
|
||||
return spt.EnsureFreshWithContext(context.Background())
|
||||
}
|
||||
|
||||
// EnsureFreshWithContext will refresh the token if it will expire within the refresh window (as set by
|
||||
// RefreshWithin) and autoRefresh flag is on. This method is safe for concurrent use.
|
||||
func (spt *ServicePrincipalToken) EnsureFreshWithContext(ctx context.Context) error {
|
||||
if spt.inner.AutoRefresh && spt.inner.Token.WillExpireIn(spt.inner.RefreshWithin) {
|
||||
// take the write lock then check to see if the token was already refreshed
|
||||
spt.refreshLock.Lock()
|
||||
defer spt.refreshLock.Unlock()
|
||||
if spt.token.WillExpireIn(spt.refreshWithin) {
|
||||
return spt.refreshInternal(spt.resource)
|
||||
if spt.inner.Token.WillExpireIn(spt.inner.RefreshWithin) {
|
||||
return spt.refreshInternal(ctx, spt.inner.Resource)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -543,7 +741,7 @@ func (spt *ServicePrincipalToken) EnsureFresh() error {
|
||||
func (spt *ServicePrincipalToken) InvokeRefreshCallbacks(token Token) error {
|
||||
if spt.refreshCallbacks != nil {
|
||||
for _, callback := range spt.refreshCallbacks {
|
||||
err := callback(spt.token)
|
||||
err := callback(spt.inner.Token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("adal: TokenRefreshCallback handler failed. Error = '%v'", err)
|
||||
}
|
||||
@@ -555,21 +753,33 @@ func (spt *ServicePrincipalToken) InvokeRefreshCallbacks(token Token) error {
|
||||
// Refresh obtains a fresh token for the Service Principal.
|
||||
// This method is not safe for concurrent use and should be syncrhonized.
|
||||
func (spt *ServicePrincipalToken) Refresh() error {
|
||||
return spt.RefreshWithContext(context.Background())
|
||||
}
|
||||
|
||||
// RefreshWithContext obtains a fresh token for the Service Principal.
|
||||
// This method is not safe for concurrent use and should be syncrhonized.
|
||||
func (spt *ServicePrincipalToken) RefreshWithContext(ctx context.Context) error {
|
||||
spt.refreshLock.Lock()
|
||||
defer spt.refreshLock.Unlock()
|
||||
return spt.refreshInternal(spt.resource)
|
||||
return spt.refreshInternal(ctx, spt.inner.Resource)
|
||||
}
|
||||
|
||||
// RefreshExchange refreshes the token, but for a different resource.
|
||||
// This method is not safe for concurrent use and should be syncrhonized.
|
||||
func (spt *ServicePrincipalToken) RefreshExchange(resource string) error {
|
||||
return spt.RefreshExchangeWithContext(context.Background(), resource)
|
||||
}
|
||||
|
||||
// RefreshExchangeWithContext refreshes the token, but for a different resource.
|
||||
// This method is not safe for concurrent use and should be syncrhonized.
|
||||
func (spt *ServicePrincipalToken) RefreshExchangeWithContext(ctx context.Context, resource string) error {
|
||||
spt.refreshLock.Lock()
|
||||
defer spt.refreshLock.Unlock()
|
||||
return spt.refreshInternal(resource)
|
||||
return spt.refreshInternal(ctx, resource)
|
||||
}
|
||||
|
||||
func (spt *ServicePrincipalToken) getGrantType() string {
|
||||
switch spt.secret.(type) {
|
||||
switch spt.inner.Secret.(type) {
|
||||
case *ServicePrincipalUsernamePasswordSecret:
|
||||
return OAuthGrantTypeUserPass
|
||||
case *ServicePrincipalAuthorizationCodeSecret:
|
||||
@@ -587,23 +797,32 @@ func isIMDS(u url.URL) bool {
|
||||
return u.Host == imds.Host && u.Path == imds.Path
|
||||
}
|
||||
|
||||
func (spt *ServicePrincipalToken) refreshInternal(resource string) error {
|
||||
req, err := http.NewRequest(http.MethodPost, spt.oauthConfig.TokenEndpoint.String(), nil)
|
||||
func (spt *ServicePrincipalToken) refreshInternal(ctx context.Context, resource string) error {
|
||||
req, err := http.NewRequest(http.MethodPost, spt.inner.OauthConfig.TokenEndpoint.String(), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("adal: Failed to build the refresh request. Error = '%v'", err)
|
||||
}
|
||||
|
||||
if !isIMDS(spt.oauthConfig.TokenEndpoint) {
|
||||
req.Header.Add("User-Agent", UserAgent())
|
||||
req = req.WithContext(ctx)
|
||||
if !isIMDS(spt.inner.OauthConfig.TokenEndpoint) {
|
||||
v := url.Values{}
|
||||
v.Set("client_id", spt.clientID)
|
||||
v.Set("client_id", spt.inner.ClientID)
|
||||
v.Set("resource", resource)
|
||||
|
||||
if spt.token.RefreshToken != "" {
|
||||
if spt.inner.Token.RefreshToken != "" {
|
||||
v.Set("grant_type", OAuthGrantTypeRefreshToken)
|
||||
v.Set("refresh_token", spt.token.RefreshToken)
|
||||
v.Set("refresh_token", spt.inner.Token.RefreshToken)
|
||||
// web apps must specify client_secret when refreshing tokens
|
||||
// see https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-code#refreshing-the-access-tokens
|
||||
if spt.getGrantType() == OAuthGrantTypeAuthorizationCode {
|
||||
err := spt.inner.Secret.SetAuthenticationValues(spt, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
v.Set("grant_type", spt.getGrantType())
|
||||
err := spt.secret.SetAuthenticationValues(spt, &v)
|
||||
err := spt.inner.Secret.SetAuthenticationValues(spt, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -616,14 +835,19 @@ func (spt *ServicePrincipalToken) refreshInternal(resource string) error {
|
||||
req.Body = body
|
||||
}
|
||||
|
||||
if _, ok := spt.secret.(*ServicePrincipalMSISecret); ok {
|
||||
if _, ok := spt.inner.Secret.(*ServicePrincipalMSISecret); ok {
|
||||
req.Method = http.MethodGet
|
||||
req.Header.Set(metadataHeader, "true")
|
||||
}
|
||||
|
||||
resp, err := spt.sender.Do(req)
|
||||
var resp *http.Response
|
||||
if isIMDS(spt.inner.OauthConfig.TokenEndpoint) {
|
||||
resp, err = retryForIMDS(spt.sender, req, spt.MaxMSIRefreshAttempts)
|
||||
} else {
|
||||
resp, err = spt.sender.Do(req)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("adal: Failed to execute the refresh request. Error = '%v'", err)
|
||||
return newTokenRefreshError(fmt.Sprintf("adal: Failed to execute the refresh request. Error = '%v'", err), nil)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
@@ -631,11 +855,15 @@ func (spt *ServicePrincipalToken) refreshInternal(resource string) error {
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if err != nil {
|
||||
return newTokenRefreshError(fmt.Sprintf("adal: Refresh request failed. Status Code = '%d'. Failed reading response body", resp.StatusCode), resp)
|
||||
return newTokenRefreshError(fmt.Sprintf("adal: Refresh request failed. Status Code = '%d'. Failed reading response body: %v", resp.StatusCode, err), resp)
|
||||
}
|
||||
return newTokenRefreshError(fmt.Sprintf("adal: Refresh request failed. Status Code = '%d'. Response body: %s", resp.StatusCode, string(rb)), resp)
|
||||
}
|
||||
|
||||
// for the following error cases don't return a TokenRefreshError. the operation succeeded
|
||||
// but some transient failure happened during deserialization. by returning a generic error
|
||||
// the retry logic will kick in (we don't retry on TokenRefreshError).
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("adal: Failed to read a new service principal token during refresh. Error = '%v'", err)
|
||||
}
|
||||
@@ -648,20 +876,99 @@ func (spt *ServicePrincipalToken) refreshInternal(resource string) error {
|
||||
return fmt.Errorf("adal: Failed to unmarshal the service principal token during refresh. Error = '%v' JSON = '%s'", err, string(rb))
|
||||
}
|
||||
|
||||
spt.token = token
|
||||
spt.inner.Token = token
|
||||
|
||||
return spt.InvokeRefreshCallbacks(token)
|
||||
}
|
||||
|
||||
// retry logic specific to retrieving a token from the IMDS endpoint
|
||||
func retryForIMDS(sender Sender, req *http.Request, maxAttempts int) (resp *http.Response, err error) {
|
||||
// copied from client.go due to circular dependency
|
||||
retries := []int{
|
||||
http.StatusRequestTimeout, // 408
|
||||
http.StatusTooManyRequests, // 429
|
||||
http.StatusInternalServerError, // 500
|
||||
http.StatusBadGateway, // 502
|
||||
http.StatusServiceUnavailable, // 503
|
||||
http.StatusGatewayTimeout, // 504
|
||||
}
|
||||
// extra retry status codes specific to IMDS
|
||||
retries = append(retries,
|
||||
http.StatusNotFound,
|
||||
http.StatusGone,
|
||||
// all remaining 5xx
|
||||
http.StatusNotImplemented,
|
||||
http.StatusHTTPVersionNotSupported,
|
||||
http.StatusVariantAlsoNegotiates,
|
||||
http.StatusInsufficientStorage,
|
||||
http.StatusLoopDetected,
|
||||
http.StatusNotExtended,
|
||||
http.StatusNetworkAuthenticationRequired)
|
||||
|
||||
// see https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/how-to-use-vm-token#retry-guidance
|
||||
|
||||
const maxDelay time.Duration = 60 * time.Second
|
||||
|
||||
attempt := 0
|
||||
delay := time.Duration(0)
|
||||
|
||||
for attempt < maxAttempts {
|
||||
resp, err = sender.Do(req)
|
||||
// retry on temporary network errors, e.g. transient network failures.
|
||||
// if we don't receive a response then assume we can't connect to the
|
||||
// endpoint so we're likely not running on an Azure VM so don't retry.
|
||||
if (err != nil && !isTemporaryNetworkError(err)) || resp == nil || resp.StatusCode == http.StatusOK || !containsInt(retries, resp.StatusCode) {
|
||||
return
|
||||
}
|
||||
|
||||
// perform exponential backoff with a cap.
|
||||
// must increment attempt before calculating delay.
|
||||
attempt++
|
||||
// the base value of 2 is the "delta backoff" as specified in the guidance doc
|
||||
delay += (time.Duration(math.Pow(2, float64(attempt))) * time.Second)
|
||||
if delay > maxDelay {
|
||||
delay = maxDelay
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(delay):
|
||||
// intentionally left blank
|
||||
case <-req.Context().Done():
|
||||
err = req.Context().Err()
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// returns true if the specified error is a temporary network error or false if it's not.
|
||||
// if the error doesn't implement the net.Error interface the return value is true.
|
||||
func isTemporaryNetworkError(err error) bool {
|
||||
if netErr, ok := err.(net.Error); !ok || (ok && netErr.Temporary()) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// returns true if slice ints contains the value n
|
||||
func containsInt(ints []int, n int) bool {
|
||||
for _, i := range ints {
|
||||
if i == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetAutoRefresh enables or disables automatic refreshing of stale tokens.
|
||||
func (spt *ServicePrincipalToken) SetAutoRefresh(autoRefresh bool) {
|
||||
spt.autoRefresh = autoRefresh
|
||||
spt.inner.AutoRefresh = autoRefresh
|
||||
}
|
||||
|
||||
// SetRefreshWithin sets the interval within which if the token will expire, EnsureFresh will
|
||||
// refresh the token.
|
||||
func (spt *ServicePrincipalToken) SetRefreshWithin(d time.Duration) {
|
||||
spt.refreshWithin = d
|
||||
spt.inner.RefreshWithin = d
|
||||
return
|
||||
}
|
||||
|
||||
@@ -673,12 +980,76 @@ func (spt *ServicePrincipalToken) SetSender(s Sender) { spt.sender = s }
|
||||
func (spt *ServicePrincipalToken) OAuthToken() string {
|
||||
spt.refreshLock.RLock()
|
||||
defer spt.refreshLock.RUnlock()
|
||||
return spt.token.OAuthToken()
|
||||
return spt.inner.Token.OAuthToken()
|
||||
}
|
||||
|
||||
// Token returns a copy of the current token.
|
||||
func (spt *ServicePrincipalToken) Token() Token {
|
||||
spt.refreshLock.RLock()
|
||||
defer spt.refreshLock.RUnlock()
|
||||
return spt.token
|
||||
return spt.inner.Token
|
||||
}
|
||||
|
||||
// MultiTenantServicePrincipalToken contains tokens for multi-tenant authorization.
|
||||
type MultiTenantServicePrincipalToken struct {
|
||||
PrimaryToken *ServicePrincipalToken
|
||||
AuxiliaryTokens []*ServicePrincipalToken
|
||||
}
|
||||
|
||||
// PrimaryOAuthToken returns the primary authorization token.
|
||||
func (mt *MultiTenantServicePrincipalToken) PrimaryOAuthToken() string {
|
||||
return mt.PrimaryToken.OAuthToken()
|
||||
}
|
||||
|
||||
// AuxiliaryOAuthTokens returns one to three auxiliary authorization tokens.
|
||||
func (mt *MultiTenantServicePrincipalToken) AuxiliaryOAuthTokens() []string {
|
||||
tokens := make([]string, len(mt.AuxiliaryTokens))
|
||||
for i := range mt.AuxiliaryTokens {
|
||||
tokens[i] = mt.AuxiliaryTokens[i].OAuthToken()
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
// EnsureFreshWithContext will refresh the token if it will expire within the refresh window (as set by
|
||||
// RefreshWithin) and autoRefresh flag is on. This method is safe for concurrent use.
|
||||
func (mt *MultiTenantServicePrincipalToken) EnsureFreshWithContext(ctx context.Context) error {
|
||||
if err := mt.PrimaryToken.EnsureFreshWithContext(ctx); err != nil {
|
||||
return fmt.Errorf("failed to refresh primary token: %v", err)
|
||||
}
|
||||
for _, aux := range mt.AuxiliaryTokens {
|
||||
if err := aux.EnsureFreshWithContext(ctx); err != nil {
|
||||
return fmt.Errorf("failed to refresh auxiliary token: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewMultiTenantServicePrincipalToken creates a new MultiTenantServicePrincipalToken with the specified credentials and resource.
|
||||
func NewMultiTenantServicePrincipalToken(multiTenantCfg MultiTenantOAuthConfig, clientID string, secret string, resource string) (*MultiTenantServicePrincipalToken, error) {
|
||||
if err := validateStringParam(clientID, "clientID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(secret, "secret"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(resource, "resource"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
auxTenants := multiTenantCfg.AuxiliaryTenants()
|
||||
m := MultiTenantServicePrincipalToken{
|
||||
AuxiliaryTokens: make([]*ServicePrincipalToken, len(auxTenants)),
|
||||
}
|
||||
primary, err := NewServicePrincipalToken(*multiTenantCfg.PrimaryTenant(), clientID, secret, resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create SPT for primary tenant: %v", err)
|
||||
}
|
||||
m.PrimaryToken = primary
|
||||
for i := range auxTenants {
|
||||
aux, err := NewServicePrincipalToken(*auxTenants[i], clientID, secret, resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create SPT for auxiliary tenant: %v", err)
|
||||
}
|
||||
m.AuxiliaryTokens[i] = aux
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
45
vendor/github.com/Azure/go-autorest/autorest/adal/version.go
generated
vendored
Normal file
45
vendor/github.com/Azure/go-autorest/autorest/adal/version.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
package adal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
const number = "v1.0.0"
|
||||
|
||||
var (
|
||||
ua = fmt.Sprintf("Go/%s (%s-%s) go-autorest/adal/%s",
|
||||
runtime.Version(),
|
||||
runtime.GOARCH,
|
||||
runtime.GOOS,
|
||||
number,
|
||||
)
|
||||
)
|
||||
|
||||
// UserAgent returns a string containing the Go version, system architecture and OS, and the adal version.
|
||||
func UserAgent() string {
|
||||
return ua
|
||||
}
|
||||
|
||||
// AddToUserAgent adds an extension to the current user agent
|
||||
func AddToUserAgent(extension string) error {
|
||||
if extension != "" {
|
||||
ua = fmt.Sprintf("%s %s", ua, extension)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Extension was empty, User Agent remained as '%s'", ua)
|
||||
}
|
103
vendor/github.com/Azure/go-autorest/autorest/authorization.go
generated
vendored
103
vendor/github.com/Azure/go-autorest/autorest/authorization.go
generated
vendored
@@ -15,12 +15,14 @@ package autorest
|
||||
// limitations under the License.
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/tracing"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -30,6 +32,8 @@ const (
|
||||
apiKeyAuthorizerHeader = "Ocp-Apim-Subscription-Key"
|
||||
bingAPISdkHeader = "X-BingApis-SDK-Client"
|
||||
golangBingAPISdkHeaderValue = "Go-SDK"
|
||||
authorization = "Authorization"
|
||||
basic = "Basic"
|
||||
)
|
||||
|
||||
// Authorizer is the interface that provides a PrepareDecorator used to supply request
|
||||
@@ -68,7 +72,7 @@ func NewAPIKeyAuthorizer(headers map[string]interface{}, queryParameters map[str
|
||||
return &APIKeyAuthorizer{headers: headers, queryParameters: queryParameters}
|
||||
}
|
||||
|
||||
// WithAuthorization returns a PrepareDecorator that adds an HTTP headers and Query Paramaters
|
||||
// WithAuthorization returns a PrepareDecorator that adds an HTTP headers and Query Parameters.
|
||||
func (aka *APIKeyAuthorizer) WithAuthorization() PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return DecoratePreparer(p, WithHeaders(aka.headers), WithQueryParameters(aka.queryParameters))
|
||||
@@ -113,17 +117,19 @@ func (ba *BearerAuthorizer) WithAuthorization() PrepareDecorator {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err == nil {
|
||||
refresher, ok := ba.tokenProvider.(adal.Refresher)
|
||||
if ok {
|
||||
err := refresher.EnsureFresh()
|
||||
if err != nil {
|
||||
var resp *http.Response
|
||||
if tokError, ok := err.(adal.TokenRefreshError); ok {
|
||||
resp = tokError.Response()
|
||||
}
|
||||
return r, NewErrorWithError(err, "azure.BearerAuthorizer", "WithAuthorization", resp,
|
||||
"Failed to refresh the Token for request to %s", r.URL)
|
||||
// the ordering is important here, prefer RefresherWithContext if available
|
||||
if refresher, ok := ba.tokenProvider.(adal.RefresherWithContext); ok {
|
||||
err = refresher.EnsureFreshWithContext(r.Context())
|
||||
} else if refresher, ok := ba.tokenProvider.(adal.Refresher); ok {
|
||||
err = refresher.EnsureFresh()
|
||||
}
|
||||
if err != nil {
|
||||
var resp *http.Response
|
||||
if tokError, ok := err.(adal.TokenRefreshError); ok {
|
||||
resp = tokError.Response()
|
||||
}
|
||||
return r, NewErrorWithError(err, "azure.BearerAuthorizer", "WithAuthorization", resp,
|
||||
"Failed to refresh the Token for request to %s", r.URL)
|
||||
}
|
||||
return Prepare(r, WithHeader(headerAuthorization, fmt.Sprintf("Bearer %s", ba.tokenProvider.OAuthToken())))
|
||||
}
|
||||
@@ -145,7 +151,7 @@ type BearerAuthorizerCallback struct {
|
||||
// is invoked when the HTTP request is submitted.
|
||||
func NewBearerAuthorizerCallback(sender Sender, callback BearerAuthorizerCallbackFunc) *BearerAuthorizerCallback {
|
||||
if sender == nil {
|
||||
sender = &http.Client{}
|
||||
sender = &http.Client{Transport: tracing.Transport}
|
||||
}
|
||||
return &BearerAuthorizerCallback{sender: sender, callback: callback}
|
||||
}
|
||||
@@ -255,3 +261,76 @@ func (egta EventGridKeyAuthorizer) WithAuthorization() PrepareDecorator {
|
||||
}
|
||||
return NewAPIKeyAuthorizerWithHeaders(headers).WithAuthorization()
|
||||
}
|
||||
|
||||
// BasicAuthorizer implements basic HTTP authorization by adding the Authorization HTTP header
|
||||
// with the value "Basic <TOKEN>" where <TOKEN> is a base64-encoded username:password tuple.
|
||||
type BasicAuthorizer struct {
|
||||
userName string
|
||||
password string
|
||||
}
|
||||
|
||||
// NewBasicAuthorizer creates a new BasicAuthorizer with the specified username and password.
|
||||
func NewBasicAuthorizer(userName, password string) *BasicAuthorizer {
|
||||
return &BasicAuthorizer{
|
||||
userName: userName,
|
||||
password: password,
|
||||
}
|
||||
}
|
||||
|
||||
// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose
|
||||
// value is "Basic " followed by the base64-encoded username:password tuple.
|
||||
func (ba *BasicAuthorizer) WithAuthorization() PrepareDecorator {
|
||||
headers := make(map[string]interface{})
|
||||
headers[authorization] = basic + " " + base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", ba.userName, ba.password)))
|
||||
|
||||
return NewAPIKeyAuthorizerWithHeaders(headers).WithAuthorization()
|
||||
}
|
||||
|
||||
// MultiTenantServicePrincipalTokenAuthorizer provides authentication across tenants.
|
||||
type MultiTenantServicePrincipalTokenAuthorizer interface {
|
||||
WithAuthorization() PrepareDecorator
|
||||
}
|
||||
|
||||
// NewMultiTenantServicePrincipalTokenAuthorizer crates a BearerAuthorizer using the given token provider
|
||||
func NewMultiTenantServicePrincipalTokenAuthorizer(tp adal.MultitenantOAuthTokenProvider) MultiTenantServicePrincipalTokenAuthorizer {
|
||||
return &multiTenantSPTAuthorizer{tp: tp}
|
||||
}
|
||||
|
||||
type multiTenantSPTAuthorizer struct {
|
||||
tp adal.MultitenantOAuthTokenProvider
|
||||
}
|
||||
|
||||
// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header using the
|
||||
// primary token along with the auxiliary authorization header using the auxiliary tokens.
|
||||
//
|
||||
// By default, the token will be automatically refreshed through the Refresher interface.
|
||||
func (mt multiTenantSPTAuthorizer) WithAuthorization() PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
if refresher, ok := mt.tp.(adal.RefresherWithContext); ok {
|
||||
err = refresher.EnsureFreshWithContext(r.Context())
|
||||
if err != nil {
|
||||
var resp *http.Response
|
||||
if tokError, ok := err.(adal.TokenRefreshError); ok {
|
||||
resp = tokError.Response()
|
||||
}
|
||||
return r, NewErrorWithError(err, "azure.multiTenantSPTAuthorizer", "WithAuthorization", resp,
|
||||
"Failed to refresh one or more Tokens for request to %s", r.URL)
|
||||
}
|
||||
}
|
||||
r, err = Prepare(r, WithHeader(headerAuthorization, fmt.Sprintf("Bearer %s", mt.tp.PrimaryOAuthToken())))
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
auxTokens := mt.tp.AuxiliaryOAuthTokens()
|
||||
for i := range auxTokens {
|
||||
auxTokens[i] = fmt.Sprintf("Bearer %s", auxTokens[i])
|
||||
}
|
||||
return Prepare(r, WithHeader(headerAuxAuthorization, strings.Join(auxTokens, "; ")))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
1043
vendor/github.com/Azure/go-autorest/autorest/azure/async.go
generated
vendored
1043
vendor/github.com/Azure/go-autorest/autorest/azure/async.go
generated
vendored
File diff suppressed because it is too large
Load Diff
523
vendor/github.com/Azure/go-autorest/autorest/azure/auth/auth.go
generated
vendored
523
vendor/github.com/Azure/go-autorest/autorest/azure/auth/auth.go
generated
vendored
@@ -31,126 +31,416 @@ import (
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/Azure/go-autorest/autorest/azure/cli"
|
||||
"github.com/dimchansky/utfbom"
|
||||
"golang.org/x/crypto/pkcs12"
|
||||
)
|
||||
|
||||
// The possible keys in the Values map.
|
||||
const (
|
||||
SubscriptionID = "AZURE_SUBSCRIPTION_ID"
|
||||
TenantID = "AZURE_TENANT_ID"
|
||||
AuxiliaryTenantIDs = "AZURE_AUXILIARY_TENANT_IDS"
|
||||
ClientID = "AZURE_CLIENT_ID"
|
||||
ClientSecret = "AZURE_CLIENT_SECRET"
|
||||
CertificatePath = "AZURE_CERTIFICATE_PATH"
|
||||
CertificatePassword = "AZURE_CERTIFICATE_PASSWORD"
|
||||
Username = "AZURE_USERNAME"
|
||||
Password = "AZURE_PASSWORD"
|
||||
EnvironmentName = "AZURE_ENVIRONMENT"
|
||||
Resource = "AZURE_AD_RESOURCE"
|
||||
ActiveDirectoryEndpoint = "ActiveDirectoryEndpoint"
|
||||
ResourceManagerEndpoint = "ResourceManagerEndpoint"
|
||||
GraphResourceID = "GraphResourceID"
|
||||
SQLManagementEndpoint = "SQLManagementEndpoint"
|
||||
GalleryEndpoint = "GalleryEndpoint"
|
||||
ManagementEndpoint = "ManagementEndpoint"
|
||||
)
|
||||
|
||||
// NewAuthorizerFromEnvironment creates an Authorizer configured from environment variables in the order:
|
||||
// 1. Client credentials
|
||||
// 2. Client certificate
|
||||
// 3. Username password
|
||||
// 4. MSI
|
||||
func NewAuthorizerFromEnvironment() (autorest.Authorizer, error) {
|
||||
tenantID := os.Getenv("AZURE_TENANT_ID")
|
||||
clientID := os.Getenv("AZURE_CLIENT_ID")
|
||||
clientSecret := os.Getenv("AZURE_CLIENT_SECRET")
|
||||
certificatePath := os.Getenv("AZURE_CERTIFICATE_PATH")
|
||||
certificatePassword := os.Getenv("AZURE_CERTIFICATE_PASSWORD")
|
||||
username := os.Getenv("AZURE_USERNAME")
|
||||
password := os.Getenv("AZURE_PASSWORD")
|
||||
envName := os.Getenv("AZURE_ENVIRONMENT")
|
||||
resource := os.Getenv("AZURE_AD_RESOURCE")
|
||||
settings, err := GetSettingsFromEnvironment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return settings.GetAuthorizer()
|
||||
}
|
||||
|
||||
var env azure.Environment
|
||||
if envName == "" {
|
||||
env = azure.PublicCloud
|
||||
// NewAuthorizerFromEnvironmentWithResource creates an Authorizer configured from environment variables in the order:
|
||||
// 1. Client credentials
|
||||
// 2. Client certificate
|
||||
// 3. Username password
|
||||
// 4. MSI
|
||||
func NewAuthorizerFromEnvironmentWithResource(resource string) (autorest.Authorizer, error) {
|
||||
settings, err := GetSettingsFromEnvironment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
settings.Values[Resource] = resource
|
||||
return settings.GetAuthorizer()
|
||||
}
|
||||
|
||||
// EnvironmentSettings contains the available authentication settings.
|
||||
type EnvironmentSettings struct {
|
||||
Values map[string]string
|
||||
Environment azure.Environment
|
||||
}
|
||||
|
||||
// GetSettingsFromEnvironment returns the available authentication settings from the environment.
|
||||
func GetSettingsFromEnvironment() (s EnvironmentSettings, err error) {
|
||||
s = EnvironmentSettings{
|
||||
Values: map[string]string{},
|
||||
}
|
||||
s.setValue(SubscriptionID)
|
||||
s.setValue(TenantID)
|
||||
s.setValue(AuxiliaryTenantIDs)
|
||||
s.setValue(ClientID)
|
||||
s.setValue(ClientSecret)
|
||||
s.setValue(CertificatePath)
|
||||
s.setValue(CertificatePassword)
|
||||
s.setValue(Username)
|
||||
s.setValue(Password)
|
||||
s.setValue(EnvironmentName)
|
||||
s.setValue(Resource)
|
||||
if v := s.Values[EnvironmentName]; v == "" {
|
||||
s.Environment = azure.PublicCloud
|
||||
} else {
|
||||
var err error
|
||||
env, err = azure.EnvironmentFromName(envName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
s.Environment, err = azure.EnvironmentFromName(v)
|
||||
}
|
||||
if s.Values[Resource] == "" {
|
||||
s.Values[Resource] = s.Environment.ResourceManagerEndpoint
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetSubscriptionID returns the available subscription ID or an empty string.
|
||||
func (settings EnvironmentSettings) GetSubscriptionID() string {
|
||||
return settings.Values[SubscriptionID]
|
||||
}
|
||||
|
||||
// adds the specified environment variable value to the Values map if it exists
|
||||
func (settings EnvironmentSettings) setValue(key string) {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
settings.Values[key] = v
|
||||
}
|
||||
}
|
||||
|
||||
// helper to return client and tenant IDs
|
||||
func (settings EnvironmentSettings) getClientAndTenant() (string, string) {
|
||||
clientID := settings.Values[ClientID]
|
||||
tenantID := settings.Values[TenantID]
|
||||
return clientID, tenantID
|
||||
}
|
||||
|
||||
// GetClientCredentials creates a config object from the available client credentials.
|
||||
// An error is returned if no client credentials are available.
|
||||
func (settings EnvironmentSettings) GetClientCredentials() (ClientCredentialsConfig, error) {
|
||||
secret := settings.Values[ClientSecret]
|
||||
if secret == "" {
|
||||
return ClientCredentialsConfig{}, errors.New("missing client secret")
|
||||
}
|
||||
clientID, tenantID := settings.getClientAndTenant()
|
||||
config := NewClientCredentialsConfig(clientID, secret, tenantID)
|
||||
config.AADEndpoint = settings.Environment.ActiveDirectoryEndpoint
|
||||
config.Resource = settings.Values[Resource]
|
||||
if auxTenants, ok := settings.Values[AuxiliaryTenantIDs]; ok {
|
||||
config.AuxTenants = strings.Split(auxTenants, ";")
|
||||
for i := range config.AuxTenants {
|
||||
config.AuxTenants[i] = strings.TrimSpace(config.AuxTenants[i])
|
||||
}
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
if resource == "" {
|
||||
resource = env.ResourceManagerEndpoint
|
||||
// GetClientCertificate creates a config object from the available certificate credentials.
|
||||
// An error is returned if no certificate credentials are available.
|
||||
func (settings EnvironmentSettings) GetClientCertificate() (ClientCertificateConfig, error) {
|
||||
certPath := settings.Values[CertificatePath]
|
||||
if certPath == "" {
|
||||
return ClientCertificateConfig{}, errors.New("missing certificate path")
|
||||
}
|
||||
certPwd := settings.Values[CertificatePassword]
|
||||
clientID, tenantID := settings.getClientAndTenant()
|
||||
config := NewClientCertificateConfig(certPath, certPwd, clientID, tenantID)
|
||||
config.AADEndpoint = settings.Environment.ActiveDirectoryEndpoint
|
||||
config.Resource = settings.Values[Resource]
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// GetUsernamePassword creates a config object from the available username/password credentials.
|
||||
// An error is returned if no username/password credentials are available.
|
||||
func (settings EnvironmentSettings) GetUsernamePassword() (UsernamePasswordConfig, error) {
|
||||
username := settings.Values[Username]
|
||||
password := settings.Values[Password]
|
||||
if username == "" || password == "" {
|
||||
return UsernamePasswordConfig{}, errors.New("missing username/password")
|
||||
}
|
||||
clientID, tenantID := settings.getClientAndTenant()
|
||||
config := NewUsernamePasswordConfig(username, password, clientID, tenantID)
|
||||
config.AADEndpoint = settings.Environment.ActiveDirectoryEndpoint
|
||||
config.Resource = settings.Values[Resource]
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// GetMSI creates a MSI config object from the available client ID.
|
||||
func (settings EnvironmentSettings) GetMSI() MSIConfig {
|
||||
config := NewMSIConfig()
|
||||
config.Resource = settings.Values[Resource]
|
||||
config.ClientID = settings.Values[ClientID]
|
||||
return config
|
||||
}
|
||||
|
||||
// GetDeviceFlow creates a device-flow config object from the available client and tenant IDs.
|
||||
func (settings EnvironmentSettings) GetDeviceFlow() DeviceFlowConfig {
|
||||
clientID, tenantID := settings.getClientAndTenant()
|
||||
config := NewDeviceFlowConfig(clientID, tenantID)
|
||||
config.AADEndpoint = settings.Environment.ActiveDirectoryEndpoint
|
||||
config.Resource = settings.Values[Resource]
|
||||
return config
|
||||
}
|
||||
|
||||
// GetAuthorizer creates an Authorizer configured from environment variables in the order:
|
||||
// 1. Client credentials
|
||||
// 2. Client certificate
|
||||
// 3. Username password
|
||||
// 4. MSI
|
||||
func (settings EnvironmentSettings) GetAuthorizer() (autorest.Authorizer, error) {
|
||||
//1.Client Credentials
|
||||
if clientSecret != "" {
|
||||
config := NewClientCredentialsConfig(clientID, clientSecret, tenantID)
|
||||
config.AADEndpoint = env.ActiveDirectoryEndpoint
|
||||
config.Resource = resource
|
||||
return config.Authorizer()
|
||||
if c, e := settings.GetClientCredentials(); e == nil {
|
||||
return c.Authorizer()
|
||||
}
|
||||
|
||||
//2. Client Certificate
|
||||
if certificatePath != "" {
|
||||
config := NewClientCertificateConfig(certificatePath, certificatePassword, clientID, tenantID)
|
||||
config.AADEndpoint = env.ActiveDirectoryEndpoint
|
||||
config.Resource = resource
|
||||
return config.Authorizer()
|
||||
if c, e := settings.GetClientCertificate(); e == nil {
|
||||
return c.Authorizer()
|
||||
}
|
||||
|
||||
//3. Username Password
|
||||
if username != "" && password != "" {
|
||||
config := NewUsernamePasswordConfig(username, password, clientID, tenantID)
|
||||
config.AADEndpoint = env.ActiveDirectoryEndpoint
|
||||
config.Resource = resource
|
||||
return config.Authorizer()
|
||||
if c, e := settings.GetUsernamePassword(); e == nil {
|
||||
return c.Authorizer()
|
||||
}
|
||||
|
||||
// 4. MSI
|
||||
config := NewMSIConfig()
|
||||
config.Resource = resource
|
||||
config.ClientID = clientID
|
||||
return config.Authorizer()
|
||||
return settings.GetMSI().Authorizer()
|
||||
}
|
||||
|
||||
// NewAuthorizerFromFile creates an Authorizer configured from a configuration file.
|
||||
// NewAuthorizerFromFile creates an Authorizer configured from a configuration file in the following order.
|
||||
// 1. Client credentials
|
||||
// 2. Client certificate
|
||||
func NewAuthorizerFromFile(baseURI string) (autorest.Authorizer, error) {
|
||||
settings, err := GetSettingsFromFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if a, err := settings.ClientCredentialsAuthorizer(baseURI); err == nil {
|
||||
return a, err
|
||||
}
|
||||
if a, err := settings.ClientCertificateAuthorizer(baseURI); err == nil {
|
||||
return a, err
|
||||
}
|
||||
return nil, errors.New("auth file missing client and certificate credentials")
|
||||
}
|
||||
|
||||
// NewAuthorizerFromFileWithResource creates an Authorizer configured from a configuration file in the following order.
|
||||
// 1. Client credentials
|
||||
// 2. Client certificate
|
||||
func NewAuthorizerFromFileWithResource(resource string) (autorest.Authorizer, error) {
|
||||
s, err := GetSettingsFromFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if a, err := s.ClientCredentialsAuthorizerWithResource(resource); err == nil {
|
||||
return a, err
|
||||
}
|
||||
if a, err := s.ClientCertificateAuthorizerWithResource(resource); err == nil {
|
||||
return a, err
|
||||
}
|
||||
return nil, errors.New("auth file missing client and certificate credentials")
|
||||
}
|
||||
|
||||
// NewAuthorizerFromCLI creates an Authorizer configured from Azure CLI 2.0 for local development scenarios.
|
||||
func NewAuthorizerFromCLI() (autorest.Authorizer, error) {
|
||||
settings, err := GetSettingsFromEnvironment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if settings.Values[Resource] == "" {
|
||||
settings.Values[Resource] = settings.Environment.ResourceManagerEndpoint
|
||||
}
|
||||
|
||||
return NewAuthorizerFromCLIWithResource(settings.Values[Resource])
|
||||
}
|
||||
|
||||
// NewAuthorizerFromCLIWithResource creates an Authorizer configured from Azure CLI 2.0 for local development scenarios.
|
||||
func NewAuthorizerFromCLIWithResource(resource string) (autorest.Authorizer, error) {
|
||||
token, err := cli.GetTokenFromCLI(resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
adalToken, err := token.ToADALToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(&adalToken), nil
|
||||
}
|
||||
|
||||
// GetSettingsFromFile returns the available authentication settings from an Azure CLI authentication file.
|
||||
func GetSettingsFromFile() (FileSettings, error) {
|
||||
s := FileSettings{}
|
||||
fileLocation := os.Getenv("AZURE_AUTH_LOCATION")
|
||||
if fileLocation == "" {
|
||||
return nil, errors.New("auth file not found. Environment variable AZURE_AUTH_LOCATION is not set")
|
||||
return s, errors.New("environment variable AZURE_AUTH_LOCATION is not set")
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadFile(fileLocation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return s, err
|
||||
}
|
||||
|
||||
// Auth file might be encoded
|
||||
decoded, err := decode(contents)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return s, err
|
||||
}
|
||||
|
||||
file := file{}
|
||||
err = json.Unmarshal(decoded, &file)
|
||||
authFile := map[string]interface{}{}
|
||||
err = json.Unmarshal(decoded, &authFile)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
s.Values = map[string]string{}
|
||||
s.setKeyValue(ClientID, authFile["clientId"])
|
||||
s.setKeyValue(ClientSecret, authFile["clientSecret"])
|
||||
s.setKeyValue(CertificatePath, authFile["clientCertificate"])
|
||||
s.setKeyValue(CertificatePassword, authFile["clientCertificatePassword"])
|
||||
s.setKeyValue(SubscriptionID, authFile["subscriptionId"])
|
||||
s.setKeyValue(TenantID, authFile["tenantId"])
|
||||
s.setKeyValue(ActiveDirectoryEndpoint, authFile["activeDirectoryEndpointUrl"])
|
||||
s.setKeyValue(ResourceManagerEndpoint, authFile["resourceManagerEndpointUrl"])
|
||||
s.setKeyValue(GraphResourceID, authFile["activeDirectoryGraphResourceId"])
|
||||
s.setKeyValue(SQLManagementEndpoint, authFile["sqlManagementEndpointUrl"])
|
||||
s.setKeyValue(GalleryEndpoint, authFile["galleryEndpointUrl"])
|
||||
s.setKeyValue(ManagementEndpoint, authFile["managementEndpointUrl"])
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// FileSettings contains the available authentication settings.
|
||||
type FileSettings struct {
|
||||
Values map[string]string
|
||||
}
|
||||
|
||||
// GetSubscriptionID returns the available subscription ID or an empty string.
|
||||
func (settings FileSettings) GetSubscriptionID() string {
|
||||
return settings.Values[SubscriptionID]
|
||||
}
|
||||
|
||||
// adds the specified value to the Values map if it isn't nil
|
||||
func (settings FileSettings) setKeyValue(key string, val interface{}) {
|
||||
if val != nil {
|
||||
settings.Values[key] = val.(string)
|
||||
}
|
||||
}
|
||||
|
||||
// returns the specified AAD endpoint or the public cloud endpoint if unspecified
|
||||
func (settings FileSettings) getAADEndpoint() string {
|
||||
if v, ok := settings.Values[ActiveDirectoryEndpoint]; ok {
|
||||
return v
|
||||
}
|
||||
return azure.PublicCloud.ActiveDirectoryEndpoint
|
||||
}
|
||||
|
||||
// ServicePrincipalTokenFromClientCredentials creates a ServicePrincipalToken from the available client credentials.
|
||||
func (settings FileSettings) ServicePrincipalTokenFromClientCredentials(baseURI string) (*adal.ServicePrincipalToken, error) {
|
||||
resource, err := settings.getResourceForToken(baseURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return settings.ServicePrincipalTokenFromClientCredentialsWithResource(resource)
|
||||
}
|
||||
|
||||
resource, err := getResourceForToken(file, baseURI)
|
||||
// ClientCredentialsAuthorizer creates an authorizer from the available client credentials.
|
||||
func (settings FileSettings) ClientCredentialsAuthorizer(baseURI string) (autorest.Authorizer, error) {
|
||||
resource, err := settings.getResourceForToken(baseURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return settings.ClientCredentialsAuthorizerWithResource(resource)
|
||||
}
|
||||
|
||||
config, err := adal.NewOAuthConfig(file.ActiveDirectoryEndpoint, file.TenantID)
|
||||
// ServicePrincipalTokenFromClientCredentialsWithResource creates a ServicePrincipalToken
|
||||
// from the available client credentials and the specified resource.
|
||||
func (settings FileSettings) ServicePrincipalTokenFromClientCredentialsWithResource(resource string) (*adal.ServicePrincipalToken, error) {
|
||||
if _, ok := settings.Values[ClientSecret]; !ok {
|
||||
return nil, errors.New("missing client secret")
|
||||
}
|
||||
config, err := adal.NewOAuthConfig(settings.getAADEndpoint(), settings.Values[TenantID])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return adal.NewServicePrincipalToken(*config, settings.Values[ClientID], settings.Values[ClientSecret], resource)
|
||||
}
|
||||
|
||||
spToken, err := adal.NewServicePrincipalToken(*config, file.ClientID, file.ClientSecret, resource)
|
||||
func (settings FileSettings) clientCertificateConfigWithResource(resource string) (ClientCertificateConfig, error) {
|
||||
if _, ok := settings.Values[CertificatePath]; !ok {
|
||||
return ClientCertificateConfig{}, errors.New("missing certificate path")
|
||||
}
|
||||
cfg := NewClientCertificateConfig(settings.Values[CertificatePath], settings.Values[CertificatePassword], settings.Values[ClientID], settings.Values[TenantID])
|
||||
cfg.AADEndpoint = settings.getAADEndpoint()
|
||||
cfg.Resource = resource
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// ClientCredentialsAuthorizerWithResource creates an authorizer from the available client credentials and the specified resource.
|
||||
func (settings FileSettings) ClientCredentialsAuthorizerWithResource(resource string) (autorest.Authorizer, error) {
|
||||
spToken, err := settings.ServicePrincipalTokenFromClientCredentialsWithResource(resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
||||
|
||||
// File represents the authentication file
|
||||
type file struct {
|
||||
ClientID string `json:"clientId,omitempty"`
|
||||
ClientSecret string `json:"clientSecret,omitempty"`
|
||||
SubscriptionID string `json:"subscriptionId,omitempty"`
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
ActiveDirectoryEndpoint string `json:"activeDirectoryEndpointUrl,omitempty"`
|
||||
ResourceManagerEndpoint string `json:"resourceManagerEndpointUrl,omitempty"`
|
||||
GraphResourceID string `json:"activeDirectoryGraphResourceId,omitempty"`
|
||||
SQLManagementEndpoint string `json:"sqlManagementEndpointUrl,omitempty"`
|
||||
GalleryEndpoint string `json:"galleryEndpointUrl,omitempty"`
|
||||
ManagementEndpoint string `json:"managementEndpointUrl,omitempty"`
|
||||
// ServicePrincipalTokenFromClientCertificate creates a ServicePrincipalToken from the available certificate credentials.
|
||||
func (settings FileSettings) ServicePrincipalTokenFromClientCertificate(baseURI string) (*adal.ServicePrincipalToken, error) {
|
||||
resource, err := settings.getResourceForToken(baseURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return settings.ServicePrincipalTokenFromClientCertificateWithResource(resource)
|
||||
}
|
||||
|
||||
// ClientCertificateAuthorizer creates an authorizer from the available certificate credentials.
|
||||
func (settings FileSettings) ClientCertificateAuthorizer(baseURI string) (autorest.Authorizer, error) {
|
||||
resource, err := settings.getResourceForToken(baseURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return settings.ClientCertificateAuthorizerWithResource(resource)
|
||||
}
|
||||
|
||||
// ServicePrincipalTokenFromClientCertificateWithResource creates a ServicePrincipalToken from the available certificate credentials.
|
||||
func (settings FileSettings) ServicePrincipalTokenFromClientCertificateWithResource(resource string) (*adal.ServicePrincipalToken, error) {
|
||||
cfg, err := settings.clientCertificateConfigWithResource(resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cfg.ServicePrincipalToken()
|
||||
}
|
||||
|
||||
// ClientCertificateAuthorizerWithResource creates an authorizer from the available certificate credentials and the specified resource.
|
||||
func (settings FileSettings) ClientCertificateAuthorizerWithResource(resource string) (autorest.Authorizer, error) {
|
||||
cfg, err := settings.clientCertificateConfigWithResource(resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cfg.Authorizer()
|
||||
}
|
||||
|
||||
func decode(b []byte) ([]byte, error) {
|
||||
@@ -175,7 +465,7 @@ func decode(b []byte) ([]byte, error) {
|
||||
return ioutil.ReadAll(reader)
|
||||
}
|
||||
|
||||
func getResourceForToken(f file, baseURI string) (string, error) {
|
||||
func (settings FileSettings) getResourceForToken(baseURI string) (string, error) {
|
||||
// Compare dafault base URI from the SDK to the endpoints from the public cloud
|
||||
// Base URI and token resource are the same string. This func finds the authentication
|
||||
// file field that matches the SDK base URI. The SDK defines the public cloud
|
||||
@@ -185,15 +475,15 @@ func getResourceForToken(f file, baseURI string) (string, error) {
|
||||
}
|
||||
switch baseURI {
|
||||
case azure.PublicCloud.ServiceManagementEndpoint:
|
||||
return f.ManagementEndpoint, nil
|
||||
return settings.Values[ManagementEndpoint], nil
|
||||
case azure.PublicCloud.ResourceManagerEndpoint:
|
||||
return f.ResourceManagerEndpoint, nil
|
||||
return settings.Values[ResourceManagerEndpoint], nil
|
||||
case azure.PublicCloud.ActiveDirectoryEndpoint:
|
||||
return f.ActiveDirectoryEndpoint, nil
|
||||
return settings.Values[ActiveDirectoryEndpoint], nil
|
||||
case azure.PublicCloud.GalleryEndpoint:
|
||||
return f.GalleryEndpoint, nil
|
||||
return settings.Values[GalleryEndpoint], nil
|
||||
case azure.PublicCloud.GraphEndpoint:
|
||||
return f.GraphResourceID, nil
|
||||
return settings.Values[GraphResourceID], nil
|
||||
}
|
||||
return "", fmt.Errorf("auth: base URI not found in endpoints")
|
||||
}
|
||||
@@ -264,23 +554,43 @@ type ClientCredentialsConfig struct {
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
TenantID string
|
||||
AuxTenants []string
|
||||
AADEndpoint string
|
||||
Resource string
|
||||
}
|
||||
|
||||
// Authorizer gets the authorizer from client credentials.
|
||||
func (ccc ClientCredentialsConfig) Authorizer() (autorest.Authorizer, error) {
|
||||
// ServicePrincipalToken creates a ServicePrincipalToken from client credentials.
|
||||
func (ccc ClientCredentialsConfig) ServicePrincipalToken() (*adal.ServicePrincipalToken, error) {
|
||||
oauthConfig, err := adal.NewOAuthConfig(ccc.AADEndpoint, ccc.TenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return adal.NewServicePrincipalToken(*oauthConfig, ccc.ClientID, ccc.ClientSecret, ccc.Resource)
|
||||
}
|
||||
|
||||
spToken, err := adal.NewServicePrincipalToken(*oauthConfig, ccc.ClientID, ccc.ClientSecret, ccc.Resource)
|
||||
// MultiTenantServicePrincipalToken creates a MultiTenantServicePrincipalToken from client credentials.
|
||||
func (ccc ClientCredentialsConfig) MultiTenantServicePrincipalToken() (*adal.MultiTenantServicePrincipalToken, error) {
|
||||
oauthConfig, err := adal.NewMultiTenantOAuthConfig(ccc.AADEndpoint, ccc.TenantID, ccc.AuxTenants, adal.OAuthOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from client credentials: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return adal.NewMultiTenantServicePrincipalToken(oauthConfig, ccc.ClientID, ccc.ClientSecret, ccc.Resource)
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
// Authorizer gets the authorizer from client credentials.
|
||||
func (ccc ClientCredentialsConfig) Authorizer() (autorest.Authorizer, error) {
|
||||
if len(ccc.AuxTenants) == 0 {
|
||||
spToken, err := ccc.ServicePrincipalToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get SPT from client credentials: %v", err)
|
||||
}
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
||||
mtSPT, err := ccc.MultiTenantServicePrincipalToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get multitenant SPT from client credentials: %v", err)
|
||||
}
|
||||
return autorest.NewMultiTenantServicePrincipalTokenAuthorizer(mtSPT), nil
|
||||
}
|
||||
|
||||
// ClientCertificateConfig provides the options to get a bearer authorizer from a client certificate.
|
||||
@@ -293,26 +603,29 @@ type ClientCertificateConfig struct {
|
||||
Resource string
|
||||
}
|
||||
|
||||
// Authorizer gets an authorizer object from client certificate.
|
||||
func (ccc ClientCertificateConfig) Authorizer() (autorest.Authorizer, error) {
|
||||
// ServicePrincipalToken creates a ServicePrincipalToken from client certificate.
|
||||
func (ccc ClientCertificateConfig) ServicePrincipalToken() (*adal.ServicePrincipalToken, error) {
|
||||
oauthConfig, err := adal.NewOAuthConfig(ccc.AADEndpoint, ccc.TenantID)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certData, err := ioutil.ReadFile(ccc.CertificatePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read the certificate file (%s): %v", ccc.CertificatePath, err)
|
||||
}
|
||||
|
||||
certificate, rsaPrivateKey, err := decodePkcs12(certData, ccc.CertificatePassword)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %v", err)
|
||||
}
|
||||
return adal.NewServicePrincipalTokenFromCertificate(*oauthConfig, ccc.ClientID, certificate, rsaPrivateKey, ccc.Resource)
|
||||
}
|
||||
|
||||
spToken, err := adal.NewServicePrincipalTokenFromCertificate(*oauthConfig, ccc.ClientID, certificate, rsaPrivateKey, ccc.Resource)
|
||||
|
||||
// Authorizer gets an authorizer object from client certificate.
|
||||
func (ccc ClientCertificateConfig) Authorizer() (autorest.Authorizer, error) {
|
||||
spToken, err := ccc.ServicePrincipalToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from certificate auth: %v", err)
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
||||
|
||||
@@ -326,26 +639,30 @@ type DeviceFlowConfig struct {
|
||||
|
||||
// Authorizer gets the authorizer from device flow.
|
||||
func (dfc DeviceFlowConfig) Authorizer() (autorest.Authorizer, error) {
|
||||
oauthClient := &autorest.Client{}
|
||||
spToken, err := dfc.ServicePrincipalToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from device flow: %v", err)
|
||||
}
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
||||
|
||||
// ServicePrincipalToken gets the service principal token from device flow.
|
||||
func (dfc DeviceFlowConfig) ServicePrincipalToken() (*adal.ServicePrincipalToken, error) {
|
||||
oauthConfig, err := adal.NewOAuthConfig(dfc.AADEndpoint, dfc.TenantID)
|
||||
deviceCode, err := adal.InitiateDeviceAuth(oauthClient, *oauthConfig, dfc.ClientID, dfc.AADEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oauthClient := &autorest.Client{}
|
||||
deviceCode, err := adal.InitiateDeviceAuth(oauthClient, *oauthConfig, dfc.ClientID, dfc.Resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to start device auth flow: %s", err)
|
||||
}
|
||||
|
||||
log.Println(*deviceCode.Message)
|
||||
|
||||
token, err := adal.WaitForUserCompletion(oauthClient, deviceCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to finish device auth flow: %s", err)
|
||||
}
|
||||
|
||||
spToken, err := adal.NewServicePrincipalTokenFromManualToken(*oauthConfig, dfc.ClientID, dfc.Resource, *token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from device flow: %v", err)
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
return adal.NewServicePrincipalTokenFromManualToken(*oauthConfig, dfc.ClientID, dfc.Resource, *token)
|
||||
}
|
||||
|
||||
func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
@@ -372,17 +689,21 @@ type UsernamePasswordConfig struct {
|
||||
Resource string
|
||||
}
|
||||
|
||||
// ServicePrincipalToken creates a ServicePrincipalToken from username and password.
|
||||
func (ups UsernamePasswordConfig) ServicePrincipalToken() (*adal.ServicePrincipalToken, error) {
|
||||
oauthConfig, err := adal.NewOAuthConfig(ups.AADEndpoint, ups.TenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return adal.NewServicePrincipalTokenFromUsernamePassword(*oauthConfig, ups.ClientID, ups.Username, ups.Password, ups.Resource)
|
||||
}
|
||||
|
||||
// Authorizer gets the authorizer from a username and a password.
|
||||
func (ups UsernamePasswordConfig) Authorizer() (autorest.Authorizer, error) {
|
||||
|
||||
oauthConfig, err := adal.NewOAuthConfig(ups.AADEndpoint, ups.TenantID)
|
||||
|
||||
spToken, err := adal.NewServicePrincipalTokenFromUsernamePassword(*oauthConfig, ups.ClientID, ups.Username, ups.Password, ups.Resource)
|
||||
|
||||
spToken, err := ups.ServicePrincipalToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from username and password auth: %v", err)
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
||||
|
||||
@@ -399,9 +720,17 @@ func (mc MSIConfig) Authorizer() (autorest.Authorizer, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spToken, err := adal.NewServicePrincipalTokenFromMSI(msiEndpoint, mc.Resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from MSI: %v", err)
|
||||
var spToken *adal.ServicePrincipalToken
|
||||
if mc.ClientID == "" {
|
||||
spToken, err = adal.NewServicePrincipalTokenFromMSI(msiEndpoint, mc.Resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from MSI: %v", err)
|
||||
}
|
||||
} else {
|
||||
spToken, err = adal.NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, mc.Resource, mc.ClientID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from MSI for user assigned identity: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
|
75
vendor/github.com/Azure/go-autorest/autorest/azure/azure.go
generated
vendored
75
vendor/github.com/Azure/go-autorest/autorest/azure/azure.go
generated
vendored
@@ -44,11 +44,12 @@ const (
|
||||
// ServiceError encapsulates the error response from an Azure service.
|
||||
// It adhears to the OData v4 specification for error responses.
|
||||
type ServiceError struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Target *string `json:"target"`
|
||||
Details []map[string]interface{} `json:"details"`
|
||||
InnerError map[string]interface{} `json:"innererror"`
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Target *string `json:"target"`
|
||||
Details []map[string]interface{} `json:"details"`
|
||||
InnerError map[string]interface{} `json:"innererror"`
|
||||
AdditionalInfo []map[string]interface{} `json:"additionalInfo"`
|
||||
}
|
||||
|
||||
func (se ServiceError) Error() string {
|
||||
@@ -74,6 +75,14 @@ func (se ServiceError) Error() string {
|
||||
result += fmt.Sprintf(" InnerError=%v", string(d))
|
||||
}
|
||||
|
||||
if se.AdditionalInfo != nil {
|
||||
d, err := json.Marshal(se.AdditionalInfo)
|
||||
if err != nil {
|
||||
result += fmt.Sprintf(" AdditionalInfo=%v", se.AdditionalInfo)
|
||||
}
|
||||
result += fmt.Sprintf(" AdditionalInfo=%v", string(d))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -86,44 +95,47 @@ func (se *ServiceError) UnmarshalJSON(b []byte) error {
|
||||
// http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793091
|
||||
|
||||
type serviceError1 struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Target *string `json:"target"`
|
||||
Details []map[string]interface{} `json:"details"`
|
||||
InnerError map[string]interface{} `json:"innererror"`
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Target *string `json:"target"`
|
||||
Details []map[string]interface{} `json:"details"`
|
||||
InnerError map[string]interface{} `json:"innererror"`
|
||||
AdditionalInfo []map[string]interface{} `json:"additionalInfo"`
|
||||
}
|
||||
|
||||
type serviceError2 struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Target *string `json:"target"`
|
||||
Details map[string]interface{} `json:"details"`
|
||||
InnerError map[string]interface{} `json:"innererror"`
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Target *string `json:"target"`
|
||||
Details map[string]interface{} `json:"details"`
|
||||
InnerError map[string]interface{} `json:"innererror"`
|
||||
AdditionalInfo []map[string]interface{} `json:"additionalInfo"`
|
||||
}
|
||||
|
||||
se1 := serviceError1{}
|
||||
err := json.Unmarshal(b, &se1)
|
||||
if err == nil {
|
||||
se.populate(se1.Code, se1.Message, se1.Target, se1.Details, se1.InnerError)
|
||||
se.populate(se1.Code, se1.Message, se1.Target, se1.Details, se1.InnerError, se1.AdditionalInfo)
|
||||
return nil
|
||||
}
|
||||
|
||||
se2 := serviceError2{}
|
||||
err = json.Unmarshal(b, &se2)
|
||||
if err == nil {
|
||||
se.populate(se2.Code, se2.Message, se2.Target, nil, se2.InnerError)
|
||||
se.populate(se2.Code, se2.Message, se2.Target, nil, se2.InnerError, se2.AdditionalInfo)
|
||||
se.Details = append(se.Details, se2.Details)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (se *ServiceError) populate(code, message string, target *string, details []map[string]interface{}, inner map[string]interface{}) {
|
||||
func (se *ServiceError) populate(code, message string, target *string, details []map[string]interface{}, inner map[string]interface{}, additional []map[string]interface{}) {
|
||||
se.Code = code
|
||||
se.Message = message
|
||||
se.Target = target
|
||||
se.Details = details
|
||||
se.InnerError = inner
|
||||
se.AdditionalInfo = additional
|
||||
}
|
||||
|
||||
// RequestError describes an error response returned by Azure service.
|
||||
@@ -279,16 +291,29 @@ func WithErrorUnlessStatusCode(codes ...int) autorest.RespondDecorator {
|
||||
resp.Body = ioutil.NopCloser(&b)
|
||||
if decodeErr != nil {
|
||||
return fmt.Errorf("autorest/azure: error response cannot be parsed: %q error: %v", b.String(), decodeErr)
|
||||
} else if e.ServiceError == nil {
|
||||
}
|
||||
if e.ServiceError == nil {
|
||||
// Check if error is unwrapped ServiceError
|
||||
if err := json.Unmarshal(b.Bytes(), &e.ServiceError); err != nil || e.ServiceError.Message == "" {
|
||||
e.ServiceError = &ServiceError{
|
||||
Code: "Unknown",
|
||||
Message: "Unknown service error",
|
||||
}
|
||||
if err := json.Unmarshal(b.Bytes(), &e.ServiceError); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if e.ServiceError.Message == "" {
|
||||
// if we're here it means the returned error wasn't OData v4 compliant.
|
||||
// try to unmarshal the body as raw JSON in hopes of getting something.
|
||||
rawBody := map[string]interface{}{}
|
||||
if err := json.Unmarshal(b.Bytes(), &rawBody); err != nil {
|
||||
return err
|
||||
}
|
||||
e.ServiceError = &ServiceError{
|
||||
Code: "Unknown",
|
||||
Message: "Unknown service error",
|
||||
}
|
||||
if len(rawBody) > 0 {
|
||||
e.ServiceError.Details = []map[string]interface{}{rawBody}
|
||||
}
|
||||
}
|
||||
e.Response = resp
|
||||
e.RequestID = ExtractRequestID(resp)
|
||||
if e.StatusCode == nil {
|
||||
e.StatusCode = resp.StatusCode
|
||||
|
79
vendor/github.com/Azure/go-autorest/autorest/azure/cli/profile.go
generated
vendored
Normal file
79
vendor/github.com/Azure/go-autorest/autorest/azure/cli/profile.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
package cli
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/dimchansky/utfbom"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
// Profile represents a Profile from the Azure CLI
|
||||
type Profile struct {
|
||||
InstallationID string `json:"installationId"`
|
||||
Subscriptions []Subscription `json:"subscriptions"`
|
||||
}
|
||||
|
||||
// Subscription represents a Subscription from the Azure CLI
|
||||
type Subscription struct {
|
||||
EnvironmentName string `json:"environmentName"`
|
||||
ID string `json:"id"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
Name string `json:"name"`
|
||||
State string `json:"state"`
|
||||
TenantID string `json:"tenantId"`
|
||||
User *User `json:"user"`
|
||||
}
|
||||
|
||||
// User represents a User from the Azure CLI
|
||||
type User struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
const azureProfileJSON = "azureProfile.json"
|
||||
|
||||
// ProfilePath returns the path where the Azure Profile is stored from the Azure CLI
|
||||
func ProfilePath() (string, error) {
|
||||
if cfgDir := os.Getenv("AZURE_CONFIG_DIR"); cfgDir != "" {
|
||||
return filepath.Join(cfgDir, azureProfileJSON), nil
|
||||
}
|
||||
return homedir.Expand("~/.azure/" + azureProfileJSON)
|
||||
}
|
||||
|
||||
// LoadProfile restores a Profile object from a file located at 'path'.
|
||||
func LoadProfile(path string) (result Profile, err error) {
|
||||
var contents []byte
|
||||
contents, err = ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to open file (%s) while loading token: %v", path, err)
|
||||
return
|
||||
}
|
||||
reader := utfbom.SkipOnly(bytes.NewReader(contents))
|
||||
|
||||
dec := json.NewDecoder(reader)
|
||||
if err = dec.Decode(&result); err != nil {
|
||||
err = fmt.Errorf("failed to decode contents of file (%s) into a Profile representation: %v", path, err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
170
vendor/github.com/Azure/go-autorest/autorest/azure/cli/token.go
generated
vendored
Normal file
170
vendor/github.com/Azure/go-autorest/autorest/azure/cli/token.go
generated
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
package cli
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/date"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
// Token represents an AccessToken from the Azure CLI
|
||||
type Token struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
Authority string `json:"_authority"`
|
||||
ClientID string `json:"_clientId"`
|
||||
ExpiresOn string `json:"expiresOn"`
|
||||
IdentityProvider string `json:"identityProvider"`
|
||||
IsMRRT bool `json:"isMRRT"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
Resource string `json:"resource"`
|
||||
TokenType string `json:"tokenType"`
|
||||
UserID string `json:"userId"`
|
||||
}
|
||||
|
||||
// ToADALToken converts an Azure CLI `Token`` to an `adal.Token``
|
||||
func (t Token) ToADALToken() (converted adal.Token, err error) {
|
||||
tokenExpirationDate, err := ParseExpirationDate(t.ExpiresOn)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error parsing Token Expiration Date %q: %+v", t.ExpiresOn, err)
|
||||
return
|
||||
}
|
||||
|
||||
difference := tokenExpirationDate.Sub(date.UnixEpoch())
|
||||
|
||||
converted = adal.Token{
|
||||
AccessToken: t.AccessToken,
|
||||
Type: t.TokenType,
|
||||
ExpiresIn: "3600",
|
||||
ExpiresOn: json.Number(strconv.Itoa(int(difference.Seconds()))),
|
||||
RefreshToken: t.RefreshToken,
|
||||
Resource: t.Resource,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AccessTokensPath returns the path where access tokens are stored from the Azure CLI
|
||||
// TODO(#199): add unit test.
|
||||
func AccessTokensPath() (string, error) {
|
||||
// Azure-CLI allows user to customize the path of access tokens thorugh environment variable.
|
||||
var accessTokenPath = os.Getenv("AZURE_ACCESS_TOKEN_FILE")
|
||||
var err error
|
||||
|
||||
// Fallback logic to default path on non-cloud-shell environment.
|
||||
// TODO(#200): remove the dependency on hard-coding path.
|
||||
if accessTokenPath == "" {
|
||||
accessTokenPath, err = homedir.Expand("~/.azure/accessTokens.json")
|
||||
}
|
||||
|
||||
return accessTokenPath, err
|
||||
}
|
||||
|
||||
// ParseExpirationDate parses either a Azure CLI or CloudShell date into a time object
|
||||
func ParseExpirationDate(input string) (*time.Time, error) {
|
||||
// CloudShell (and potentially the Azure CLI in future)
|
||||
expirationDate, cloudShellErr := time.Parse(time.RFC3339, input)
|
||||
if cloudShellErr != nil {
|
||||
// Azure CLI (Python) e.g. 2017-08-31 19:48:57.998857 (plus the local timezone)
|
||||
const cliFormat = "2006-01-02 15:04:05.999999"
|
||||
expirationDate, cliErr := time.ParseInLocation(cliFormat, input, time.Local)
|
||||
if cliErr == nil {
|
||||
return &expirationDate, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Error parsing expiration date %q.\n\nCloudShell Error: \n%+v\n\nCLI Error:\n%+v", input, cloudShellErr, cliErr)
|
||||
}
|
||||
|
||||
return &expirationDate, nil
|
||||
}
|
||||
|
||||
// LoadTokens restores a set of Token objects from a file located at 'path'.
|
||||
func LoadTokens(path string) ([]Token, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open file (%s) while loading token: %v", path, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var tokens []Token
|
||||
|
||||
dec := json.NewDecoder(file)
|
||||
if err = dec.Decode(&tokens); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode contents of file (%s) into a `cli.Token` representation: %v", path, err)
|
||||
}
|
||||
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
// GetTokenFromCLI gets a token using Azure CLI 2.0 for local development scenarios.
|
||||
func GetTokenFromCLI(resource string) (*Token, error) {
|
||||
// This is the path that a developer can set to tell this class what the install path for Azure CLI is.
|
||||
const azureCLIPath = "AzureCLIPath"
|
||||
|
||||
// The default install paths are used to find Azure CLI. This is for security, so that any path in the calling program's Path environment is not used to execute Azure CLI.
|
||||
azureCLIDefaultPathWindows := fmt.Sprintf("%s\\Microsoft SDKs\\Azure\\CLI2\\wbin; %s\\Microsoft SDKs\\Azure\\CLI2\\wbin", os.Getenv("ProgramFiles(x86)"), os.Getenv("ProgramFiles"))
|
||||
|
||||
// Default path for non-Windows.
|
||||
const azureCLIDefaultPath = "/bin:/sbin:/usr/bin:/usr/local/bin"
|
||||
|
||||
// Validate resource, since it gets sent as a command line argument to Azure CLI
|
||||
const invalidResourceErrorTemplate = "Resource %s is not in expected format. Only alphanumeric characters, [dot], [colon], [hyphen], and [forward slash] are allowed."
|
||||
match, err := regexp.MatchString("^[0-9a-zA-Z-.:/]+$", resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !match {
|
||||
return nil, fmt.Errorf(invalidResourceErrorTemplate, resource)
|
||||
}
|
||||
|
||||
// Execute Azure CLI to get token
|
||||
var cliCmd *exec.Cmd
|
||||
if runtime.GOOS == "windows" {
|
||||
cliCmd = exec.Command(fmt.Sprintf("%s\\system32\\cmd.exe", os.Getenv("windir")))
|
||||
cliCmd.Env = os.Environ()
|
||||
cliCmd.Env = append(cliCmd.Env, fmt.Sprintf("PATH=%s;%s", os.Getenv(azureCLIPath), azureCLIDefaultPathWindows))
|
||||
cliCmd.Args = append(cliCmd.Args, "/c", "az")
|
||||
} else {
|
||||
cliCmd = exec.Command("az")
|
||||
cliCmd.Env = os.Environ()
|
||||
cliCmd.Env = append(cliCmd.Env, fmt.Sprintf("PATH=%s:%s", os.Getenv(azureCLIPath), azureCLIDefaultPath))
|
||||
}
|
||||
cliCmd.Args = append(cliCmd.Args, "account", "get-access-token", "-o", "json", "--resource", resource)
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cliCmd.Stderr = &stderr
|
||||
|
||||
output, err := cliCmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invoking Azure CLI failed with the following error: %s", stderr.String())
|
||||
}
|
||||
|
||||
tokenResponse := Token{}
|
||||
err = json.Unmarshal(output, &tokenResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tokenResponse, err
|
||||
}
|
105
vendor/github.com/Azure/go-autorest/autorest/azure/environments.go
generated
vendored
105
vendor/github.com/Azure/go-autorest/autorest/azure/environments.go
generated
vendored
@@ -22,9 +22,14 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EnvironmentFilepathName captures the name of the environment variable containing the path to the file
|
||||
// to be used while populating the Azure Environment.
|
||||
const EnvironmentFilepathName = "AZURE_ENVIRONMENT_FILEPATH"
|
||||
const (
|
||||
// EnvironmentFilepathName captures the name of the environment variable containing the path to the file
|
||||
// to be used while populating the Azure Environment.
|
||||
EnvironmentFilepathName = "AZURE_ENVIRONMENT_FILEPATH"
|
||||
|
||||
// NotAvailable is used for endpoints and resource IDs that are not available for a given cloud.
|
||||
NotAvailable = "N/A"
|
||||
)
|
||||
|
||||
var environments = map[string]Environment{
|
||||
"AZURECHINACLOUD": ChinaCloud,
|
||||
@@ -33,28 +38,40 @@ var environments = map[string]Environment{
|
||||
"AZUREUSGOVERNMENTCLOUD": USGovernmentCloud,
|
||||
}
|
||||
|
||||
// ResourceIdentifier contains a set of Azure resource IDs.
|
||||
type ResourceIdentifier struct {
|
||||
Graph string `json:"graph"`
|
||||
KeyVault string `json:"keyVault"`
|
||||
Datalake string `json:"datalake"`
|
||||
Batch string `json:"batch"`
|
||||
OperationalInsights string `json:"operationalInsights"`
|
||||
Storage string `json:"storage"`
|
||||
}
|
||||
|
||||
// Environment represents a set of endpoints for each of Azure's Clouds.
|
||||
type Environment struct {
|
||||
Name string `json:"name"`
|
||||
ManagementPortalURL string `json:"managementPortalURL"`
|
||||
PublishSettingsURL string `json:"publishSettingsURL"`
|
||||
ServiceManagementEndpoint string `json:"serviceManagementEndpoint"`
|
||||
ResourceManagerEndpoint string `json:"resourceManagerEndpoint"`
|
||||
ActiveDirectoryEndpoint string `json:"activeDirectoryEndpoint"`
|
||||
GalleryEndpoint string `json:"galleryEndpoint"`
|
||||
KeyVaultEndpoint string `json:"keyVaultEndpoint"`
|
||||
GraphEndpoint string `json:"graphEndpoint"`
|
||||
ServiceBusEndpoint string `json:"serviceBusEndpoint"`
|
||||
BatchManagementEndpoint string `json:"batchManagementEndpoint"`
|
||||
StorageEndpointSuffix string `json:"storageEndpointSuffix"`
|
||||
SQLDatabaseDNSSuffix string `json:"sqlDatabaseDNSSuffix"`
|
||||
TrafficManagerDNSSuffix string `json:"trafficManagerDNSSuffix"`
|
||||
KeyVaultDNSSuffix string `json:"keyVaultDNSSuffix"`
|
||||
ServiceBusEndpointSuffix string `json:"serviceBusEndpointSuffix"`
|
||||
ServiceManagementVMDNSSuffix string `json:"serviceManagementVMDNSSuffix"`
|
||||
ResourceManagerVMDNSSuffix string `json:"resourceManagerVMDNSSuffix"`
|
||||
ContainerRegistryDNSSuffix string `json:"containerRegistryDNSSuffix"`
|
||||
TokenAudience string `json:"tokenAudience"`
|
||||
Name string `json:"name"`
|
||||
ManagementPortalURL string `json:"managementPortalURL"`
|
||||
PublishSettingsURL string `json:"publishSettingsURL"`
|
||||
ServiceManagementEndpoint string `json:"serviceManagementEndpoint"`
|
||||
ResourceManagerEndpoint string `json:"resourceManagerEndpoint"`
|
||||
ActiveDirectoryEndpoint string `json:"activeDirectoryEndpoint"`
|
||||
GalleryEndpoint string `json:"galleryEndpoint"`
|
||||
KeyVaultEndpoint string `json:"keyVaultEndpoint"`
|
||||
GraphEndpoint string `json:"graphEndpoint"`
|
||||
ServiceBusEndpoint string `json:"serviceBusEndpoint"`
|
||||
BatchManagementEndpoint string `json:"batchManagementEndpoint"`
|
||||
StorageEndpointSuffix string `json:"storageEndpointSuffix"`
|
||||
SQLDatabaseDNSSuffix string `json:"sqlDatabaseDNSSuffix"`
|
||||
TrafficManagerDNSSuffix string `json:"trafficManagerDNSSuffix"`
|
||||
KeyVaultDNSSuffix string `json:"keyVaultDNSSuffix"`
|
||||
ServiceBusEndpointSuffix string `json:"serviceBusEndpointSuffix"`
|
||||
ServiceManagementVMDNSSuffix string `json:"serviceManagementVMDNSSuffix"`
|
||||
ResourceManagerVMDNSSuffix string `json:"resourceManagerVMDNSSuffix"`
|
||||
ContainerRegistryDNSSuffix string `json:"containerRegistryDNSSuffix"`
|
||||
CosmosDBDNSSuffix string `json:"cosmosDBDNSSuffix"`
|
||||
TokenAudience string `json:"tokenAudience"`
|
||||
ResourceIdentifiers ResourceIdentifier `json:"resourceIdentifiers"`
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -79,7 +96,16 @@ var (
|
||||
ServiceManagementVMDNSSuffix: "cloudapp.net",
|
||||
ResourceManagerVMDNSSuffix: "cloudapp.azure.com",
|
||||
ContainerRegistryDNSSuffix: "azurecr.io",
|
||||
CosmosDBDNSSuffix: "documents.azure.com",
|
||||
TokenAudience: "https://management.azure.com/",
|
||||
ResourceIdentifiers: ResourceIdentifier{
|
||||
Graph: "https://graph.windows.net/",
|
||||
KeyVault: "https://vault.azure.net",
|
||||
Datalake: "https://datalake.azure.net/",
|
||||
Batch: "https://batch.core.windows.net/",
|
||||
OperationalInsights: "https://api.loganalytics.io",
|
||||
Storage: "https://storage.azure.com/",
|
||||
},
|
||||
}
|
||||
|
||||
// USGovernmentCloud is the cloud environment for the US Government
|
||||
@@ -102,8 +128,17 @@ var (
|
||||
ServiceBusEndpointSuffix: "servicebus.usgovcloudapi.net",
|
||||
ServiceManagementVMDNSSuffix: "usgovcloudapp.net",
|
||||
ResourceManagerVMDNSSuffix: "cloudapp.windowsazure.us",
|
||||
ContainerRegistryDNSSuffix: "azurecr.io",
|
||||
ContainerRegistryDNSSuffix: "azurecr.us",
|
||||
CosmosDBDNSSuffix: "documents.azure.us",
|
||||
TokenAudience: "https://management.usgovcloudapi.net/",
|
||||
ResourceIdentifiers: ResourceIdentifier{
|
||||
Graph: "https://graph.windows.net/",
|
||||
KeyVault: "https://vault.usgovcloudapi.net",
|
||||
Datalake: NotAvailable,
|
||||
Batch: "https://batch.core.usgovcloudapi.net/",
|
||||
OperationalInsights: "https://api.loganalytics.us",
|
||||
Storage: "https://storage.azure.com/",
|
||||
},
|
||||
}
|
||||
|
||||
// ChinaCloud is the cloud environment operated in China
|
||||
@@ -126,8 +161,17 @@ var (
|
||||
ServiceBusEndpointSuffix: "servicebus.chinacloudapi.cn",
|
||||
ServiceManagementVMDNSSuffix: "chinacloudapp.cn",
|
||||
ResourceManagerVMDNSSuffix: "cloudapp.azure.cn",
|
||||
ContainerRegistryDNSSuffix: "azurecr.io",
|
||||
ContainerRegistryDNSSuffix: "azurecr.cn",
|
||||
CosmosDBDNSSuffix: "documents.azure.cn",
|
||||
TokenAudience: "https://management.chinacloudapi.cn/",
|
||||
ResourceIdentifiers: ResourceIdentifier{
|
||||
Graph: "https://graph.chinacloudapi.cn/",
|
||||
KeyVault: "https://vault.azure.cn",
|
||||
Datalake: NotAvailable,
|
||||
Batch: "https://batch.chinacloudapi.cn/",
|
||||
OperationalInsights: NotAvailable,
|
||||
Storage: "https://storage.azure.com/",
|
||||
},
|
||||
}
|
||||
|
||||
// GermanCloud is the cloud environment operated in Germany
|
||||
@@ -150,8 +194,17 @@ var (
|
||||
ServiceBusEndpointSuffix: "servicebus.cloudapi.de",
|
||||
ServiceManagementVMDNSSuffix: "azurecloudapp.de",
|
||||
ResourceManagerVMDNSSuffix: "cloudapp.microsoftazure.de",
|
||||
ContainerRegistryDNSSuffix: "azurecr.io",
|
||||
ContainerRegistryDNSSuffix: NotAvailable,
|
||||
CosmosDBDNSSuffix: "documents.microsoftazure.de",
|
||||
TokenAudience: "https://management.microsoftazure.de/",
|
||||
ResourceIdentifiers: ResourceIdentifier{
|
||||
Graph: "https://graph.cloudapi.de/",
|
||||
KeyVault: "https://vault.microsoftazure.de",
|
||||
Datalake: NotAvailable,
|
||||
Batch: "https://batch.cloudapi.de/",
|
||||
OperationalInsights: NotAvailable,
|
||||
Storage: "https://storage.azure.com/",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
8
vendor/github.com/Azure/go-autorest/autorest/azure/rp.go
generated
vendored
8
vendor/github.com/Azure/go-autorest/autorest/azure/rp.go
generated
vendored
@@ -64,7 +64,7 @@ func DoRetryWithRegistration(client autorest.Client) autorest.SendDecorator {
|
||||
}
|
||||
}
|
||||
}
|
||||
return resp, fmt.Errorf("failed request: %s", err)
|
||||
return resp, err
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -140,8 +140,8 @@ func register(client autorest.Client, originalReq *http.Request, re RequestError
|
||||
}
|
||||
|
||||
// poll for registered provisioning state
|
||||
now := time.Now()
|
||||
for err == nil && time.Since(now) < client.PollingDuration {
|
||||
registrationStartTime := time.Now()
|
||||
for err == nil && (client.PollingDuration == 0 || (client.PollingDuration != 0 && time.Since(registrationStartTime) < client.PollingDuration)) {
|
||||
// taken from the resources SDK
|
||||
// https://github.com/Azure/azure-sdk-for-go/blob/9f366792afa3e0ddaecdc860e793ba9d75e76c27/arm/resources/resources/providers.go#L45
|
||||
preparer := autorest.CreatePreparer(
|
||||
@@ -183,7 +183,7 @@ func register(client autorest.Client, originalReq *http.Request, re RequestError
|
||||
return originalReq.Context().Err()
|
||||
}
|
||||
}
|
||||
if !(time.Since(now) < client.PollingDuration) {
|
||||
if client.PollingDuration != 0 && !(time.Since(registrationStartTime) < client.PollingDuration) {
|
||||
return errors.New("polling for resource provider registration has exceeded the polling duration")
|
||||
}
|
||||
return err
|
||||
|
92
vendor/github.com/Azure/go-autorest/autorest/client.go
generated
vendored
92
vendor/github.com/Azure/go-autorest/autorest/client.go
generated
vendored
@@ -16,14 +16,18 @@ package autorest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/logger"
|
||||
"github.com/Azure/go-autorest/tracing"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -41,15 +45,6 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// defaultUserAgent builds a string containing the Go version, system archityecture and OS,
|
||||
// and the go-autorest version.
|
||||
defaultUserAgent = fmt.Sprintf("Go/%s (%s-%s) go-autorest/%s",
|
||||
runtime.Version(),
|
||||
runtime.GOARCH,
|
||||
runtime.GOOS,
|
||||
Version(),
|
||||
)
|
||||
|
||||
// StatusCodesForRetry are a defined group of status code for which the client will retry
|
||||
StatusCodesForRetry = []int{
|
||||
http.StatusRequestTimeout, // 408
|
||||
@@ -78,6 +73,22 @@ type Response struct {
|
||||
*http.Response `json:"-"`
|
||||
}
|
||||
|
||||
// IsHTTPStatus returns true if the returned HTTP status code matches the provided status code.
|
||||
// If there was no response (i.e. the underlying http.Response is nil) the return value is false.
|
||||
func (r Response) IsHTTPStatus(statusCode int) bool {
|
||||
if r.Response == nil {
|
||||
return false
|
||||
}
|
||||
return r.Response.StatusCode == statusCode
|
||||
}
|
||||
|
||||
// HasHTTPStatus returns true if the returned HTTP status code matches one of the provided status codes.
|
||||
// If there was no response (i.e. the underlying http.Response is nil) or not status codes are provided
|
||||
// the return value is false.
|
||||
func (r Response) HasHTTPStatus(statusCodes ...int) bool {
|
||||
return ResponseHasStatusCode(r.Response, statusCodes...)
|
||||
}
|
||||
|
||||
// LoggingInspector implements request and response inspectors that log the full request and
|
||||
// response to a supplied log.
|
||||
type LoggingInspector struct {
|
||||
@@ -153,6 +164,7 @@ type Client struct {
|
||||
PollingDelay time.Duration
|
||||
|
||||
// PollingDuration sets the maximum polling time after which an error is returned.
|
||||
// Setting this to zero will use the provided context to control the duration.
|
||||
PollingDuration time.Duration
|
||||
|
||||
// RetryAttempts sets the default number of retry attempts for client.
|
||||
@@ -174,14 +186,32 @@ type Client struct {
|
||||
// NewClientWithUserAgent returns an instance of a Client with the UserAgent set to the passed
|
||||
// string.
|
||||
func NewClientWithUserAgent(ua string) Client {
|
||||
return newClient(ua, tls.RenegotiateNever)
|
||||
}
|
||||
|
||||
// ClientOptions contains various Client configuration options.
|
||||
type ClientOptions struct {
|
||||
// UserAgent is an optional user-agent string to append to the default user agent.
|
||||
UserAgent string
|
||||
|
||||
// Renegotiation is an optional setting to control client-side TLS renegotiation.
|
||||
Renegotiation tls.RenegotiationSupport
|
||||
}
|
||||
|
||||
// NewClientWithOptions returns an instance of a Client with the specified values.
|
||||
func NewClientWithOptions(options ClientOptions) Client {
|
||||
return newClient(options.UserAgent, options.Renegotiation)
|
||||
}
|
||||
|
||||
func newClient(ua string, renegotiation tls.RenegotiationSupport) Client {
|
||||
c := Client{
|
||||
PollingDelay: DefaultPollingDelay,
|
||||
PollingDuration: DefaultPollingDuration,
|
||||
RetryAttempts: DefaultRetryAttempts,
|
||||
RetryDuration: DefaultRetryDuration,
|
||||
UserAgent: defaultUserAgent,
|
||||
UserAgent: UserAgent(),
|
||||
}
|
||||
c.Sender = c.sender()
|
||||
c.Sender = c.sender(renegotiation)
|
||||
c.AddToUserAgent(ua)
|
||||
return c
|
||||
}
|
||||
@@ -216,18 +246,48 @@ func (c Client) Do(r *http.Request) (*http.Response, error) {
|
||||
}
|
||||
return resp, NewErrorWithError(err, "autorest/Client", "Do", nil, "Preparing request failed")
|
||||
}
|
||||
|
||||
resp, err := SendWithSender(c.sender(), r)
|
||||
logger.Instance.WriteRequest(r, logger.Filter{
|
||||
Header: func(k string, v []string) (bool, []string) {
|
||||
// remove the auth token from the log
|
||||
if strings.EqualFold(k, "Authorization") || strings.EqualFold(k, "Ocp-Apim-Subscription-Key") {
|
||||
v = []string{"**REDACTED**"}
|
||||
}
|
||||
return true, v
|
||||
},
|
||||
})
|
||||
resp, err := SendWithSender(c.sender(tls.RenegotiateNever), r)
|
||||
logger.Instance.WriteResponse(resp, logger.Filter{})
|
||||
Respond(resp, c.ByInspecting())
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// sender returns the Sender to which to send requests.
|
||||
func (c Client) sender() Sender {
|
||||
func (c Client) sender(renengotiation tls.RenegotiationSupport) Sender {
|
||||
if c.Sender == nil {
|
||||
// Use behaviour compatible with DefaultTransport, but require TLS minimum version.
|
||||
var defaultTransport = http.DefaultTransport.(*http.Transport)
|
||||
transport := tracing.Transport
|
||||
// for non-default values of TLS renegotiation create a new tracing transport.
|
||||
// updating tracing.Transport affects all clients which is not what we want.
|
||||
if renengotiation != tls.RenegotiateNever {
|
||||
transport = tracing.NewTransport()
|
||||
}
|
||||
transport.Base = &http.Transport{
|
||||
Proxy: defaultTransport.Proxy,
|
||||
DialContext: defaultTransport.DialContext,
|
||||
MaxIdleConns: defaultTransport.MaxIdleConns,
|
||||
IdleConnTimeout: defaultTransport.IdleConnTimeout,
|
||||
TLSHandshakeTimeout: defaultTransport.TLSHandshakeTimeout,
|
||||
ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout,
|
||||
TLSClientConfig: &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
Renegotiation: renengotiation,
|
||||
},
|
||||
}
|
||||
j, _ := cookiejar.New(nil)
|
||||
return &http.Client{Jar: j}
|
||||
return &http.Client{Jar: j, Transport: transport}
|
||||
}
|
||||
|
||||
return c.Sender
|
||||
}
|
||||
|
||||
|
74
vendor/github.com/Azure/go-autorest/autorest/preparer.go
generated
vendored
74
vendor/github.com/Azure/go-autorest/autorest/preparer.go
generated
vendored
@@ -16,7 +16,9 @@ package autorest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -31,11 +33,33 @@ const (
|
||||
mimeTypeOctetStream = "application/octet-stream"
|
||||
mimeTypeFormPost = "application/x-www-form-urlencoded"
|
||||
|
||||
headerAuthorization = "Authorization"
|
||||
headerContentType = "Content-Type"
|
||||
headerUserAgent = "User-Agent"
|
||||
headerAuthorization = "Authorization"
|
||||
headerAuxAuthorization = "x-ms-authorization-auxiliary"
|
||||
headerContentType = "Content-Type"
|
||||
headerUserAgent = "User-Agent"
|
||||
)
|
||||
|
||||
// used as a key type in context.WithValue()
|
||||
type ctxPrepareDecorators struct{}
|
||||
|
||||
// WithPrepareDecorators adds the specified PrepareDecorators to the provided context.
|
||||
// If no PrepareDecorators are provided the context is unchanged.
|
||||
func WithPrepareDecorators(ctx context.Context, prepareDecorator []PrepareDecorator) context.Context {
|
||||
if len(prepareDecorator) == 0 {
|
||||
return ctx
|
||||
}
|
||||
return context.WithValue(ctx, ctxPrepareDecorators{}, prepareDecorator)
|
||||
}
|
||||
|
||||
// GetPrepareDecorators returns the PrepareDecorators in the provided context or the provided default PrepareDecorators.
|
||||
func GetPrepareDecorators(ctx context.Context, defaultPrepareDecorators ...PrepareDecorator) []PrepareDecorator {
|
||||
inCtx := ctx.Value(ctxPrepareDecorators{})
|
||||
if pd, ok := inCtx.([]PrepareDecorator); ok {
|
||||
return pd
|
||||
}
|
||||
return defaultPrepareDecorators
|
||||
}
|
||||
|
||||
// Preparer is the interface that wraps the Prepare method.
|
||||
//
|
||||
// Prepare accepts and possibly modifies an http.Request (e.g., adding Headers). Implementations
|
||||
@@ -190,6 +214,9 @@ func AsGet() PrepareDecorator { return WithMethod("GET") }
|
||||
// AsHead returns a PrepareDecorator that sets the HTTP method to HEAD.
|
||||
func AsHead() PrepareDecorator { return WithMethod("HEAD") }
|
||||
|
||||
// AsMerge returns a PrepareDecorator that sets the HTTP method to MERGE.
|
||||
func AsMerge() PrepareDecorator { return WithMethod("MERGE") }
|
||||
|
||||
// AsOptions returns a PrepareDecorator that sets the HTTP method to OPTIONS.
|
||||
func AsOptions() PrepareDecorator { return WithMethod("OPTIONS") }
|
||||
|
||||
@@ -225,6 +252,25 @@ func WithBaseURL(baseURL string) PrepareDecorator {
|
||||
}
|
||||
}
|
||||
|
||||
// WithBytes returns a PrepareDecorator that takes a list of bytes
|
||||
// which passes the bytes directly to the body
|
||||
func WithBytes(input *[]byte) PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err == nil {
|
||||
if input == nil {
|
||||
return r, fmt.Errorf("Input Bytes was nil")
|
||||
}
|
||||
|
||||
r.ContentLength = int64(len(*input))
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(*input))
|
||||
}
|
||||
return r, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithCustomBaseURL returns a PrepareDecorator that replaces brace-enclosed keys within the
|
||||
// request base URL (i.e., http.Request.URL) with the corresponding values from the passed map.
|
||||
func WithCustomBaseURL(baseURL string, urlParameters map[string]interface{}) PrepareDecorator {
|
||||
@@ -377,6 +423,28 @@ func WithJSON(v interface{}) PrepareDecorator {
|
||||
}
|
||||
}
|
||||
|
||||
// WithXML returns a PrepareDecorator that encodes the data passed as XML into the body of the
|
||||
// request and sets the Content-Length header.
|
||||
func WithXML(v interface{}) PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err == nil {
|
||||
b, err := xml.Marshal(v)
|
||||
if err == nil {
|
||||
// we have to tack on an XML header
|
||||
withHeader := xml.Header + string(b)
|
||||
bytesWithHeader := []byte(withHeader)
|
||||
|
||||
r.ContentLength = int64(len(bytesWithHeader))
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(bytesWithHeader))
|
||||
}
|
||||
}
|
||||
return r, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithPath returns a PrepareDecorator that adds the supplied path to the request URL. If the path
|
||||
// is absolute (that is, it begins with a "/"), it replaces the existing path.
|
||||
func WithPath(path string) PrepareDecorator {
|
||||
|
19
vendor/github.com/Azure/go-autorest/autorest/responder.go
generated
vendored
19
vendor/github.com/Azure/go-autorest/autorest/responder.go
generated
vendored
@@ -153,6 +153,25 @@ func ByClosingIfError() RespondDecorator {
|
||||
}
|
||||
}
|
||||
|
||||
// ByUnmarshallingBytes returns a RespondDecorator that copies the Bytes returned in the
|
||||
// response Body into the value pointed to by v.
|
||||
func ByUnmarshallingBytes(v *[]byte) RespondDecorator {
|
||||
return func(r Responder) Responder {
|
||||
return ResponderFunc(func(resp *http.Response) error {
|
||||
err := r.Respond(resp)
|
||||
if err == nil {
|
||||
bytes, errInner := ioutil.ReadAll(resp.Body)
|
||||
if errInner != nil {
|
||||
err = fmt.Errorf("Error occurred reading http.Response#Body - Error = '%v'", errInner)
|
||||
} else {
|
||||
*v = bytes
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ByUnmarshallingJSON returns a RespondDecorator that decodes a JSON document returned in the
|
||||
// response Body into the value pointed to by v.
|
||||
func ByUnmarshallingJSON(v interface{}) RespondDecorator {
|
||||
|
137
vendor/github.com/Azure/go-autorest/autorest/sender.go
generated
vendored
137
vendor/github.com/Azure/go-autorest/autorest/sender.go
generated
vendored
@@ -15,14 +15,38 @@ package autorest
|
||||
// limitations under the License.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/tracing"
|
||||
)
|
||||
|
||||
// used as a key type in context.WithValue()
|
||||
type ctxSendDecorators struct{}
|
||||
|
||||
// WithSendDecorators adds the specified SendDecorators to the provided context.
|
||||
// If no SendDecorators are provided the context is unchanged.
|
||||
func WithSendDecorators(ctx context.Context, sendDecorator []SendDecorator) context.Context {
|
||||
if len(sendDecorator) == 0 {
|
||||
return ctx
|
||||
}
|
||||
return context.WithValue(ctx, ctxSendDecorators{}, sendDecorator)
|
||||
}
|
||||
|
||||
// GetSendDecorators returns the SendDecorators in the provided context or the provided default SendDecorators.
|
||||
func GetSendDecorators(ctx context.Context, defaultSendDecorators ...SendDecorator) []SendDecorator {
|
||||
inCtx := ctx.Value(ctxSendDecorators{})
|
||||
if sd, ok := inCtx.([]SendDecorator); ok {
|
||||
return sd
|
||||
}
|
||||
return defaultSendDecorators
|
||||
}
|
||||
|
||||
// Sender is the interface that wraps the Do method to send HTTP requests.
|
||||
//
|
||||
// The standard http.Client conforms to this interface.
|
||||
@@ -38,7 +62,7 @@ func (sf SenderFunc) Do(r *http.Request) (*http.Response, error) {
|
||||
return sf(r)
|
||||
}
|
||||
|
||||
// SendDecorator takes and possibily decorates, by wrapping, a Sender. Decorators may affect the
|
||||
// SendDecorator takes and possibly decorates, by wrapping, a Sender. Decorators may affect the
|
||||
// http.Request and pass it along or, first, pass the http.Request along then react to the
|
||||
// http.Response result.
|
||||
type SendDecorator func(Sender) Sender
|
||||
@@ -68,7 +92,7 @@ func DecorateSender(s Sender, decorators ...SendDecorator) Sender {
|
||||
//
|
||||
// Send will not poll or retry requests.
|
||||
func Send(r *http.Request, decorators ...SendDecorator) (*http.Response, error) {
|
||||
return SendWithSender(&http.Client{}, r, decorators...)
|
||||
return SendWithSender(&http.Client{Transport: tracing.Transport}, r, decorators...)
|
||||
}
|
||||
|
||||
// SendWithSender sends the passed http.Request, through the provided Sender, returning the
|
||||
@@ -209,50 +233,77 @@ func DoRetryForAttempts(attempts int, backoff time.Duration) SendDecorator {
|
||||
|
||||
// DoRetryForStatusCodes returns a SendDecorator that retries for specified statusCodes for up to the specified
|
||||
// number of attempts, exponentially backing off between requests using the supplied backoff
|
||||
// time.Duration (which may be zero). Retrying may be canceled by closing the optional channel on
|
||||
// the http.Request.
|
||||
// time.Duration (which may be zero). Retrying may be canceled by cancelling the context on the http.Request.
|
||||
// NOTE: Code http.StatusTooManyRequests (429) will *not* be counted against the number of attempts.
|
||||
func DoRetryForStatusCodes(attempts int, backoff time.Duration, codes ...int) SendDecorator {
|
||||
return func(s Sender) Sender {
|
||||
return SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
|
||||
rr := NewRetriableRequest(r)
|
||||
// Increment to add the first call (attempts denotes number of retries)
|
||||
attempts++
|
||||
for attempt := 0; attempt < attempts; {
|
||||
err = rr.Prepare()
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
resp, err = s.Do(rr.Request())
|
||||
// we want to retry if err is not nil (e.g. transient network failure). note that for failed authentication
|
||||
// resp and err will both have a value, so in this case we don't want to retry as it will never succeed.
|
||||
if err == nil && !ResponseHasStatusCode(resp, codes...) || IsTokenRefreshError(err) {
|
||||
return resp, err
|
||||
}
|
||||
delayed := DelayWithRetryAfter(resp, r.Context().Done())
|
||||
if !delayed && !DelayForBackoff(backoff, attempt, r.Context().Done()) {
|
||||
return nil, r.Context().Err()
|
||||
}
|
||||
// don't count a 429 against the number of attempts
|
||||
// so that we continue to retry until it succeeds
|
||||
if resp == nil || resp.StatusCode != http.StatusTooManyRequests {
|
||||
attempt++
|
||||
}
|
||||
}
|
||||
return resp, err
|
||||
return SenderFunc(func(r *http.Request) (*http.Response, error) {
|
||||
return doRetryForStatusCodesImpl(s, r, false, attempts, backoff, 0, codes...)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// DelayWithRetryAfter invokes time.After for the duration specified in the "Retry-After" header in
|
||||
// responses with status code 429
|
||||
// DoRetryForStatusCodesWithCap returns a SendDecorator that retries for specified statusCodes for up to the
|
||||
// specified number of attempts, exponentially backing off between requests using the supplied backoff
|
||||
// time.Duration (which may be zero). To cap the maximum possible delay between iterations specify a value greater
|
||||
// than zero for cap. Retrying may be canceled by cancelling the context on the http.Request.
|
||||
func DoRetryForStatusCodesWithCap(attempts int, backoff, cap time.Duration, codes ...int) SendDecorator {
|
||||
return func(s Sender) Sender {
|
||||
return SenderFunc(func(r *http.Request) (*http.Response, error) {
|
||||
return doRetryForStatusCodesImpl(s, r, true, attempts, backoff, cap, codes...)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func doRetryForStatusCodesImpl(s Sender, r *http.Request, count429 bool, attempts int, backoff, cap time.Duration, codes ...int) (resp *http.Response, err error) {
|
||||
rr := NewRetriableRequest(r)
|
||||
// Increment to add the first call (attempts denotes number of retries)
|
||||
for attempt := 0; attempt < attempts+1; {
|
||||
err = rr.Prepare()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp, err = s.Do(rr.Request())
|
||||
// if the error isn't temporary don't bother retrying
|
||||
if err != nil && !IsTemporaryNetworkError(err) {
|
||||
return
|
||||
}
|
||||
// we want to retry if err is not nil (e.g. transient network failure). note that for failed authentication
|
||||
// resp and err will both have a value, so in this case we don't want to retry as it will never succeed.
|
||||
if err == nil && !ResponseHasStatusCode(resp, codes...) || IsTokenRefreshError(err) {
|
||||
return resp, err
|
||||
}
|
||||
delayed := DelayWithRetryAfter(resp, r.Context().Done())
|
||||
if !delayed && !DelayForBackoffWithCap(backoff, cap, attempt, r.Context().Done()) {
|
||||
return resp, r.Context().Err()
|
||||
}
|
||||
// when count429 == false don't count a 429 against the number
|
||||
// of attempts so that we continue to retry until it succeeds
|
||||
if count429 || (resp == nil || resp.StatusCode != http.StatusTooManyRequests) {
|
||||
attempt++
|
||||
}
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// DelayWithRetryAfter invokes time.After for the duration specified in the "Retry-After" header.
|
||||
// The value of Retry-After can be either the number of seconds or a date in RFC1123 format.
|
||||
// The function returns true after successfully waiting for the specified duration. If there is
|
||||
// no Retry-After header or the wait is cancelled the return value is false.
|
||||
func DelayWithRetryAfter(resp *http.Response, cancel <-chan struct{}) bool {
|
||||
if resp == nil {
|
||||
return false
|
||||
}
|
||||
retryAfter, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
|
||||
if resp.StatusCode == http.StatusTooManyRequests && retryAfter > 0 {
|
||||
var dur time.Duration
|
||||
ra := resp.Header.Get("Retry-After")
|
||||
if retryAfter, _ := strconv.Atoi(ra); retryAfter > 0 {
|
||||
dur = time.Duration(retryAfter) * time.Second
|
||||
} else if t, err := time.Parse(time.RFC1123, ra); err == nil {
|
||||
dur = t.Sub(time.Now())
|
||||
}
|
||||
if dur > 0 {
|
||||
select {
|
||||
case <-time.After(time.Duration(retryAfter) * time.Second):
|
||||
case <-time.After(dur):
|
||||
return true
|
||||
case <-cancel:
|
||||
return false
|
||||
@@ -312,8 +363,22 @@ func WithLogging(logger *log.Logger) SendDecorator {
|
||||
// Note: Passing attempt 1 will result in doubling "backoff" duration. Treat this as a zero-based attempt
|
||||
// count.
|
||||
func DelayForBackoff(backoff time.Duration, attempt int, cancel <-chan struct{}) bool {
|
||||
return DelayForBackoffWithCap(backoff, 0, attempt, cancel)
|
||||
}
|
||||
|
||||
// DelayForBackoffWithCap invokes time.After for the supplied backoff duration raised to the power of
|
||||
// passed attempt (i.e., an exponential backoff delay). Backoff duration is in seconds and can set
|
||||
// to zero for no delay. To cap the maximum possible delay specify a value greater than zero for cap.
|
||||
// The delay may be canceled by closing the passed channel. If terminated early, returns false.
|
||||
// Note: Passing attempt 1 will result in doubling "backoff" duration. Treat this as a zero-based attempt
|
||||
// count.
|
||||
func DelayForBackoffWithCap(backoff, cap time.Duration, attempt int, cancel <-chan struct{}) bool {
|
||||
d := time.Duration(backoff.Seconds()*math.Pow(2, float64(attempt))) * time.Second
|
||||
if cap > 0 && d > cap {
|
||||
d = cap
|
||||
}
|
||||
select {
|
||||
case <-time.After(time.Duration(backoff.Seconds()*math.Pow(2, float64(attempt))) * time.Second):
|
||||
case <-time.After(d):
|
||||
return true
|
||||
case <-cancel:
|
||||
return false
|
||||
|
5
vendor/github.com/Azure/go-autorest/autorest/to/convert.go
generated
vendored
5
vendor/github.com/Azure/go-autorest/autorest/to/convert.go
generated
vendored
@@ -145,3 +145,8 @@ func Float64(i *float64) float64 {
|
||||
func Float64Ptr(i float64) *float64 {
|
||||
return &i
|
||||
}
|
||||
|
||||
// ByteSlicePtr returns a pointer to the passed byte slice.
|
||||
func ByteSlicePtr(b []byte) *[]byte {
|
||||
return &b
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user