Compare commits

...

10 Commits

Author SHA1 Message Date
Ludovic Fernandez
9da0bcf3aa Prepare release v1.7.12 2019-05-29 20:42:06 +02:00
Ludovic Fernandez
51287d9316 Remove authentication hashes from API 2019-05-29 19:22:07 +02:00
Ludovic Fernandez
29307fe9fa fix: update-lego. 2019-05-28 17:32:05 +02:00
ravilr
52772c9642 fix kubernetes template for backend responseforwarding flushinterval setting 2019-05-23 18:14:05 +02:00
ravilr
8f135fdb0a Add missing callback on close of hijacked connections 2019-05-23 09:40:04 +02:00
Ludovic Fernandez
7722a41270 Adds a log fields documentation. 2019-05-21 17:02:11 +02:00
Alex Antonov
cd11b1aab4 Upgraded DataDog tracing library to 1.13.0 2019-05-15 18:04:06 +02:00
Ludovic Fernandez
e7564d4cf8 Enhance KV logs. 2019-05-15 17:20:08 +02:00
Wenxuan Zhao
cc130fb673 Allow SANs for wildcards domain. (#4821) 2019-05-03 18:08:37 +02:00
Greg Berns
4106cf647b Docs: Troubleshooting help for Docker Swarm labels 2019-05-03 10:28:05 +02:00
54 changed files with 2571 additions and 192 deletions

View File

@@ -1,5 +1,21 @@
# Change Log
## [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)
**Bug fixes:**
- **[acme]** Allow SANs for wildcards domain. ([#4821](https://github.com/containous/traefik/pull/4821) by [vizv](https://github.com/vizv))
- **[acme]** fix: update lego. ([#4910](https://github.com/containous/traefik/pull/4910) by [ldez](https://github.com/ldez))
- **[api,authentication]** Remove authentication hashes from API ([#4918](https://github.com/containous/traefik/pull/4918) by [ldez](https://github.com/ldez))
- **[consul]** Enhance KV logs. ([#4877](https://github.com/containous/traefik/pull/4877) by [ldez](https://github.com/ldez))
- **[k8s]** Fix kubernetes template for backend responseforwarding flushinterval setting ([#4901](https://github.com/containous/traefik/pull/4901) by [ravilr](https://github.com/ravilr))
- **[metrics]** Upgraded DataDog tracing library to 1.13.0 ([#4878](https://github.com/containous/traefik/pull/4878) by [aantono](https://github.com/aantono))
- **[server]** Add missing callback on close of hijacked connections ([#4900](https://github.com/containous/traefik/pull/4900) by [ravilr](https://github.com/ravilr))
**Documentation:**
- **[docker]** Docs: Troubleshooting help for Docker Swarm labels ([#4751](https://github.com/containous/traefik/pull/4751) by [gregberns](https://github.com/gregberns))
- **[logs]** Adds a log fields documentation. ([#4890](https://github.com/containous/traefik/pull/4890) by [ldez](https://github.com/ldez))
## [v1.7.11](https://github.com/containous/traefik/tree/v1.7.11) (2019-04-26)
[All Commits](https://github.com/containous/traefik/compare/v1.7.10...v1.7.11)

View File

@@ -158,7 +158,7 @@ Integration tests must be run from the `integration/` directory and require the
## Documentation
The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/)
The [documentation site](https://docs.traefik.io/) is built with [mkdocs](https://mkdocs.org/)
### Building Documentation

53
Gopkg.lock generated
View File

@@ -735,7 +735,7 @@
revision = "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
[[projects]]
digest = "1:68a7713d996a30a8394715220e779bbcbc880a18b1e0ab0e12fd1fbbf9c711e6"
digest = "1:d82b2dc81c551e7c15f31523a2cc8ee9121b39cfbf63174d98a0bc8edf2d3c5e"
name = "github.com/go-acme/lego"
packages = [
"acme",
@@ -759,6 +759,7 @@
"providers/dns/alidns",
"providers/dns/auroradns",
"providers/dns/azure",
"providers/dns/bindman",
"providers/dns/bluecat",
"providers/dns/cloudflare",
"providers/dns/cloudns",
@@ -777,6 +778,7 @@
"providers/dns/dreamhost",
"providers/dns/duckdns",
"providers/dns/dyn",
"providers/dns/easydns",
"providers/dns/exec",
"providers/dns/exoscale",
"providers/dns/fastdns",
@@ -789,6 +791,7 @@
"providers/dns/httpreq",
"providers/dns/iij",
"providers/dns/inwx",
"providers/dns/joker",
"providers/dns/lightsail",
"providers/dns/linode",
"providers/dns/linodev4",
@@ -820,8 +823,8 @@
"registration",
]
pruneopts = "NUT"
revision = "3d13faf68920543a393ad6cdfdea429627af2d34"
version = "v2.5.0"
revision = "01903cdfb9869df45cf5274c53226823a2532f2d"
version = "v2.6.0"
[[projects]]
branch = "fork-containous"
@@ -832,6 +835,14 @@
revision = "ca0bf163426aa183d03fd4949101785c0347f273"
source = "github.com/containous/check"
[[projects]]
digest = "1:ea1d5bfdb4ec5c2ee48c97865e6de1a28fa8c4849a3f56b27d521aa619038e06"
name = "github.com/go-errors/errors"
packages = ["."]
pruneopts = "NUT"
revision = "a6af135bd4e28680facf08a3d206b454abc877a4"
version = "v1.0.1"
[[projects]]
digest = "1:5e92676b56ce4c69edf9ee1f6343c56f637e30af11b9d8b5edd1b6530f3fbc3d"
name = "github.com/go-ini/ini"
@@ -1218,6 +1229,25 @@
pruneopts = "NUT"
revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0"
[[projects]]
digest = "1:1082aeb059ff66b4fb6da53f9e7591726c6a81901f05ce48a470091784b23914"
name = "github.com/labbsr0x/bindman-dns-webhook"
packages = [
"src/client",
"src/types",
]
pruneopts = "NUT"
revision = "234ca2a50eebc2095f42a884709a6e9013366d86"
version = "v1.0.0"
[[projects]]
branch = "master"
digest = "1:ad2a63b2d6dfe7d66bf14c01f1171a3951abef6e0fb136170359c3f7c4f51615"
name = "github.com/labbsr0x/goh"
packages = ["gohclient"]
pruneopts = "NUT"
revision = "94bcf1cb07b70b26b72ad54b2b050bcd0a66a9c8"
[[projects]]
branch = "master"
digest = "1:5a96e1f04259484b3dd183ca95d1e7bff768b1bab36c530e308a8d56243b50c7"
@@ -1838,7 +1868,7 @@
[[projects]]
branch = "master"
digest = "1:c878a802780168c80738d74607d14e7cb8765706990ae1260a3fd271c2c3b133"
digest = "1:86f14aadf288fe3ad8ac060bcb2b5083cec3829dd883803486ec834d031060c9"
name = "github.com/vulcand/oxy"
packages = [
"buffer",
@@ -1851,7 +1881,7 @@
"utils",
]
pruneopts = "NUT"
revision = "c34b0c501e43223bc816ac9b40b0ac29c44c8952"
revision = "0d102f45103cf49a95b5c6e810e092973cbcb68c"
[[projects]]
digest = "1:ca6bac407fedc14fbeeba861dd33a821ba3a1624c10126ec6003b0a28d4139c5"
@@ -2023,7 +2053,8 @@
revision = "8be79e1e0910c292df4e79c241bb7e8f7e725959"
[[projects]]
digest = "1:887c07769ee52c81222ea1112b11a3947c703da529d1643d85c216b9537d87b8"
branch = "master"
digest = "1:70c173b8ecc111dd01dc07f0ada72c076e4ed91618ee559312ef8adf154cc539"
name = "google.golang.org/api"
packages = [
"dns/v1",
@@ -2037,8 +2068,7 @@
"transport/http/internal/propagation",
]
pruneopts = "NUT"
revision = "0cbcb99a9ea0c8023c794b2693cbe1def82ed4d7"
version = "v0.3.2"
revision = "bbbc0e98e3cc6ddcebca23f9ac35acda33bd5b38"
[[projects]]
digest = "1:7206d98ec77c90c72ec2c405181a1dcf86965803b6dbc4f98ceab7a5047c37a9"
@@ -2100,7 +2130,7 @@
version = "v1.12.0"
[[projects]]
digest = "1:b886012746f19e2a7c6c3901ea9f86e8a5e32ff2b4407086f4f3181269976957"
digest = "1:2d9005f36a1bd4f00c3fd501f87cd82909aa69b5fe15aee28636cced39c9ecfe"
name = "gopkg.in/DataDog/dd-trace-go.v1"
packages = [
"ddtrace",
@@ -2108,10 +2138,11 @@
"ddtrace/internal",
"ddtrace/opentracer",
"ddtrace/tracer",
"internal/globalconfig",
]
pruneopts = "NUT"
revision = "7fb2bce4b1ed6ab61f7a9e1be30dea56de19db7c"
version = "v1.8.0"
revision = "eed4d38387cb9c74f7a29cb3eb9b06155a09d259"
version = "v1.13.0"
[[projects]]
digest = "1:c970218a20933dd0a2eb2006de922217fa9276f57d25009b2a934eb1c50031cc"

View File

@@ -180,7 +180,7 @@
[[constraint]]
name = "github.com/go-acme/lego"
version = "2.4.0"
version = "2.6.0"
[[constraint]]
name = "google.golang.org/grpc"
@@ -255,7 +255,7 @@
[[constraint]]
name = "gopkg.in/DataDog/dd-trace-go.v1"
version = "1.7.0"
version = "1.13.0"
[[constraint]]
name = "github.com/google/uuid"

View File

@@ -751,12 +751,6 @@ func (a *ACME) getValidDomains(domains []string, wildcardAllowed bool) ([]string
return nil, fmt.Errorf("unable to generate a wildcard certificate for domain %q : ACME does not allow '*.*' wildcard domain", strings.Join(domains, ","))
}
}
for _, san := range domains[1:] {
if strings.HasPrefix(san, "*") {
return nil, fmt.Errorf("unable to generate a certificate for domains %q: SANs can not be a wildcard domain", strings.Join(domains, ","))
}
}
domains = fun.Map(types.CanonicalDomain, domains).([]string)
return domains, nil

View File

@@ -419,12 +419,12 @@ func TestAcme_getValidDomain(t *testing.T) {
expectedDomains: []string{"*.traefik.wtf", "traefik.wtf"},
},
{
desc: "unexpected SANs",
desc: "wildcard SANs",
domains: []string{"*.traefik.wtf", "*.acme.wtf"},
dnsChallenge: &acmeprovider.DNSChallenge{},
wildcardAllowed: true,
expectedErr: "unable to generate a certificate for domains \"*.traefik.wtf,*.acme.wtf\": SANs can not be a wildcard domain",
expectedDomains: nil,
expectedErr: "",
expectedDomains: []string{"*.traefik.wtf", "*.acme.wtf"},
},
}
for _, test := range testCases {

View File

@@ -1316,7 +1316,7 @@ var _templatesKubernetesTmpl = []byte(`[backends]
{{if $backend.ResponseForwarding }}
[backends."{{ $backendName }}".responseForwarding]
flushInterval = "{{ $backend.responseForwarding.FlushInterval }}"
flushInterval = "{{ $backend.ResponseForwarding.FlushInterval }}"
{{end}}
[backends."{{ $backendName }}".loadBalancer]

View File

@@ -282,6 +282,7 @@ For example, `CF_API_EMAIL_FILE=/run/secrets/traefik_cf-api-email` could be used
| [Alibaba Cloud](https://www.vultr.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 |
| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | `BLUECAT_SERVER_URL`, `BLUECAT_USER_NAME`, `BLUECAT_PASSWORD`, `BLUECAT_CONFIG_NAME`, `BLUECAT_DNS_VIEW` | Not tested yet |
| [ClouDNS](https://www.cloudns.net/) | `cloudns` | `CLOUDNS_AUTH_ID`, `CLOUDNS_AUTH_PASSWORD` | YES |
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CF_API_EMAIL`, `CF_API_KEY` - The `Global API Key` needs to be used, not the `Origin CA Key` | YES |
@@ -295,6 +296,7 @@ For example, `CF_API_EMAIL_FILE=/run/secrets/traefik_cf-api-email` could be used
| [DreamHost](https://www.dreamhost.com/) | `dreamhost` | `DREAMHOST_API_KEY` | YES |
| [Duck DNS](https://www.duckdns.org/) | `duckdns` | `DUCKDNS_TOKEN` | YES |
| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | Not tested yet |
| [EasyDNS](https://easydns.com/) | `easydns` | `EASYDNS_TOKEN`, `EASYDNS_KEY` | YES |
| External Program | `exec` | `EXEC_PATH` | YES |
| [Exoscale](https://www.exoscale.com) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` | YES |
| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | YES |
@@ -307,6 +309,7 @@ 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 |
| [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 |

View File

@@ -257,6 +257,20 @@ services:
traefik.docker.network: traefik
```
Required labels:
- `traefik.frontend.rule`
- `traefik.port` - Without this the debug logs will show this service is deliberately filtered out.
- `traefik.docker.network` - Without this a 504 may occur.
#### Troubleshooting
If service doesn't show up in the dashboard, check the debug logs to see if the port is missing:
`Filtering container without port, <SERVICE_NAME>: port label is missing, ...')`
If `504 Gateway Timeout` occurs and there are networks used, ensure that `traefik.docker.network` is defined.
The complete name is required, meaning if the network is internal the name needs to be `<project_name>_<network_name>`.
### Using Docker Compose
If you are intending to use only Docker Compose commands (e.g. `docker-compose up --scale whoami=2 -d`), labels should be under your service, otherwise they will be ignored.

View File

@@ -230,38 +230,38 @@ format = "json"
### List of all available fields
```ini
StartUTC
StartLocal
Duration
FrontendName
BackendName
BackendURL
BackendAddr
ClientAddr
ClientHost
ClientPort
ClientUsername
RequestAddr
RequestHost
RequestPort
RequestMethod
RequestPath
RequestProtocol
RequestLine
RequestContentSize
OriginDuration
OriginContentSize
OriginStatus
OriginStatusLine
DownstreamStatus
DownstreamStatusLine
DownstreamContentSize
RequestCount
GzipRatio
Overhead
RetryAttempts
```
| Field | Description |
|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `StartUTC` | The time at which request processing started. |
| `StartLocal` | The local time at which request processing started. |
| `Duration` | The total time taken by processing the response, including the origin server's time but not the log writing time. |
| `FrontendName` | The name of the Traefik frontend. |
| `BackendName` | The name of the Traefik backend. |
| `BackendURL` | The URL of the Traefik backend. |
| `BackendAddr` | The IP:port of the Traefik backend (extracted from `BackendURL`) |
| `ClientAddr` | The remote address in its original form (usually IP:port). |
| `ClientHost` | The remote IP address from which the client request was received. |
| `ClientPort` | The remote TCP port from which the client request was received. |
| `ClientUsername` | The username provided in the URL, if present. |
| `RequestAddr` | The HTTP Host header (usually IP:port). This is treated as not a header by the Go API. |
| `RequestHost` | The HTTP Host server name (not including port). |
| `RequestPort` | The TCP port from the HTTP Host. |
| `RequestMethod` | The HTTP method. |
| `RequestPath` | The HTTP request URI, not including the scheme, host or port. |
| `RequestProtocol` | The version of HTTP requested. |
| `RequestLine` | `RequestMethod` + `RequestPath` + `RequestProtocol` |
| `RequestContentSize` | The number of bytes in the request entity (a.k.a. body) sent by the client. |
| `OriginDuration` | The time taken by the origin server ('upstream') to return its response. |
| `OriginContentSize` | The content length specified by the origin server, or 0 if unspecified. |
| `OriginStatus` | The HTTP status code returned by the origin server. If the request was handled by this Traefik instance (e.g. with a redirect), then this value will be absent. |
| `OriginStatusLine` | `OriginStatus` + Status code explanation |
| `DownstreamStatus` | The HTTP status code returned to the client. |
| `DownstreamStatusLine` | `DownstreamStatus` + Status code explanation |
| `DownstreamContentSize` | The number of bytes in the response entity returned to the client. This is in addition to the "Content-Length" header, which may be present in the origin response. |
| `RequestCount` | The number of requests received since the Traefik instance started. |
| `GzipRatio` | The response body compression ratio achieved. |
| `Overhead` | The processing time overhead caused by Traefik. |
| `RetryAttempts` | The amount of attempts the request was retried. |
Deprecated way (before 1.4):

View File

@@ -88,9 +88,9 @@
</div>
{% endif %}
powered by
<a href="http://www.mkdocs.org" title="MkDocs">MkDocs</a>
<a href="https://www.mkdocs.org" title="MkDocs">MkDocs</a>
and
<a href="http://squidfunk.github.io/mkdocs-material/"
<a href="https://squidfunk.github.io/mkdocs-material/"
title="Material for MkDocs">
Material for MkDocs</a>
</div>

View File

@@ -49,7 +49,7 @@ type Configuration struct {
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge"`
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge"`
TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge"`
Domains []types.Domain `description:"CN and SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='*.main.net'. No SANs for wildcards domain. Wildcard domains only accepted with DNSChallenge"`
Domains []types.Domain `description:"CN and SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='*.main.net'. Wildcard domains only accepted with DNSChallenge"`
}
// Provider holds configurations of the provider.
@@ -756,12 +756,6 @@ func (p *Provider) getValidDomains(domain types.Domain, wildcardAllowed bool) ([
}
}
for _, san := range domain.SANs {
if strings.HasPrefix(san, "*") {
return nil, fmt.Errorf("unable to generate a certificate in ACME provider for domains %q: SAN %q can not be a wildcard domain", strings.Join(domains, ","), san)
}
}
var cleanDomains []string
for _, domain := range domains {
canonicalDomain := types.CanonicalDomain(domain)

View File

@@ -267,12 +267,12 @@ func TestGetValidDomain(t *testing.T) {
expectedDomains: []string{"*.traefik.wtf", "traefik.wtf"},
},
{
desc: "unexpected SANs",
desc: "wildcard SANs",
domains: types.Domain{Main: "*.traefik.wtf", SANs: []string{"*.acme.wtf"}},
dnsChallenge: &DNSChallenge{},
wildcardAllowed: true,
expectedErr: "unable to generate a certificate in ACME provider for domains \"*.traefik.wtf,*.acme.wtf\": SAN \"*.acme.wtf\" can not be a wildcard domain",
expectedDomains: nil,
expectedErr: "",
expectedDomains: []string{"*.traefik.wtf", "*.acme.wtf"},
},
}

View File

@@ -87,7 +87,7 @@ func (p *Provider) safeGetConfiguration(defaultTemplate string, funcMap template
defer func() {
e := recover()
if e != nil {
err = fmt.Errorf("%v", e)
err = fmt.Errorf("error while getting the configuration: %v", e)
}
}()
@@ -576,9 +576,9 @@ func (p *Provider) listServers(backend string) []string {
func (p *Provider) serverFilter(serverName string) bool {
key := fmt.Sprint(serverName, pathBackendServerURL)
if _, err := p.kvClient.Get(key, nil); err != nil {
log.Errorf("Failed to retrieve value for key %s: %s", key, err)
checkError(err)
log.Errorf("failed to retrieve value for key %s: %s", key, err)
return false
}
return p.checkConstraints(serverName, pathTags)
@@ -639,15 +639,11 @@ func (p *Provider) get(defaultValue string, keyParts ...string) string {
}
keyPair, err := p.kvClient.Get(key, nil)
if err != nil {
if err != nil || keyPair == nil {
log.Debugf("Cannot get key %s %s", key, err)
checkError(err)
log.Debugf("Cannot get key %s %s, setting default %s", key, err, defaultValue)
return defaultValue
}
if keyPair == nil {
log.Debugf("Cannot get key %s, setting default %s", key, defaultValue)
log.Debugf("Setting %s to default: %s", key, defaultValue)
return defaultValue
}
@@ -682,9 +678,9 @@ func (p *Provider) hasPrefix(keyParts ...string) bool {
listKeys, err := p.kvClient.List(baseKey, nil)
if err != nil {
log.Debugf("Cannot list keys under %q: %v", baseKey, err)
checkError(err)
log.Debugf("Cannot list keys under %q: %v", baseKey, err)
return false
}
@@ -726,9 +722,9 @@ func (p *Provider) list(keyParts ...string) []string {
keysPairs, err := p.kvClient.List(rootKey, nil)
if err != nil {
log.Debugf("Cannot list keys under %q: %v", rootKey, err)
checkError(err)
log.Debugf("Cannot list keys under %q: %v", rootKey, err)
return nil
}
@@ -801,7 +797,7 @@ func (p *Provider) getMap(keyParts ...string) map[string]string {
}
func checkError(err error) {
if err != store.ErrKeyNotFound {
if err != nil && err != store.ErrKeyNotFound {
panic(err)
}
}

View File

@@ -10,7 +10,7 @@
{{if $backend.ResponseForwarding }}
[backends."{{ $backendName }}".responseForwarding]
flushInterval = "{{ $backend.responseForwarding.FlushInterval }}"
flushInterval = "{{ $backend.ResponseForwarding.FlushInterval }}"
{{end}}
[backends."{{ $backendName }}".loadBalancer]

View File

@@ -408,14 +408,14 @@ type Users []string
// Basic HTTP basic authentication
type Basic struct {
Users `json:"users,omitempty" mapstructure:","`
Users `json:"-" mapstructure:","`
UsersFile string `json:"usersFile,omitempty"`
RemoveHeader bool `json:"removeHeader,omitempty"`
}
// Digest HTTP authentication
type Digest struct {
Users `json:"users,omitempty" mapstructure:","`
Users `json:"-" mapstructure:","`
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,omitempty"`
Key string `description:"TLS key" json:"-"`
InsecureSkipVerify bool `description:"TLS insecure skip verify" json:"insecureSkipVerify,omitempty"`
}

View File

@@ -5,7 +5,7 @@ package sender
const (
// ourUserAgent is the User-Agent of this underlying library package.
ourUserAgent = "xenolf-acme/2.5.0"
ourUserAgent = "xenolf-acme/2.6.0"
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
// values: detach|release

View File

@@ -464,6 +464,33 @@ func (c *Certifier) GetOCSP(bundle []byte) ([]byte, *ocsp.Response, error) {
return ocspResBytes, ocspRes, nil
}
// Get attempts to fetch the certificate at the supplied URL.
// The URL is the same as what would normally be supplied at the Resource's CertURL.
//
// The returned Resource will not have the PrivateKey and CSR fields populated as these will not be available.
//
// If bundle is true, the Certificate field in the returned Resource includes the issuer certificate.
func (c *Certifier) Get(url string, bundle bool) (*Resource, error) {
cert, issuer, err := c.core.Certificates.Get(url, bundle)
if err != nil {
return nil, err
}
// Parse the returned cert bundle so that we can grab the domain from the common name.
x509Certs, err := certcrypto.ParsePEMBundle(cert)
if err != nil {
return nil, err
}
return &Resource{
Domain: x509Certs[0].Subject.CommonName,
Certificate: cert,
IssuerCertificate: issuer,
CertURL: url,
CertStableURL: url,
}, nil
}
func checkOrderStatus(order acme.Order) (bool, error) {
switch order.Status {
case acme.StatusValid:

View File

@@ -4,6 +4,7 @@ import (
"bufio"
"fmt"
"os"
"time"
)
const (
@@ -50,3 +51,9 @@ func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error {
return nil
}
// Sequential All DNS challenges for this provider will be resolved sequentially.
// Returns the interval between each iteration.
func (d *DNSProviderManual) Sequential() time.Duration {
return DefaultPropagationTimeout
}

View File

@@ -159,5 +159,5 @@ func GetOrFile(envVar string) string {
return ""
}
return string(fileContents)
return strings.TrimSuffix(string(fileContents), "\n")
}

View File

@@ -0,0 +1,99 @@
// Package bindman implements a DNS provider for solving the DNS-01 challenge.
package bindman
import (
"errors"
"fmt"
"net/http"
"time"
"github.com/go-acme/lego/challenge/dns01"
"github.com/go-acme/lego/platform/config/env"
"github.com/labbsr0x/bindman-dns-webhook/src/client"
)
// Config is used to configure the creation of the DNSProvider
type Config struct {
PropagationTimeout time.Duration
PollingInterval time.Duration
BaseURL string
HTTPClient *http.Client
}
// NewDefaultConfig returns a default configuration for the DNSProvider
func NewDefaultConfig() *Config {
return &Config{
PropagationTimeout: env.GetOrDefaultSecond("BINDMAN_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
PollingInterval: env.GetOrDefaultSecond("BINDMAN_POLLING_INTERVAL", dns01.DefaultPollingInterval),
HTTPClient: &http.Client{
Timeout: env.GetOrDefaultSecond("BINDMAN_HTTP_TIMEOUT", time.Minute),
},
}
}
// DNSProvider is an implementation of the acme.ChallengeProvider interface that uses
// Bindman's Address Manager REST API to manage TXT records for a domain.
type DNSProvider struct {
config *Config
client *client.DNSWebhookClient
}
// NewDNSProvider returns a DNSProvider instance configured for Bindman.
// BINDMAN_MANAGER_ADDRESS should have the scheme, hostname, and port (if required) of the authoritative Bindman Manager server.
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get("BINDMAN_MANAGER_ADDRESS")
if err != nil {
return nil, fmt.Errorf("bindman: %v", err)
}
config := NewDefaultConfig()
config.BaseURL = values["BINDMAN_MANAGER_ADDRESS"]
return NewDNSProviderConfig(config)
}
// NewDNSProviderConfig return a DNSProvider instance configured for Bindman.
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("bindman: the configuration of the DNS provider is nil")
}
if config.BaseURL == "" {
return nil, fmt.Errorf("bindman: bindman manager address missing")
}
bClient, err := client.New(config.BaseURL, config.HTTPClient)
if err != nil {
return nil, fmt.Errorf("bindman: %v", err)
}
return &DNSProvider{config: config, client: bClient}, nil
}
// Present creates a TXT record using the specified parameters.
// This will *not* create a subzone to contain the TXT record,
// so make sure the FQDN specified is within an extant zone.
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth)
if err := d.client.AddRecord(fqdn, "TXT", value); err != nil {
return fmt.Errorf("bindman: %v", err)
}
return nil
}
// CleanUp removes the TXT record matching the specified parameters.
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _ := dns01.GetRecord(domain, keyAuth)
if err := d.client.RemoveRecord(fqdn, "TXT"); err != nil {
return fmt.Errorf("bindman: %v", err)
}
return nil
}
// Timeout returns the timeout and interval to use when checking for DNS propagation.
// Adjusting here to cope with spikes in propagation times.
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}

View File

@@ -57,10 +57,10 @@ func (d *DNSProvider) removeTxtRecord(domain string, recordID int) error {
return nil
}
func (d *DNSProvider) addTxtRecord(domain, fqdn, value string) (*txtRecordResponse, error) {
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
func (d *DNSProvider) addTxtRecord(fqdn, value string) (*txtRecordResponse, error) {
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(fqdn))
if err != nil {
return nil, fmt.Errorf("could not determine zone for domain: '%s'. %s", domain, err)
return nil, fmt.Errorf("could not determine zone for domain: '%s'. %s", fqdn, err)
}
reqData := record{Type: "TXT", Name: fqdn, Data: value, TTL: d.config.TTL}

View File

@@ -88,7 +88,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth)
respData, err := d.addTxtRecord(domain, fqdn, value)
respData, err := d.addTxtRecord(fqdn, value)
if err != nil {
return fmt.Errorf("digitalocean: %v", err)
}
@@ -104,6 +104,11 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _ := dns01.GetRecord(domain, keyAuth)
authZone, err := dns01.FindZoneByFqdn(fqdn)
if err != nil {
return fmt.Errorf("digitalocean: %v", err)
}
// get the record's unique ID from when we created it
d.recordIDsMu.Lock()
recordID, ok := d.recordIDs[fqdn]
@@ -112,7 +117,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return fmt.Errorf("digitalocean: unknown record ID for '%s'", fqdn)
}
err := d.removeTxtRecord(domain, recordID)
err = d.removeTxtRecord(authZone, recordID)
if err != nil {
return fmt.Errorf("digitalocean: %v", err)
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/go-acme/lego/providers/dns/alidns"
"github.com/go-acme/lego/providers/dns/auroradns"
"github.com/go-acme/lego/providers/dns/azure"
"github.com/go-acme/lego/providers/dns/bindman"
"github.com/go-acme/lego/providers/dns/bluecat"
"github.com/go-acme/lego/providers/dns/cloudflare"
"github.com/go-acme/lego/providers/dns/cloudns"
@@ -23,6 +24,7 @@ import (
"github.com/go-acme/lego/providers/dns/dreamhost"
"github.com/go-acme/lego/providers/dns/duckdns"
"github.com/go-acme/lego/providers/dns/dyn"
"github.com/go-acme/lego/providers/dns/easydns"
"github.com/go-acme/lego/providers/dns/exec"
"github.com/go-acme/lego/providers/dns/exoscale"
"github.com/go-acme/lego/providers/dns/fastdns"
@@ -35,6 +37,7 @@ import (
"github.com/go-acme/lego/providers/dns/httpreq"
"github.com/go-acme/lego/providers/dns/iij"
"github.com/go-acme/lego/providers/dns/inwx"
"github.com/go-acme/lego/providers/dns/joker"
"github.com/go-acme/lego/providers/dns/lightsail"
"github.com/go-acme/lego/providers/dns/linode"
"github.com/go-acme/lego/providers/dns/linodev4"
@@ -72,6 +75,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
return azure.NewDNSProvider()
case "auroradns":
return auroradns.NewDNSProvider()
case "bindman":
return bindman.NewDNSProvider()
case "bluecat":
return bluecat.NewDNSProvider()
case "cloudflare":
@@ -102,6 +107,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
return dyn.NewDNSProvider()
case "fastdns":
return fastdns.NewDNSProvider()
case "easydns":
return easydns.NewDNSProvider()
case "exec":
return exec.NewDNSProvider()
case "exoscale":
@@ -124,6 +131,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
return iij.NewDNSProvider()
case "inwx":
return inwx.NewDNSProvider()
case "joker":
return joker.NewDNSProvider()
case "lightsail":
return lightsail.NewDNSProvider()
case "linode":

View File

@@ -0,0 +1,97 @@
package easydns
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"path"
)
const defaultEndpoint = "https://rest.easydns.net"
type zoneRecord struct {
ID string `json:"id,omitempty"`
Domain string `json:"domain"`
Host string `json:"host"`
TTL string `json:"ttl"`
Prio string `json:"prio"`
Type string `json:"type"`
Rdata string `json:"rdata"`
LastMod string `json:"last_mod,omitempty"`
Revoked int `json:"revoked,omitempty"`
NewHost string `json:"new_host,omitempty"`
}
type addRecordResponse struct {
Msg string `json:"msg"`
Tm int `json:"tm"`
Data zoneRecord `json:"data"`
Status int `json:"status"`
}
func (d *DNSProvider) addRecord(domain string, record interface{}) (string, error) {
pathAdd := path.Join("/zones/records/add", domain, "TXT")
response := &addRecordResponse{}
err := d.doRequest(http.MethodPut, pathAdd, record, response)
if err != nil {
return "", err
}
recordID := response.Data.ID
return recordID, nil
}
func (d *DNSProvider) deleteRecord(domain, recordID string) error {
pathDelete := path.Join("/zones/records", domain, recordID)
return d.doRequest(http.MethodDelete, pathDelete, nil, nil)
}
func (d *DNSProvider) doRequest(method, path string, requestMsg, responseMsg interface{}) error {
reqBody := &bytes.Buffer{}
if requestMsg != nil {
err := json.NewEncoder(reqBody).Encode(requestMsg)
if err != nil {
return err
}
}
endpoint, err := d.config.Endpoint.Parse(path + "?format=json")
if err != nil {
return err
}
request, err := http.NewRequest(method, endpoint.String(), reqBody)
if err != nil {
return err
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("Accept", "application/json")
request.SetBasicAuth(d.config.Token, d.config.Key)
response, err := d.config.HTTPClient.Do(request)
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode >= http.StatusBadRequest {
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("%d: failed to read response body: %v", response.StatusCode, err)
}
return fmt.Errorf("%d: request failed: %v", response.StatusCode, string(body))
}
if responseMsg != nil {
return json.NewDecoder(response.Body).Decode(responseMsg)
}
return nil
}

View File

@@ -0,0 +1,165 @@
// Package easydns implements a DNS provider for solving the DNS-01 challenge using EasyDNS API.
package easydns
import (
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/miekg/dns"
"github.com/go-acme/lego/challenge/dns01"
"github.com/go-acme/lego/platform/config/env"
)
// Config is used to configure the creation of the DNSProvider
type Config struct {
Endpoint *url.URL
Token string
Key string
TTL int
HTTPClient *http.Client
PropagationTimeout time.Duration
PollingInterval time.Duration
SequenceInterval time.Duration
}
// NewDefaultConfig returns a default configuration for the DNSProvider
func NewDefaultConfig() *Config {
return &Config{
PropagationTimeout: env.GetOrDefaultSecond("EASYDNS_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
SequenceInterval: env.GetOrDefaultSecond("EASYDNS_SEQUENCE_INTERVAL", dns01.DefaultPropagationTimeout),
PollingInterval: env.GetOrDefaultSecond("EASYDNS_POLLING_INTERVAL", dns01.DefaultPollingInterval),
TTL: env.GetOrDefaultInt("EASYDNS_TTL", dns01.DefaultTTL),
HTTPClient: &http.Client{
Timeout: env.GetOrDefaultSecond("EASYDNS_HTTP_TIMEOUT", 30*time.Second),
},
}
}
// DNSProvider describes a provider for acme-proxy
type DNSProvider struct {
config *Config
recordIDs map[string]string
recordIDsMu sync.Mutex
}
// NewDNSProvider returns a DNSProvider instance.
func NewDNSProvider() (*DNSProvider, error) {
config := NewDefaultConfig()
endpoint, err := url.Parse(env.GetOrDefaultString("EASYDNS_ENDPOINT", defaultEndpoint))
if err != nil {
return nil, fmt.Errorf("easydns: %v", err)
}
config.Endpoint = endpoint
values, err := env.Get("EASYDNS_TOKEN", "EASYDNS_KEY")
if err != nil {
return nil, fmt.Errorf("easydns: %v", err)
}
config.Token = values["EASYDNS_TOKEN"]
config.Key = values["EASYDNS_KEY"]
return NewDNSProviderConfig(config)
}
// NewDNSProviderConfig return a DNSProvider .
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("easydns: the configuration of the DNS provider is nil")
}
if config.Token == "" {
return nil, errors.New("easydns: the API token is missing")
}
if config.Key == "" {
return nil, errors.New("easydns: the API key is missing")
}
return &DNSProvider{config: config, recordIDs: map[string]string{}}, nil
}
// Present creates a TXT record to fulfill the dns-01 challenge
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth)
apiHost, apiDomain := splitFqdn(fqdn)
record := &zoneRecord{
Domain: apiDomain,
Host: apiHost,
Type: "TXT",
Rdata: value,
TTL: strconv.Itoa(d.config.TTL),
Prio: "0",
}
recordID, err := d.addRecord(apiDomain, record)
if err != nil {
return fmt.Errorf("easydns: error adding zone record: %v", err)
}
key := getMapKey(fqdn, value)
d.recordIDsMu.Lock()
d.recordIDs[key] = recordID
d.recordIDsMu.Unlock()
return nil
}
// CleanUp removes the TXT record matching the specified parameters
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, challenge := dns01.GetRecord(domain, keyAuth)
key := getMapKey(fqdn, challenge)
recordID, exists := d.recordIDs[key]
if !exists {
return nil
}
_, apiDomain := splitFqdn(fqdn)
err := d.deleteRecord(apiDomain, recordID)
d.recordIDsMu.Lock()
defer delete(d.recordIDs, key)
d.recordIDsMu.Unlock()
if err != nil {
return fmt.Errorf("easydns: %v", err)
}
return nil
}
// Timeout returns the timeout and interval to use when checking for DNS propagation.
// Adjusting here to cope with spikes in propagation times.
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
// Sequential All DNS challenges for this provider will be resolved sequentially.
// Returns the interval between each iteration.
func (d *DNSProvider) Sequential() time.Duration {
return d.config.SequenceInterval
}
func splitFqdn(fqdn string) (host, domain string) {
parts := dns.SplitDomainName(fqdn)
length := len(parts)
host = strings.Join(parts[0:length-2], ".")
domain = strings.Join(parts[length-2:length], ".")
return
}
func getMapKey(fqdn, value string) string {
return fqdn + "|" + value
}

View File

@@ -105,3 +105,9 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
// Sequential All DNS challenges for this provider will be resolved sequentially.
// Returns the interval between each iteration.
func (d *DNSProvider) Sequential() time.Duration {
return d.config.PropagationTimeout
}

View File

@@ -156,7 +156,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth)
zone, err := d.getHostedZone(domain)
zone, err := d.getHostedZone(fqdn)
if err != nil {
return fmt.Errorf("googlecloud: %v", err)
}
@@ -264,7 +264,7 @@ func (d *DNSProvider) applyChanges(zone string, change *dns.Change) error {
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _ := dns01.GetRecord(domain, keyAuth)
zone, err := d.getHostedZone(domain)
zone, err := d.getHostedZone(fqdn)
if err != nil {
return fmt.Errorf("googlecloud: %v", err)
}
@@ -311,7 +311,7 @@ func (d *DNSProvider) getHostedZone(domain string) (string, error) {
}
for _, z := range zones.ManagedZones {
if z.Visibility == "public" {
if z.Visibility == "public" || z.Visibility == "" {
return z.Name, nil
}
}

View File

@@ -0,0 +1,197 @@
package joker
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/go-acme/lego/challenge/dns01"
"github.com/go-acme/lego/log"
)
const defaultBaseURL = "https://dmapi.joker.com/request/"
// Joker DMAPI Response
type response struct {
Headers url.Values
Body string
StatusCode int
StatusText string
AuthSid string
}
// parseResponse parses HTTP response body
func parseResponse(message string) *response {
r := &response{Headers: url.Values{}, StatusCode: -1}
parts := strings.SplitN(message, "\n\n", 2)
for _, line := range strings.Split(parts[0], "\n") {
if strings.TrimSpace(line) == "" {
continue
}
kv := strings.SplitN(line, ":", 2)
val := ""
if len(kv) == 2 {
val = strings.TrimSpace(kv[1])
}
r.Headers.Add(kv[0], val)
switch kv[0] {
case "Status-Code":
i, err := strconv.Atoi(val)
if err == nil {
r.StatusCode = i
}
case "Status-Text":
r.StatusText = val
case "Auth-Sid":
r.AuthSid = val
}
}
if len(parts) > 1 {
r.Body = parts[1]
}
return r
}
// login performs a login to Joker's DMAPI
func (d *DNSProvider) login() (*response, error) {
if d.config.AuthSid != "" {
// already logged in
return nil, nil
}
response, err := d.postRequest("login", url.Values{"api-key": {d.config.APIKey}})
if err != nil {
return response, err
}
if response == nil {
return nil, fmt.Errorf("login returned nil response")
}
if response.AuthSid == "" {
return response, fmt.Errorf("login did not return valid Auth-Sid")
}
d.config.AuthSid = response.AuthSid
return response, nil
}
// logout closes authenticated session with Joker's DMAPI
func (d *DNSProvider) logout() (*response, error) {
if d.config.AuthSid == "" {
return nil, fmt.Errorf("already logged out")
}
response, err := d.postRequest("logout", url.Values{})
if err == nil {
d.config.AuthSid = ""
}
return response, err
}
// getZone returns content of DNS zone for domain
func (d *DNSProvider) getZone(domain string) (*response, error) {
if d.config.AuthSid == "" {
return nil, fmt.Errorf("must be logged in to get zone")
}
return d.postRequest("dns-zone-get", url.Values{"domain": {dns01.UnFqdn(domain)}})
}
// putZone uploads DNS zone to Joker DMAPI
func (d *DNSProvider) putZone(domain, zone string) (*response, error) {
if d.config.AuthSid == "" {
return nil, fmt.Errorf("must be logged in to put zone")
}
return d.postRequest("dns-zone-put", url.Values{"domain": {dns01.UnFqdn(domain)}, "zone": {strings.TrimSpace(zone)}})
}
// postRequest performs actual HTTP request
func (d *DNSProvider) postRequest(cmd string, data url.Values) (*response, error) {
uri := d.config.BaseURL + cmd
if d.config.AuthSid != "" {
data.Set("auth-sid", d.config.AuthSid)
}
if d.config.Debug {
log.Infof("postRequest:\n\tURL: %q\n\tData: %v", uri, data)
}
resp, err := d.config.HTTPClient.PostForm(uri, data)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("HTTP error %d [%s]: %v", resp.StatusCode, http.StatusText(resp.StatusCode), string(body))
}
return parseResponse(string(body)), nil
}
// Temporary workaround, until it get fixed on API side
func fixTxtLines(line string) string {
fields := strings.Fields(line)
if len(fields) < 6 || fields[1] != "TXT" {
return line
}
if fields[3][0] == '"' && fields[4] == `"` {
fields[3] = strings.TrimSpace(fields[3]) + `"`
fields = append(fields[:4], fields[5:]...)
}
return strings.Join(fields, " ")
}
// removeTxtEntryFromZone clean-ups all TXT records with given name
func removeTxtEntryFromZone(zone, relative string) (string, bool) {
prefix := fmt.Sprintf("%s TXT 0 ", relative)
modified := false
var zoneEntries []string
for _, line := range strings.Split(zone, "\n") {
if strings.HasPrefix(line, prefix) {
modified = true
continue
}
zoneEntries = append(zoneEntries, line)
}
return strings.TrimSpace(strings.Join(zoneEntries, "\n")), modified
}
// addTxtEntryToZone returns DNS zone with added TXT record
func addTxtEntryToZone(zone, relative, value string, ttl int) string {
var zoneEntries []string
for _, line := range strings.Split(zone, "\n") {
zoneEntries = append(zoneEntries, fixTxtLines(line))
}
newZoneEntry := fmt.Sprintf("%s TXT 0 %q %d", relative, value, ttl)
zoneEntries = append(zoneEntries, newZoneEntry)
return strings.TrimSpace(strings.Join(zoneEntries, "\n"))
}

View File

@@ -0,0 +1,174 @@
// Package joker implements a DNS provider for solving the DNS-01 challenge using joker.com DMAPI.
package joker
import (
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/go-acme/lego/challenge/dns01"
"github.com/go-acme/lego/log"
"github.com/go-acme/lego/platform/config/env"
)
// Config is used to configure the creation of the DNSProvider.
type Config struct {
Debug bool
BaseURL string
APIKey string
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int
HTTPClient *http.Client
AuthSid string
}
// NewDefaultConfig returns a default configuration for the DNSProvider
func NewDefaultConfig() *Config {
return &Config{
BaseURL: defaultBaseURL,
Debug: env.GetOrDefaultBool("JOKER_DEBUG", false),
TTL: env.GetOrDefaultInt("JOKER_TTL", dns01.DefaultTTL),
PropagationTimeout: env.GetOrDefaultSecond("JOKER_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
PollingInterval: env.GetOrDefaultSecond("JOKER_POLLING_INTERVAL", dns01.DefaultPollingInterval),
HTTPClient: &http.Client{
Timeout: env.GetOrDefaultSecond("JOKER_HTTP_TIMEOUT", 60*time.Second),
},
}
}
// DNSProvider is an implementation of the ChallengeProviderTimeout interface
// that uses Joker's DMAPI to manage TXT records for a domain.
type DNSProvider struct {
config *Config
}
// NewDNSProvider returns a DNSProvider instance configured for Joker DMAPI.
// Credentials must be passed in the environment variable JOKER_API_KEY.
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get("JOKER_API_KEY")
if err != nil {
return nil, fmt.Errorf("joker: %v", err)
}
config := NewDefaultConfig()
config.APIKey = values["JOKER_API_KEY"]
return NewDNSProviderConfig(config)
}
// NewDNSProviderConfig return a DNSProvider instance configured for Joker DMAPI.
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("joker: the configuration of the DNS provider is nil")
}
if config.APIKey == "" {
return nil, fmt.Errorf("joker: credentials missing")
}
if !strings.HasSuffix(config.BaseURL, "/") {
config.BaseURL += "/"
}
return &DNSProvider{config: config}, nil
}
// Timeout returns the timeout and interval to use when checking for DNS propagation.
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
// Present installs a TXT record for the DNS challenge.
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth)
zone, err := dns01.FindZoneByFqdn(fqdn)
if err != nil {
return fmt.Errorf("joker: %v", err)
}
relative := getRelative(fqdn, zone)
if d.config.Debug {
log.Infof("[%s] joker: adding TXT record %q to zone %q with value %q", domain, relative, zone, value)
}
response, err := d.login()
if err != nil {
return formatResponseError(response, err)
}
response, err = d.getZone(zone)
if err != nil || response.StatusCode != 0 {
return formatResponseError(response, err)
}
dnsZone := addTxtEntryToZone(response.Body, relative, value, d.config.TTL)
response, err = d.putZone(zone, dnsZone)
if err != nil || response.StatusCode != 0 {
return formatResponseError(response, err)
}
return nil
}
// CleanUp removes a TXT record used for a previous DNS challenge.
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _ := dns01.GetRecord(domain, keyAuth)
zone, err := dns01.FindZoneByFqdn(fqdn)
if err != nil {
return fmt.Errorf("joker: %v", err)
}
relative := getRelative(fqdn, zone)
if d.config.Debug {
log.Infof("[%s] joker: removing entry %q from zone %q", domain, relative, zone)
}
response, err := d.login()
if err != nil {
return formatResponseError(response, err)
}
defer func() {
// Try to logout in case of errors
_, _ = d.logout()
}()
response, err = d.getZone(zone)
if err != nil || response.StatusCode != 0 {
return formatResponseError(response, err)
}
dnsZone, modified := removeTxtEntryFromZone(response.Body, relative)
if modified {
response, err = d.putZone(zone, dnsZone)
if err != nil || response.StatusCode != 0 {
return formatResponseError(response, err)
}
}
response, err = d.logout()
if err != nil {
return formatResponseError(response, err)
}
return nil
}
func getRelative(fqdn, zone string) string {
return dns01.UnFqdn(strings.TrimSuffix(fqdn, dns01.ToFqdn(zone)))
}
// formatResponseError formats error with optional details from DMAPI response
func formatResponseError(response *response, err error) error {
if response != nil {
return fmt.Errorf("joker: DMAPI error: %v Response: %v", err, response.Headers)
}
return fmt.Errorf("joker: DMAPI error: %v", err)
}

7
vendor/github.com/go-errors/errors/LICENSE.MIT generated vendored Normal file
View File

@@ -0,0 +1,7 @@
Copyright (c) 2015 Conrad Irwin <conrad@bugsnag.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

217
vendor/github.com/go-errors/errors/error.go generated vendored Normal file
View File

@@ -0,0 +1,217 @@
// Package errors provides errors that have stack-traces.
//
// This is particularly useful when you want to understand the
// state of execution when an error was returned unexpectedly.
//
// It provides the type *Error which implements the standard
// golang error interface, so you can use this library interchangably
// with code that is expecting a normal error return.
//
// For example:
//
// package crashy
//
// import "github.com/go-errors/errors"
//
// var Crashed = errors.Errorf("oh dear")
//
// func Crash() error {
// return errors.New(Crashed)
// }
//
// This can be called as follows:
//
// package main
//
// import (
// "crashy"
// "fmt"
// "github.com/go-errors/errors"
// )
//
// func main() {
// err := crashy.Crash()
// if err != nil {
// if errors.Is(err, crashy.Crashed) {
// fmt.Println(err.(*errors.Error).ErrorStack())
// } else {
// panic(err)
// }
// }
// }
//
// This package was original written to allow reporting to Bugsnag,
// but after I found similar packages by Facebook and Dropbox, it
// was moved to one canonical location so everyone can benefit.
package errors
import (
"bytes"
"fmt"
"reflect"
"runtime"
)
// The maximum number of stackframes on any error.
var MaxStackDepth = 50
// Error is an error with an attached stacktrace. It can be used
// wherever the builtin error interface is expected.
type Error struct {
Err error
stack []uintptr
frames []StackFrame
prefix string
}
// New makes an Error from the given value. If that value is already an
// error then it will be used directly, if not, it will be passed to
// fmt.Errorf("%v"). The stacktrace will point to the line of code that
// called New.
func New(e interface{}) *Error {
var err error
switch e := e.(type) {
case error:
err = e
default:
err = fmt.Errorf("%v", e)
}
stack := make([]uintptr, MaxStackDepth)
length := runtime.Callers(2, stack[:])
return &Error{
Err: err,
stack: stack[:length],
}
}
// Wrap makes an Error from the given value. If that value is already an
// error then it will be used directly, if not, it will be passed to
// fmt.Errorf("%v"). The skip parameter indicates how far up the stack
// to start the stacktrace. 0 is from the current call, 1 from its caller, etc.
func Wrap(e interface{}, skip int) *Error {
var err error
switch e := e.(type) {
case *Error:
return e
case error:
err = e
default:
err = fmt.Errorf("%v", e)
}
stack := make([]uintptr, MaxStackDepth)
length := runtime.Callers(2+skip, stack[:])
return &Error{
Err: err,
stack: stack[:length],
}
}
// WrapPrefix makes an Error from the given value. If that value is already an
// error then it will be used directly, if not, it will be passed to
// fmt.Errorf("%v"). The prefix parameter is used to add a prefix to the
// error message when calling Error(). The skip parameter indicates how far
// up the stack to start the stacktrace. 0 is from the current call,
// 1 from its caller, etc.
func WrapPrefix(e interface{}, prefix string, skip int) *Error {
err := Wrap(e, 1+skip)
if err.prefix != "" {
prefix = fmt.Sprintf("%s: %s", prefix, err.prefix)
}
return &Error{
Err: err.Err,
stack: err.stack,
prefix: prefix,
}
}
// Is detects whether the error is equal to a given error. Errors
// are considered equal by this function if they are the same object,
// or if they both contain the same error inside an errors.Error.
func Is(e error, original error) bool {
if e == original {
return true
}
if e, ok := e.(*Error); ok {
return Is(e.Err, original)
}
if original, ok := original.(*Error); ok {
return Is(e, original.Err)
}
return false
}
// Errorf creates a new error with the given message. You can use it
// as a drop-in replacement for fmt.Errorf() to provide descriptive
// errors in return values.
func Errorf(format string, a ...interface{}) *Error {
return Wrap(fmt.Errorf(format, a...), 1)
}
// Error returns the underlying error's message.
func (err *Error) Error() string {
msg := err.Err.Error()
if err.prefix != "" {
msg = fmt.Sprintf("%s: %s", err.prefix, msg)
}
return msg
}
// Stack returns the callstack formatted the same way that go does
// in runtime/debug.Stack()
func (err *Error) Stack() []byte {
buf := bytes.Buffer{}
for _, frame := range err.StackFrames() {
buf.WriteString(frame.String())
}
return buf.Bytes()
}
// Callers satisfies the bugsnag ErrorWithCallerS() interface
// so that the stack can be read out.
func (err *Error) Callers() []uintptr {
return err.stack
}
// ErrorStack returns a string that contains both the
// error message and the callstack.
func (err *Error) ErrorStack() string {
return err.TypeName() + " " + err.Error() + "\n" + string(err.Stack())
}
// StackFrames returns an array of frames containing information about the
// stack.
func (err *Error) StackFrames() []StackFrame {
if err.frames == nil {
err.frames = make([]StackFrame, len(err.stack))
for i, pc := range err.stack {
err.frames[i] = NewStackFrame(pc)
}
}
return err.frames
}
// TypeName returns the type this error. e.g. *errors.stringError.
func (err *Error) TypeName() string {
if _, ok := err.Err.(uncaughtPanic); ok {
return "panic"
}
return reflect.TypeOf(err.Err).String()
}

127
vendor/github.com/go-errors/errors/parse_panic.go generated vendored Normal file
View File

@@ -0,0 +1,127 @@
package errors
import (
"strconv"
"strings"
)
type uncaughtPanic struct{ message string }
func (p uncaughtPanic) Error() string {
return p.message
}
// ParsePanic allows you to get an error object from the output of a go program
// that panicked. This is particularly useful with https://github.com/mitchellh/panicwrap.
func ParsePanic(text string) (*Error, error) {
lines := strings.Split(text, "\n")
state := "start"
var message string
var stack []StackFrame
for i := 0; i < len(lines); i++ {
line := lines[i]
if state == "start" {
if strings.HasPrefix(line, "panic: ") {
message = strings.TrimPrefix(line, "panic: ")
state = "seek"
} else {
return nil, Errorf("bugsnag.panicParser: Invalid line (no prefix): %s", line)
}
} else if state == "seek" {
if strings.HasPrefix(line, "goroutine ") && strings.HasSuffix(line, "[running]:") {
state = "parsing"
}
} else if state == "parsing" {
if line == "" {
state = "done"
break
}
createdBy := false
if strings.HasPrefix(line, "created by ") {
line = strings.TrimPrefix(line, "created by ")
createdBy = true
}
i++
if i >= len(lines) {
return nil, Errorf("bugsnag.panicParser: Invalid line (unpaired): %s", line)
}
frame, err := parsePanicFrame(line, lines[i], createdBy)
if err != nil {
return nil, err
}
stack = append(stack, *frame)
if createdBy {
state = "done"
break
}
}
}
if state == "done" || state == "parsing" {
return &Error{Err: uncaughtPanic{message}, frames: stack}, nil
}
return nil, Errorf("could not parse panic: %v", text)
}
// The lines we're passing look like this:
//
// main.(*foo).destruct(0xc208067e98)
// /0/go/src/github.com/bugsnag/bugsnag-go/pan/main.go:22 +0x151
func parsePanicFrame(name string, line string, createdBy bool) (*StackFrame, error) {
idx := strings.LastIndex(name, "(")
if idx == -1 && !createdBy {
return nil, Errorf("bugsnag.panicParser: Invalid line (no call): %s", name)
}
if idx != -1 {
name = name[:idx]
}
pkg := ""
if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
pkg += name[:lastslash] + "/"
name = name[lastslash+1:]
}
if period := strings.Index(name, "."); period >= 0 {
pkg += name[:period]
name = name[period+1:]
}
name = strings.Replace(name, "·", ".", -1)
if !strings.HasPrefix(line, "\t") {
return nil, Errorf("bugsnag.panicParser: Invalid line (no tab): %s", line)
}
idx = strings.LastIndex(line, ":")
if idx == -1 {
return nil, Errorf("bugsnag.panicParser: Invalid line (no line number): %s", line)
}
file := line[1:idx]
number := line[idx+1:]
if idx = strings.Index(number, " +"); idx > -1 {
number = number[:idx]
}
lno, err := strconv.ParseInt(number, 10, 32)
if err != nil {
return nil, Errorf("bugsnag.panicParser: Invalid line (bad line number): %s", line)
}
return &StackFrame{
File: file,
LineNumber: int(lno),
Package: pkg,
Name: name,
}, nil
}

102
vendor/github.com/go-errors/errors/stackframe.go generated vendored Normal file
View File

@@ -0,0 +1,102 @@
package errors
import (
"bytes"
"fmt"
"io/ioutil"
"runtime"
"strings"
)
// A StackFrame contains all necessary information about to generate a line
// in a callstack.
type StackFrame struct {
// The path to the file containing this ProgramCounter
File string
// The LineNumber in that file
LineNumber int
// The Name of the function that contains this ProgramCounter
Name string
// The Package that contains this function
Package string
// The underlying ProgramCounter
ProgramCounter uintptr
}
// NewStackFrame popoulates a stack frame object from the program counter.
func NewStackFrame(pc uintptr) (frame StackFrame) {
frame = StackFrame{ProgramCounter: pc}
if frame.Func() == nil {
return
}
frame.Package, frame.Name = packageAndName(frame.Func())
// pc -1 because the program counters we use are usually return addresses,
// and we want to show the line that corresponds to the function call
frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1)
return
}
// Func returns the function that contained this frame.
func (frame *StackFrame) Func() *runtime.Func {
if frame.ProgramCounter == 0 {
return nil
}
return runtime.FuncForPC(frame.ProgramCounter)
}
// String returns the stackframe formatted in the same way as go does
// in runtime/debug.Stack()
func (frame *StackFrame) String() string {
str := fmt.Sprintf("%s:%d (0x%x)\n", frame.File, frame.LineNumber, frame.ProgramCounter)
source, err := frame.SourceLine()
if err != nil {
return str
}
return str + fmt.Sprintf("\t%s: %s\n", frame.Name, source)
}
// SourceLine gets the line of code (from File and Line) of the original source if possible.
func (frame *StackFrame) SourceLine() (string, error) {
data, err := ioutil.ReadFile(frame.File)
if err != nil {
return "", New(err)
}
lines := bytes.Split(data, []byte{'\n'})
if frame.LineNumber <= 0 || frame.LineNumber >= len(lines) {
return "???", nil
}
// -1 because line-numbers are 1 based, but our array is 0 based
return string(bytes.Trim(lines[frame.LineNumber-1], " \t")), nil
}
func packageAndName(fn *runtime.Func) (string, string) {
name := fn.Name()
pkg := ""
// The name includes the path name to the package, which is unnecessary
// since the file name is already included. Plus, it has center dots.
// That is, we see
// runtime/debug.*T·ptrmethod
// and want
// *T.ptrmethod
// Since the package path might contains dots (e.g. code.google.com/...),
// we first remove the path prefix if there is one.
if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
pkg += name[:lastslash] + "/"
name = name[lastslash+1:]
}
if period := strings.Index(name, "."); period >= 0 {
pkg += name[:period]
name = name[period+1:]
}
name = strings.Replace(name, "·", ".", -1)
return pkg, name
}

21
vendor/github.com/labbsr0x/bindman-dns-webhook/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Labbs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,113 @@
package client
import (
"encoding/json"
"errors"
"fmt"
"github.com/labbsr0x/bindman-dns-webhook/src/types"
"github.com/labbsr0x/goh/gohclient"
"net/http"
"strings"
)
const recordsPath = "/records"
// DNSWebhookClient defines the basic structure of a DNS Listener
type DNSWebhookClient struct {
ClientAPI gohclient.API
}
// New builds the client to communicate with the dns manager
func New(managerAddress string, httpClient *http.Client) (*DNSWebhookClient, error) {
if strings.TrimSpace(managerAddress) == "" {
return nil, errors.New("managerAddress parameter must be a non-empty string")
}
client, err := gohclient.New(httpClient, managerAddress)
if err != nil {
return nil, err
}
client.Accept = "application/json"
client.ContentType = "application/json"
client.UserAgent = "bindman-dns-webhook-client"
return &DNSWebhookClient{
ClientAPI: client,
}, nil
}
// GetRecords communicates with the dns manager and gets the DNS Records
func (l *DNSWebhookClient) GetRecords() (result []types.DNSRecord, err error) {
resp, data, err := l.ClientAPI.Get(recordsPath)
if err != nil {
return
}
if resp.StatusCode == http.StatusOK {
err = json.Unmarshal(data, &result)
} else {
err = parseResponseBodyToError(data)
}
return
}
// GetRecord communicates with the dns manager and gets a DNS Record
func (l *DNSWebhookClient) GetRecord(name, recordType string) (result types.DNSRecord, err error) {
resp, data, err := l.ClientAPI.Get(fmt.Sprintf(recordsPath+"/%s/%s", name, recordType))
if err != nil {
return
}
if resp.StatusCode == http.StatusOK {
err = json.Unmarshal(data, &result)
} else {
err = parseResponseBodyToError(data)
}
return
}
// AddRecord adds a DNS record
func (l *DNSWebhookClient) AddRecord(name string, recordType string, value string) error {
return l.addOrUpdateRecord(&types.DNSRecord{Value: value, Name: name, Type: recordType}, l.ClientAPI.Post)
}
// UpdateRecord is a function that calls the defined webhook to update a specific dns record
func (l *DNSWebhookClient) UpdateRecord(record *types.DNSRecord) error {
return l.addOrUpdateRecord(record, l.ClientAPI.Put)
}
// addOrUpdateRecord .
func (l *DNSWebhookClient) addOrUpdateRecord(record *types.DNSRecord, action func(url string, body []byte) (*http.Response, []byte, error)) error {
if errs := record.Check(); errs != nil {
return fmt.Errorf("invalid DNS Record: %v", strings.Join(errs, ", "))
}
mr, err := json.Marshal(record)
if err != nil {
return err
}
resp, data, err := action(recordsPath, mr)
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
return parseResponseBodyToError(data)
}
return nil
}
// RemoveRecord is a function that calls the defined webhook to remove a specific dns record
func (l *DNSWebhookClient) RemoveRecord(name, recordType string) error {
resp, data, err := l.ClientAPI.Delete(fmt.Sprintf(recordsPath+"/%s/%s", name, recordType))
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
return parseResponseBodyToError(data)
}
return err
}
func parseResponseBodyToError(data []byte) error {
var err types.Error
if errUnmarshal := json.Unmarshal(data, &err); errUnmarshal != nil {
return errUnmarshal
}
return &err
}

View File

@@ -0,0 +1,20 @@
package types
// DNSManager defines the operations a DNS Manager provider should implement
type DNSManager interface {
// GetDNSRecords retrieves all the dns records being managed
GetDNSRecords() ([]DNSRecord, error)
// GetDNSRecord retrieves the dns record identified by name
GetDNSRecord(name, recordType string) (*DNSRecord, error)
// RemoveDNSRecord removes a DNS record
RemoveDNSRecord(name, recordType string) error
// AddDNSRecord adds a new DNS record
AddDNSRecord(record DNSRecord) error
// UpdateDNSRecord updates an existing DNS record
UpdateDNSRecord(record DNSRecord) error
}

View File

@@ -0,0 +1,40 @@
package types
import (
"fmt"
"strings"
"github.com/sirupsen/logrus"
)
// DNSRecord defines what we understand as a DNSRecord
type DNSRecord struct {
// Name the DNS host name
Name string `json:"name"`
// Value the value of this record
Value string `json:"value"`
// Type the record type
Type string `json:"type"`
}
// Check verifies if the DNS record satisfies certain conditions
func (record *DNSRecord) Check() []string {
logrus.Infof("Record to check: '%v'", record)
emptyValueErrorMessage := "the value of field '%s' cannot be empty"
var errs []string
if strings.TrimSpace(record.Name) == "" {
errs = append(errs, fmt.Sprintf(emptyValueErrorMessage, "name"))
}
if strings.TrimSpace(record.Value) == "" {
errs = append(errs, fmt.Sprintf(emptyValueErrorMessage, "value"))
}
if strings.TrimSpace(record.Type) == "" {
errs = append(errs, fmt.Sprintf(emptyValueErrorMessage, "type"))
}
return errs
}

View File

@@ -0,0 +1,51 @@
package types
import (
"fmt"
"github.com/sirupsen/logrus"
"net/http"
)
// Error groups together information that defines an error. Should always be used to
type Error struct {
Message string `json:"message"`
Code int `json:"code"`
Details []string `json:"details,omitempty"`
Err error `json:"-"`
}
// Error() gives a string representing the error; also, forces the Error type to comply with the error interface
func (e *Error) Error() string {
msg := fmt.Sprintf("ERROR (%v): %s; \n Inner error: %s", e.Code, e.Message, e.Err)
logrus.Debug(msg)
return msg
}
// BadRequestError create an Error instance with http.StatusBadRequest code
func BadRequestError(message string, err error, details ...string) *Error {
return &Error{Message: message, Err: err, Code: http.StatusBadRequest, Details: details}
}
// BadRequestError create an Error instance with http.StatusNotFound code
func NotFoundError(message string, err error, details ...string) *Error {
return &Error{Message: message, Err: err, Code: http.StatusNotFound, Details: details}
}
// BadRequestError create an Error instance with http.StatusInternalServerError code
func InternalServerError(message string, err error, details ...string) *Error {
return &Error{Message: message, Err: err, Code: http.StatusInternalServerError, Details: details}
}
// PanicIfError is just a wrapper to a panic call that propagates error when it's not nil
func PanicIfError(e error) {
if e != nil {
logrus.Errorf(e.Error())
panic(e)
}
}
// Panic wraps a panic call propagating the given error parameter
func Panic(e Error) {
logrus.Errorf(e.Error())
panic(e)
}

21
vendor/github.com/labbsr0x/goh/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Abilio Esteves
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

114
vendor/github.com/labbsr0x/goh/gohclient/gohclient.go generated vendored Normal file
View File

@@ -0,0 +1,114 @@
package gohclient
import (
"bytes"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/go-errors/errors"
"github.com/sirupsen/logrus"
)
// API defines an interface for helper methods that encapsulates http requests complexities
type API interface {
Put(url string, data []byte) (*http.Response, []byte, error)
Post(url string, data []byte) (*http.Response, []byte, error)
Get(url string) (*http.Response, []byte, error)
Delete(url string) (*http.Response, []byte, error)
}
// Default defines a struct that handles with HTTP requests for a bindman webhook client
type Default struct {
// User agent used when communicating with the API
UserAgent string
// Request content type used when communicating with the API
ContentType string
Accept string
BaseURL *url.URL
HTTPClient *http.Client
}
// New instantiates a default goh client
// If a nil httpClient is provided, http.DefaultClient will be used.
func New(httpClient *http.Client, baseURL string) (*Default, error) {
if httpClient == nil {
httpClient = http.DefaultClient
}
if strings.TrimSpace(baseURL) == "" {
return nil, errors.New("base URL cannot be an empty string")
}
parsedURL, err := url.Parse(baseURL)
if err != nil {
return nil, err
}
return &Default{
BaseURL: parsedURL,
HTTPClient: httpClient,
}, nil
}
// Put wraps the call to http.NewRequest apis and properly submits a new HTTP POST request
func (c *Default) Put(path string, data []byte) (*http.Response, []byte, error) {
return c.request(path, "PUT", data)
}
// Post wraps the call to http.NewRequest apis and properly submits a new HTTP POST request
func (c *Default) Post(path string, data []byte) (*http.Response, []byte, error) {
return c.request(path, "POST", data)
}
// Get wraps the call to http.NewRequest apis and properly submits a new HTTP GET request
func (c *Default) Get(path string) (*http.Response, []byte, error) {
return c.request(path, "GET", nil)
}
// Delete wraps the call to http.NewRequest apis and properly submits a new HTTP DELETE request
func (c *Default) Delete(path string) (*http.Response, []byte, error) {
return c.request(path, "DELETE", nil)
}
// request defines a generic method to execute http requests
func (c *Default) request(path, method string, body []byte) (resp *http.Response, data []byte, err error) {
u, err := c.BaseURL.Parse(path)
if err != nil {
return
}
req, err := http.NewRequest(method, u.String(), bytes.NewBuffer(body))
if err != nil {
logrus.Errorf("HTTP request creation failed. err=%v", err)
return
}
if body != nil && strings.TrimSpace(c.ContentType) != "" {
req.Header.Set("Content-Type", c.ContentType)
}
if strings.TrimSpace(c.Accept) != "" {
req.Header.Set("Accept", c.Accept)
}
if strings.TrimSpace(c.UserAgent) != "" {
req.Header.Set("User-Agent", c.UserAgent)
}
logrus.Debugf("%v request=%v", method, req)
resp, err = c.HTTPClient.Do(req)
if err != nil {
logrus.Errorf("HTTP %v request invocation failed. err=%v", method, err)
return
}
defer dClose(resp.Body)
logrus.Debugf("Response: %v", resp)
data, err = ioutil.ReadAll(resp.Body)
logrus.Debugf("Response body: %v", data)
return
}
func dClose(c io.Closer) {
if err := c.Close(); err != nil {
logrus.Errorf("HTTP response body close invocation failed. err=%v", err)
}
}

View File

@@ -363,7 +363,12 @@ func (f *httpForwarder) serveWebSocket(w http.ResponseWriter, req *http.Request,
ctx.errHandler.ServeHTTP(w, req, errHijack)
return
}
defer conn.Close()
defer func() {
conn.Close()
if f.websocketConnectionClosedHook != nil {
f.websocketConnectionClosedHook(req, conn)
}
}()
errWrite := resp.Write(conn)
if errWrite != nil {

File diff suppressed because it is too large Load Diff

View File

@@ -108,4 +108,8 @@ type StartSpanConfig struct {
// Tags holds a set of key/value pairs that should be set as metadata on the
// new span.
Tags map[string]interface{}
// Force-set the SpanID, rather than use a random number. If no Parent SpanContext is present,
// then this will also set the TraceID to the same value.
SpanID uint64
}

View File

@@ -27,10 +27,15 @@ const (
// HTTPURL sets the HTTP URL for a span.
HTTPURL = "http.url"
// TODO: In the next major version, suffix these constants (SpanType, etc)
// with "*Key" (SpanTypeKey, etc) to more easily differentiate between
// TODO: In the next major version, prefix these constants (SpanType, etc)
// with "Key*" (KeySpanType, etc) to more easily differentiate between
// constants representing tag values and constants representing keys.
// SpanName is a pseudo-key for setting a span's operation name by means of
// a tag. It is mostly here to facilitate vendor-agnostic frameworks like Opentracing
// and OpenCensus.
SpanName = "span.name"
// SpanType defines the Span type (web, db, cache).
SpanType = "span.type"
@@ -54,4 +59,20 @@ const (
// Environment specifies the environment to use with a trace.
Environment = "env"
// EventSampleRate specifies the rate at which this span will be sampled
// as an APM event.
EventSampleRate = "_dd1.sr.eausr"
// AnalyticsEvent specifies whether the span should be recorded as a Trace
// Search & Analytics event.
AnalyticsEvent = "analytics.event"
// ManualKeep is a tag which specifies that the trace to which this span
// belongs to should be kept when set to true.
ManualKeep = "manual.keep"
// ManualDrop is a tag which specifies that the trace to which this span
// belongs to should be dropped when set to true.
ManualDrop = "manual.drop"
)

View File

@@ -18,6 +18,11 @@ func ResourceName(name string) opentracing.StartSpanOption {
return opentracing.Tag{Key: ext.ResourceName, Value: name}
}
// SpanName sets the Datadog operation name for the span.
func SpanName(name string) opentracing.StartSpanOption {
return opentracing.Tag{Key: ext.SpanName, Value: name}
}
// SpanType can be used with opentracing.StartSpan to set the type of a span.
func SpanType(name string) opentracing.StartSpanOption {
return opentracing.Tag{Key: ext.SpanType, Value: name}

View File

@@ -8,6 +8,7 @@ import (
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
"gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig"
)
// config holds the tracer configuration.
@@ -117,6 +118,22 @@ func WithHTTPRoundTripper(r http.RoundTripper) StartOption {
}
}
// WithAnalytics allows specifying whether Trace Search & Analytics should be enabled
// for integrations.
func WithAnalytics(on bool) StartOption {
if on {
return WithAnalyticsRate(1.0)
}
return WithAnalyticsRate(0.0)
}
// WithAnalyticsRate sets the global sampling rate for sampling APM events.
func WithAnalyticsRate(rate float64) StartOption {
return func(_ *config) {
globalconfig.SetAnalyticsRate(rate)
}
}
// StartSpanOption is a configuration option for StartSpan. It is aliased in order
// to help godoc group all the functions returning it together. It is considered
// more correct to refer to it as the type as the origin, ddtrace.StartSpanOption.
@@ -149,6 +166,15 @@ func SpanType(name string) StartSpanOption {
return Tag(ext.SpanType, name)
}
// WithSpanID sets the SpanID on the started span, instead of using a random number.
// If there is no parent Span (eg from ChildOf), then the TraceID will also be set to the
// value given here.
func WithSpanID(id uint64) StartSpanOption {
return func(cfg *ddtrace.StartSpanConfig) {
cfg.SpanID = id
}
}
// ChildOf tells StartSpan to use the given span context as a parent for the
// created span.
func ChildOf(ctx ddtrace.SpanContext) StartSpanOption {
@@ -179,7 +205,8 @@ func FinishTime(t time.Time) FinishOption {
}
// WithError marks the span as having had an error. It uses the information from
// err to set tags such as the error message, error type and stack trace.
// err to set tags such as the error message, error type and stack trace. It has
// no effect if the error is nil.
func WithError(err error) FinishOption {
return func(cfg *ddtrace.FinishConfig) {
cfg.Error = err

View File

@@ -140,5 +140,5 @@ func (ps *prioritySampler) apply(spn *span) {
} else {
spn.SetTag(ext.SamplingPriority, ext.PriorityAutoReject)
}
spn.SetTag(samplingPriorityRateKey, rate)
spn.SetTag(keySamplingPriorityRate, rate)
}

View File

@@ -80,10 +80,15 @@ func (s *span) SetTag(key string, value interface{}) {
if s.finished {
return
}
if key == ext.Error {
switch key {
case ext.Error:
s.setTagError(value, true)
return
}
if v, ok := value.(bool); ok {
s.setTagBool(key, v)
return
}
if v, ok := value.(string); ok {
s.setTagString(key, v)
return
@@ -133,6 +138,8 @@ func (s *span) setTagError(value interface{}, debugStack bool) {
// setTagString sets a string tag. This method is not safe for concurrent use.
func (s *span) setTagString(key, v string) {
switch key {
case ext.SpanName:
s.Name = v
case ext.ServiceName:
s.Service = v
case ext.ResourceName:
@@ -144,13 +151,39 @@ func (s *span) setTagString(key, v string) {
}
}
// setTagBool sets a boolean tag on the span.
func (s *span) setTagBool(key string, v bool) {
switch key {
case ext.AnalyticsEvent:
if v {
s.setTagNumeric(ext.EventSampleRate, 1.0)
} else {
s.setTagNumeric(ext.EventSampleRate, 0.0)
}
case ext.ManualDrop:
if v {
s.setTagNumeric(ext.SamplingPriority, ext.PriorityUserReject)
}
case ext.ManualKeep:
if v {
s.setTagNumeric(ext.SamplingPriority, ext.PriorityUserKeep)
}
default:
if v {
s.setTagString(key, "true")
} else {
s.setTagString(key, "false")
}
}
}
// setTagNumeric sets a numeric tag, in our case called a metric. This method
// is not safe for concurrent use.
func (s *span) setTagNumeric(key string, v float64) {
switch key {
case ext.SamplingPriority:
// setting sampling priority per spec
s.Metrics[samplingPriorityKey] = v
s.Metrics[keySamplingPriority] = v
s.context.setSamplingPriority(int(v))
default:
s.Metrics[key] = v
@@ -236,6 +269,7 @@ func (s *span) String() string {
}
const (
samplingPriorityKey = "_sampling_priority_v1"
samplingPriorityRateKey = "_sampling_priority_rate_v1"
keySamplingPriority = "_sampling_priority_v1"
keySamplingPriorityRate = "_sampling_priority_rate_v1"
keyOrigin = "_dd.origin"
)

View File

@@ -25,10 +25,9 @@ type spanContext struct {
traceID uint64
spanID uint64
mu sync.RWMutex // guards below fields
baggage map[string]string
priority int
hasPriority bool
mu sync.RWMutex // guards below fields
baggage map[string]string
origin string // e.g. "synthetics"
}
// newSpanContext creates a new SpanContext to serve as context for the given
@@ -42,15 +41,10 @@ func newSpanContext(span *span, parent *spanContext) *spanContext {
spanID: span.SpanID,
span: span,
}
if v, ok := span.Metrics[samplingPriorityKey]; ok {
context.hasPriority = true
context.priority = int(v)
}
if parent != nil {
context.trace = parent.trace
context.drop = parent.drop
context.hasPriority = parent.hasSamplingPriority()
context.priority = parent.samplingPriority()
context.origin = parent.origin
parent.ForeachBaggageItem(func(k, v string) bool {
context.setBaggageItem(k, v)
return true
@@ -59,6 +53,10 @@ func newSpanContext(span *span, parent *spanContext) *spanContext {
if context.trace == nil {
context.trace = newTrace()
}
if context.trace.root == nil {
// first span in the trace can safely be assumed to be the root
context.trace.root = span
}
// put span in context's trace
context.trace.push(span)
return context
@@ -82,22 +80,21 @@ func (c *spanContext) ForeachBaggageItem(handler func(k, v string) bool) {
}
func (c *spanContext) setSamplingPriority(p int) {
c.mu.Lock()
defer c.mu.Unlock()
c.priority = p
c.hasPriority = true
if c.trace == nil {
c.trace = newTrace()
}
c.trace.setSamplingPriority(float64(p))
}
func (c *spanContext) samplingPriority() int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.priority
if c.trace == nil {
return 0
}
return c.trace.samplingPriority()
}
func (c *spanContext) hasSamplingPriority() bool {
c.mu.RLock()
defer c.mu.RUnlock()
return c.hasPriority
return c.trace != nil && c.trace.hasSamplingPriority()
}
func (c *spanContext) setBaggageItem(key, val string) {
@@ -116,15 +113,23 @@ func (c *spanContext) baggageItem(key string) string {
}
// finish marks this span as finished in the trace.
func (c *spanContext) finish() { c.trace.ackFinish() }
func (c *spanContext) finish() { c.trace.finishedOne() }
// trace holds information about a specific trace. This structure is shared
// between all spans in a trace.
// trace contains shared context information about a trace, such as sampling
// priority, the root reference and a buffer of the spans which are part of the
// trace, if these exist.
type trace struct {
mu sync.RWMutex // guards below fields
spans []*span // all the spans that are part of this trace
finished int // the number of finished spans
full bool // signifies that the span buffer is full
priority *float64 // sampling priority
locked bool // specifies if the sampling priority can be altered
// root specifies the root of the trace, if known; it is nil when a span
// context is extracted from a carrier, at which point there are no spans in
// the trace yet.
root *span
}
var (
@@ -146,6 +151,43 @@ func newTrace() *trace {
return &trace{spans: make([]*span, 0, traceStartSize)}
}
func (t *trace) hasSamplingPriority() bool {
t.mu.RLock()
defer t.mu.RUnlock()
return t.priority != nil
}
func (t *trace) samplingPriority() int {
t.mu.RLock()
defer t.mu.RUnlock()
if t.priority == nil {
return 0
}
return int(*t.priority)
}
func (t *trace) setSamplingPriority(p float64) {
t.mu.Lock()
defer t.mu.Unlock()
t.setSamplingPriorityLocked(p)
}
func (t *trace) setSamplingPriorityLocked(p float64) {
if t.locked {
return
}
if t.root == nil {
// this trace is part of a context that doesn't belong to a
// trace yet, meaning that the sampling priority is locked
// by a distributed trace.
t.locked = true
}
if t.priority == nil {
t.priority = new(float64)
}
*t.priority = p
}
// push pushes a new span into the trace. If the buffer is full, it returns
// a errBufferFull error.
func (t *trace) push(sp *span) {
@@ -164,12 +206,16 @@ func (t *trace) push(sp *span) {
}
return
}
if v, ok := sp.Metrics[keySamplingPriority]; ok {
t.setSamplingPriorityLocked(v)
}
t.spans = append(t.spans, sp)
}
// ackFinish aknowledges that another span in the trace has finished, and checks
// if the trace is complete, in which case it calls the onFinish function.
func (t *trace) ackFinish() {
// finishedOne aknowledges that another span in the trace has finished, and checks
// if the trace is complete, in which case it calls the onFinish function. It uses
// the given priority, if non-nil, to mark the root span.
func (t *trace) finishedOne() {
t.mu.Lock()
defer t.mu.Unlock()
if t.full {
@@ -185,6 +231,9 @@ func (t *trace) ackFinish() {
}
if tr, ok := internal.GetGlobalTracer().(*tracer); ok {
// we have a tracer that can receive completed traces.
if t.priority != nil {
t.root.Metrics[keySamplingPriority] = *t.priority
}
tr.pushTrace(t.spans)
}
t.spans = nil

View File

@@ -2,10 +2,12 @@ package tracer
import (
"net/http"
"os"
"strconv"
"strings"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
)
// HTTPHeadersCarrier wraps an http.Header as a TextMapWriter and TextMapReader, allowing
@@ -54,6 +56,11 @@ func (c TextMapCarrier) ForeachKey(handler func(key, val string) error) error {
return nil
}
const (
headerPropagationStyleInject = "DD_PROPAGATION_STYLE_INJECT"
headerPropagationStyleExtract = "DD_PROPAGATION_STYLE_EXTRACT"
)
const (
// DefaultBaggageHeaderPrefix specifies the prefix that will be used in
// HTTP headers or text maps to prefix baggage keys.
@@ -72,6 +79,10 @@ const (
DefaultPriorityHeader = "x-datadog-sampling-priority"
)
// originHeader specifies the name of the header indicating the origin of the trace.
// It is used with the Synthetics product and usually has the value "synthetics".
const originHeader = "x-datadog-origin"
// PropagatorConfig defines the configuration for initializing a propagator.
type PropagatorConfig struct {
// BaggagePrefix specifies the prefix that will be used to store baggage
@@ -110,21 +121,86 @@ func NewPropagator(cfg *PropagatorConfig) Propagator {
if cfg.PriorityHeader == "" {
cfg.PriorityHeader = DefaultPriorityHeader
}
return &propagator{cfg}
return &chainedPropagator{
injectors: getPropagators(cfg, headerPropagationStyleInject),
extractors: getPropagators(cfg, headerPropagationStyleExtract),
}
}
// propagator implements a propagator which uses TextMap internally.
// It propagates the trace and span IDs, as well as the baggage from the
// context.
type propagator struct{ cfg *PropagatorConfig }
// chainedPropagator implements Propagator and applies a list of injectors and extractors.
// When injecting, all injectors are called to propagate the span context.
// When extracting, it tries each extractor, selecting the first successful one.
type chainedPropagator struct {
injectors []Propagator
extractors []Propagator
}
// getPropagators returns a list of propagators based on the list found in the
// given environment variable. If the list doesn't contain a value or has invalid
// values, the default propagator will be returned.
func getPropagators(cfg *PropagatorConfig, env string) []Propagator {
dd := &propagator{cfg}
ps := os.Getenv(env)
if ps == "" {
return []Propagator{dd}
}
var list []Propagator
for _, v := range strings.Split(ps, ",") {
switch strings.ToLower(v) {
case "datadog":
list = append(list, dd)
case "b3":
list = append(list, &propagatorB3{})
default:
// TODO(cgilmour): consider logging something for invalid/unknown styles.
}
}
if len(list) == 0 {
// return the default
return []Propagator{dd}
}
return list
}
// Inject defines the Propagator to propagate SpanContext data
// out of the current process. The implementation propagates the
// TraceID and the current active SpanID, as well as the Span baggage.
func (p *chainedPropagator) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error {
for _, v := range p.injectors {
err := v.Inject(spanCtx, carrier)
if err != nil {
return err
}
}
return nil
}
// Extract implements Propagator.
func (p *chainedPropagator) Extract(carrier interface{}) (ddtrace.SpanContext, error) {
for _, v := range p.extractors {
ctx, err := v.Extract(carrier)
if ctx != nil {
// first extractor returns
return ctx, nil
}
if err == ErrSpanContextNotFound {
continue
}
return nil, err
}
return nil, ErrSpanContextNotFound
}
// propagator implements Propagator and injects/extracts span contexts
// using datadog headers. Only TextMap carriers are supported.
type propagator struct {
cfg *PropagatorConfig
}
func (p *propagator) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error {
switch v := carrier.(type) {
switch c := carrier.(type) {
case TextMapWriter:
return p.injectTextMap(spanCtx, v)
return p.injectTextMap(spanCtx, c)
default:
return ErrInvalidCarrier
}
@@ -141,6 +217,9 @@ func (p *propagator) injectTextMap(spanCtx ddtrace.SpanContext, writer TextMapWr
if ctx.hasSamplingPriority() {
writer.Set(p.cfg.PriorityHeader, strconv.Itoa(ctx.samplingPriority()))
}
if ctx.origin != "" {
writer.Set(originHeader, ctx.origin)
}
// propagate OpenTracing baggage
for k, v := range ctx.baggage {
writer.Set(p.cfg.BaggagePrefix+k, v)
@@ -148,11 +227,10 @@ func (p *propagator) injectTextMap(spanCtx ddtrace.SpanContext, writer TextMapWr
return nil
}
// Extract implements Propagator.
func (p *propagator) Extract(carrier interface{}) (ddtrace.SpanContext, error) {
switch v := carrier.(type) {
switch c := carrier.(type) {
case TextMapReader:
return p.extractTextMap(v)
return p.extractTextMap(c)
default:
return nil, ErrInvalidCarrier
}
@@ -180,6 +258,8 @@ func (p *propagator) extractTextMap(reader TextMapReader) (ddtrace.SpanContext,
return ErrSpanContextCorrupted
}
ctx.setSamplingPriority(priority)
case originHeader:
ctx.origin = v
default:
if strings.HasPrefix(key, p.cfg.BaggagePrefix) {
ctx.setBaggageItem(strings.TrimPrefix(key, p.cfg.BaggagePrefix), v)
@@ -195,3 +275,83 @@ func (p *propagator) extractTextMap(reader TextMapReader) (ddtrace.SpanContext,
}
return &ctx, nil
}
const (
b3TraceIDHeader = "x-b3-traceid"
b3SpanIDHeader = "x-b3-spanid"
b3SampledHeader = "x-b3-sampled"
)
// propagatorB3 implements Propagator and injects/extracts span contexts
// using B3 headers. Only TextMap carriers are supported.
type propagatorB3 struct{}
func (p *propagatorB3) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error {
switch c := carrier.(type) {
case TextMapWriter:
return p.injectTextMap(spanCtx, c)
default:
return ErrInvalidCarrier
}
}
func (*propagatorB3) injectTextMap(spanCtx ddtrace.SpanContext, writer TextMapWriter) error {
ctx, ok := spanCtx.(*spanContext)
if !ok || ctx.traceID == 0 || ctx.spanID == 0 {
return ErrInvalidSpanContext
}
writer.Set(b3TraceIDHeader, strconv.FormatUint(ctx.traceID, 16))
writer.Set(b3SpanIDHeader, strconv.FormatUint(ctx.spanID, 16))
if ctx.hasSamplingPriority() {
if ctx.samplingPriority() >= ext.PriorityAutoKeep {
writer.Set(b3SampledHeader, "1")
} else {
writer.Set(b3SampledHeader, "0")
}
}
return nil
}
func (p *propagatorB3) Extract(carrier interface{}) (ddtrace.SpanContext, error) {
switch c := carrier.(type) {
case TextMapReader:
return p.extractTextMap(c)
default:
return nil, ErrInvalidCarrier
}
}
func (*propagatorB3) extractTextMap(reader TextMapReader) (ddtrace.SpanContext, error) {
var ctx spanContext
err := reader.ForeachKey(func(k, v string) error {
var err error
key := strings.ToLower(k)
switch key {
case b3TraceIDHeader:
ctx.traceID, err = strconv.ParseUint(v, 16, 64)
if err != nil {
return ErrSpanContextCorrupted
}
case b3SpanIDHeader:
ctx.spanID, err = strconv.ParseUint(v, 16, 64)
if err != nil {
return ErrSpanContextCorrupted
}
case b3SampledHeader:
priority, err := strconv.Atoi(v)
if err != nil {
return ErrSpanContextCorrupted
}
ctx.setSamplingPriority(priority)
default:
}
return nil
})
if err != nil {
return nil, err
}
if ctx.traceID == 0 || ctx.spanID == 0 {
return nil, ErrSpanContextNotFound
}
return &ctx, nil
}

View File

@@ -230,7 +230,10 @@ func (t *tracer) StartSpan(operationName string, options ...ddtrace.StartSpanOpt
context = ctx
}
}
id := random.Uint64()
id := opts.SpanID
if id == 0 {
id = random.Uint64()
}
// span defaults
span := &span{
Name: operationName,
@@ -248,13 +251,19 @@ func (t *tracer) StartSpan(operationName string, options ...ddtrace.StartSpanOpt
span.TraceID = context.traceID
span.ParentID = context.spanID
if context.hasSamplingPriority() {
span.Metrics[samplingPriorityKey] = float64(context.samplingPriority())
span.Metrics[keySamplingPriority] = float64(context.samplingPriority())
}
if context.span != nil {
// it has a local parent, inherit the service
// local parent, inherit service
context.span.RLock()
span.Service = context.span.Service
context.span.RUnlock()
} else {
// remote parent
if context.origin != "" {
// mark origin
span.Meta[keyOrigin] = context.origin
}
}
}
span.context = newSpanContext(span, context)
@@ -361,7 +370,7 @@ const sampleRateMetricKey = "_sample_rate"
// Sample samples a span with the internal sampler.
func (t *tracer) sample(span *span) {
if span.context.hasPriority {
if span.context.hasSamplingPriority() {
// sampling decision was already made
return
}

View File

@@ -15,7 +15,7 @@ import (
var (
// TODO(gbbr): find a more effective way to keep this up to date,
// e.g. via `go generate`
tracerVersion = "v1.7.0"
tracerVersion = "v1.10.0"
// We copy the transport to avoid using the default one, as it might be
// augmented with tracing and we don't want these calls to be recorded.

View File

@@ -0,0 +1,28 @@
// Package globalconfig stores configuration which applies globally to both the tracer
// and integrations.
package globalconfig
import "sync"
var cfg = &config{}
type config struct {
mu sync.RWMutex
analyticsRate float64
}
// AnalyticsRate returns the sampling rate at which events should be marked. It uses
// synchronizing mechanisms, meaning that for optimal performance it's best to read it
// once and store it.
func AnalyticsRate() float64 {
cfg.mu.RLock()
defer cfg.mu.RUnlock()
return cfg.analyticsRate
}
// SetAnalyticsRate sets the given event sampling rate globally.
func SetAnalyticsRate(rate float64) {
cfg.mu.Lock()
cfg.analyticsRate = rate
cfg.mu.Unlock()
}