Redirection: permanent move option.
This commit is contained in:
parent
c944d203fb
commit
58d6681824
83 changed files with 622 additions and 8611 deletions
28
Gopkg.lock
generated
28
Gopkg.lock
generated
|
@ -222,12 +222,6 @@
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "3a0bb77429bd3a61596f5e8a3172445844342120"
|
revision = "3a0bb77429bd3a61596f5e8a3172445844342120"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/codegangsta/cli"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1"
|
|
||||||
version = "v1.20.0"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/containerd/continuity"
|
name = "github.com/containerd/continuity"
|
||||||
|
@ -1158,24 +1152,6 @@
|
||||||
revision = "939c094524d124c55fa8afe0e077701db4a865e2"
|
revision = "939c094524d124c55fa8afe0e077701db4a865e2"
|
||||||
version = "v1.0.0"
|
version = "v1.0.0"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/vulcand/route"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "61904570391bdf22252f8e376b49a57593d9124c"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/vulcand/vulcand"
|
|
||||||
packages = [
|
|
||||||
"conntracker",
|
|
||||||
"plugin",
|
|
||||||
"plugin/cacheprovider",
|
|
||||||
"plugin/rewrite",
|
|
||||||
"router"
|
|
||||||
]
|
|
||||||
revision = "3ab68471860269ba2afdd1a97e2d058087631975"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/xeipuuv/gojsonpointer"
|
name = "github.com/xeipuuv/gojsonpointer"
|
||||||
|
@ -1228,8 +1204,6 @@
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/crypto"
|
name = "golang.org/x/crypto"
|
||||||
packages = [
|
packages = [
|
||||||
"acme",
|
|
||||||
"acme/autocert",
|
|
||||||
"bcrypt",
|
"bcrypt",
|
||||||
"blowfish",
|
"blowfish",
|
||||||
"ocsp",
|
"ocsp",
|
||||||
|
@ -1518,6 +1492,6 @@
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "b2cb3354504a6350e3022513b39206a3ece7c2f1a509bcef907c45422a8afb31"
|
inputs-digest = "daede4415dbd8614087d38fc4d8cba45d679bbb7185bfca805091ef7b295341f"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
|
@ -188,10 +188,6 @@
|
||||||
name = "k8s.io/client-go"
|
name = "k8s.io/client-go"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/vulcand/vulcand"
|
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/libkermit/docker"
|
name = "github.com/libkermit/docker"
|
||||||
|
|
|
@ -139,6 +139,7 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
regex = "{{ $redirect.Regex }}"
|
regex = "{{ $redirect.Regex }}"
|
||||||
replacement = "{{ $redirect.Replacement }}"
|
replacement = "{{ $redirect.Replacement }}"
|
||||||
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if hasErrorPages $service.Attributes }}
|
{{if hasErrorPages $service.Attributes }}
|
||||||
|
@ -343,6 +344,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
regex = "{{ $redirect.Regex }}"
|
regex = "{{ $redirect.Regex }}"
|
||||||
replacement = "{{ $redirect.Replacement }}"
|
replacement = "{{ $redirect.Replacement }}"
|
||||||
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getServiceErrorPages $container $serviceName }}
|
{{ $errorPages := getServiceErrorPages $container $serviceName }}
|
||||||
|
@ -458,6 +460,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
regex = "{{ $redirect.Regex }}"
|
regex = "{{ $redirect.Regex }}"
|
||||||
replacement = "{{ $redirect.Replacement }}"
|
replacement = "{{ $redirect.Replacement }}"
|
||||||
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getErrorPages $container }}
|
{{ $errorPages := getErrorPages $container }}
|
||||||
|
@ -648,6 +651,7 @@ var _templatesEcsTmpl = []byte(`[backends]
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
regex = "{{ $redirect.Regex }}"
|
regex = "{{ $redirect.Regex }}"
|
||||||
replacement = "{{ $redirect.Replacement }}"
|
replacement = "{{ $redirect.Replacement }}"
|
||||||
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getErrorPages $instance }}
|
{{ $errorPages := getErrorPages $instance }}
|
||||||
|
@ -1040,6 +1044,7 @@ var _templatesKvTmpl = []byte(`[backends]
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
regex = "{{ $redirect.Regex }}"
|
regex = "{{ $redirect.Regex }}"
|
||||||
replacement = "{{ $redirect.Replacement }}"
|
replacement = "{{ $redirect.Replacement }}"
|
||||||
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getErrorPages $frontend }}
|
{{ $errorPages := getErrorPages $frontend }}
|
||||||
|
@ -1249,6 +1254,7 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
regex = "{{ $redirect.Regex }}"
|
regex = "{{ $redirect.Regex }}"
|
||||||
replacement = "{{ $redirect.Replacement }}"
|
replacement = "{{ $redirect.Replacement }}"
|
||||||
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getErrorPages $app $serviceName }}
|
{{ $errorPages := getErrorPages $app $serviceName }}
|
||||||
|
@ -1440,6 +1446,7 @@ var _templatesMesosTmpl = []byte(`[backends]
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
regex = "{{ $redirect.Regex }}"
|
regex = "{{ $redirect.Regex }}"
|
||||||
replacement = "{{ $redirect.Replacement }}"
|
replacement = "{{ $redirect.Replacement }}"
|
||||||
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getErrorPages $app }}
|
{{ $errorPages := getErrorPages $app }}
|
||||||
|
@ -1652,6 +1659,7 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
regex = "{{ $redirect.Regex }}"
|
regex = "{{ $redirect.Regex }}"
|
||||||
replacement = "{{ $redirect.Replacement }}"
|
replacement = "{{ $redirect.Replacement }}"
|
||||||
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getErrorPages $service }}
|
{{ $errorPages := getErrorPages $service }}
|
||||||
|
|
|
@ -95,9 +95,10 @@ Additional settings can be defined using Consul Catalog tags.
|
||||||
| `<prefix>.frontend.rateLimit.rateSet.<name>.period=6` | See [custom error pages](/configuration/commons/#rate-limiting) section. |
|
| `<prefix>.frontend.rateLimit.rateSet.<name>.period=6` | See [custom error pages](/configuration/commons/#rate-limiting) section. |
|
||||||
| `<prefix>.frontend.rateLimit.rateSet.<name>.average=6` | See [custom error pages](/configuration/commons/#rate-limiting) section. |
|
| `<prefix>.frontend.rateLimit.rateSet.<name>.average=6` | See [custom error pages](/configuration/commons/#rate-limiting) section. |
|
||||||
| `<prefix>.frontend.rateLimit.rateSet.<name>.burst=6` | See [custom error pages](/configuration/commons/#rate-limiting) section. |
|
| `<prefix>.frontend.rateLimit.rateSet.<name>.burst=6` | See [custom error pages](/configuration/commons/#rate-limiting) section. |
|
||||||
| `<prefix>.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) . |
|
| `<prefix>.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS). |
|
||||||
| `<prefix>.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
| `<prefix>.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||||
| `<prefix>.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
| `<prefix>.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||||
|
| `<prefix>.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||||
| `<prefix>.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{{.ServiceName}}.{{.Domain}}`. |
|
| `<prefix>.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{{.ServiceName}}.{{.Domain}}`. |
|
||||||
| `<prefix>.frontend.whitelistSourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
| `<prefix>.frontend.whitelistSourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||||
|
|
||||||
|
|
|
@ -202,6 +202,7 @@ Labels can be used on containers to override default behaviour.
|
||||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
||||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||||
|
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||||
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. |
|
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. |
|
||||||
| `traefik.frontend.whitelistSourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
| `traefik.frontend.whitelistSourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||||
|
|
||||||
|
@ -255,6 +256,7 @@ Services labels can be used for overriding default behaviour
|
||||||
| `traefik.<service-name>.frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. |
|
| `traefik.<service-name>.frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. |
|
||||||
| `traefik.<service-name>.frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. |
|
| `traefik.<service-name>.frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. |
|
||||||
| `traefik.<service-name>.frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. |
|
| `traefik.<service-name>.frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. |
|
||||||
|
| `traefik.<service-name>.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||||
| `traefik.<service-name>.frontend.rule` | Overrides `traefik.frontend.rule`. |
|
| `traefik.<service-name>.frontend.rule` | Overrides `traefik.frontend.rule`. |
|
||||||
| `traefik.<service-name>.frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. |
|
| `traefik.<service-name>.frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. |
|
||||||
|
|
||||||
|
|
|
@ -162,6 +162,7 @@ Labels can be used on task containers to override default behaviour:
|
||||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
||||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||||
|
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||||
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{instance_name}.{domain}`. |
|
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{instance_name}.{domain}`. |
|
||||||
| `traefik.frontend.whitelistSourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
| `traefik.frontend.whitelistSourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||||
|
|
||||||
|
|
|
@ -119,6 +119,7 @@ Træfik can be configured with a file.
|
||||||
entryPoint = "https"
|
entryPoint = "https"
|
||||||
regex = "^http://localhost/(.*)"
|
regex = "^http://localhost/(.*)"
|
||||||
replacement = "http://mydomain/$1"
|
replacement = "http://mydomain/$1"
|
||||||
|
permanent = true
|
||||||
|
|
||||||
[frontends.frontend2]
|
[frontends.frontend2]
|
||||||
# ...
|
# ...
|
||||||
|
|
|
@ -110,6 +110,7 @@ The following general annotations are applicable on the Ingress object:
|
||||||
| `traefik.ingress.kubernetes.io/priority: "3"` | Override the default frontend rule priority. |
|
| `traefik.ingress.kubernetes.io/priority: "3"` | Override the default frontend rule priority. |
|
||||||
| `traefik.ingress.kubernetes.io/rate-limit: <YML>` | (2) See [custom error pages](/configuration/commons/#rate-limiting) section. |
|
| `traefik.ingress.kubernetes.io/rate-limit: <YML>` | (2) See [custom error pages](/configuration/commons/#rate-limiting) section. |
|
||||||
| `traefik.ingress.kubernetes.io/redirect-entry-point: https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS). |
|
| `traefik.ingress.kubernetes.io/redirect-entry-point: https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS). |
|
||||||
|
| `traefik.ingress.kubernetes.io/redirect-permanent: true` | Return 301 instead of 302. |
|
||||||
| `traefik.ingress.kubernetes.io/redirect-regex: ^http://localhost/(.*)` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-replacement`. |
|
| `traefik.ingress.kubernetes.io/redirect-regex: ^http://localhost/(.*)` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-replacement`. |
|
||||||
| `traefik.ingress.kubernetes.io/redirect-replacement: http://mydomain/$1` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-regex`. |
|
| `traefik.ingress.kubernetes.io/redirect-replacement: http://mydomain/$1` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-regex`. |
|
||||||
| `traefik.ingress.kubernetes.io/rewrite-target: /users` | Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header. |
|
| `traefik.ingress.kubernetes.io/rewrite-target: /users` | Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header. |
|
||||||
|
|
|
@ -199,6 +199,7 @@ The following labels can be defined on Marathon applications. They adjust the be
|
||||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
||||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||||
|
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||||
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{sub_domain}.{domain}`. |
|
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{sub_domain}.{domain}`. |
|
||||||
| `traefik.frontend.whitelistSourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
| `traefik.frontend.whitelistSourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||||
|
|
||||||
|
@ -253,6 +254,7 @@ For applications that expose multiple ports, specific labels can be used to extr
|
||||||
| `traefik.<service-name>.frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. |
|
| `traefik.<service-name>.frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. |
|
||||||
| `traefik.<service-name>.frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. |
|
| `traefik.<service-name>.frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. |
|
||||||
| `traefik.<service-name>.frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. |
|
| `traefik.<service-name>.frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. |
|
||||||
|
| `traefik.<service-name>.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||||
| `traefik.<service-name>.frontend.rule=EXP` | Overrides `traefik.frontend.rule`. Default: `{service_name}.{sub_domain}.{domain}` |
|
| `traefik.<service-name>.frontend.rule=EXP` | Overrides `traefik.frontend.rule`. Default: `{service_name}.{sub_domain}.{domain}` |
|
||||||
| `traefik.<service-name>.frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. |
|
| `traefik.<service-name>.frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. |
|
||||||
|
|
||||||
|
|
|
@ -134,6 +134,7 @@ The following labels can be defined on Mesos tasks. They adjust the behaviour fo
|
||||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
||||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||||
|
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||||
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{discovery_name}.{domain}`. |
|
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{discovery_name}.{domain}`. |
|
||||||
| `traefik.frontend.whitelistSourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
| `traefik.frontend.whitelistSourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||||
|
|
||||||
|
|
|
@ -158,6 +158,7 @@ Labels can be used on task containers to override default behaviour:
|
||||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
||||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||||
|
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||||
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{service_name}.{stack_name}.{domain}`. |
|
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{service_name}.{stack_name}.{domain}`. |
|
||||||
| `traefik.frontend.whitelistSourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
| `traefik.frontend.whitelistSourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||||
|
|
||||||
|
|
144
middlewares/redirect/redirect.go
Normal file
144
middlewares/redirect/redirect.go
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
package redirect
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/configuration"
|
||||||
|
"github.com/urfave/negroni"
|
||||||
|
"github.com/vulcand/oxy/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultRedirectRegex = `^(?:https?:\/\/)?([\w\._-]+)(?::\d+)?(.*)$`
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewEntryPointHandler create a new redirection handler base on entry point
|
||||||
|
func NewEntryPointHandler(dstEntryPoint *configuration.EntryPoint, permanent bool) (negroni.Handler, error) {
|
||||||
|
exp := regexp.MustCompile(`(:\d+)`)
|
||||||
|
match := exp.FindStringSubmatch(dstEntryPoint.Address)
|
||||||
|
if len(match) == 0 {
|
||||||
|
return nil, fmt.Errorf("bad Address format %q", dstEntryPoint.Address)
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol := "http"
|
||||||
|
if dstEntryPoint.TLS != nil {
|
||||||
|
protocol = "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
replacement := protocol + "://$1" + match[0] + "$2"
|
||||||
|
|
||||||
|
return NewRegexHandler(defaultRedirectRegex, replacement, permanent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRegexHandler create a new redirection handler base on regex
|
||||||
|
func NewRegexHandler(exp string, replacement string, permanent bool) (negroni.Handler, error) {
|
||||||
|
re, err := regexp.Compile(exp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &handler{
|
||||||
|
regexp: re,
|
||||||
|
replacement: replacement,
|
||||||
|
permanent: permanent,
|
||||||
|
errHandler: utils.DefaultHandler,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type handler struct {
|
||||||
|
regexp *regexp.Regexp
|
||||||
|
replacement string
|
||||||
|
permanent bool
|
||||||
|
errHandler utils.ErrorHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
|
||||||
|
oldURL := rawURL(req)
|
||||||
|
|
||||||
|
// only continue if the Regexp param matches the URL
|
||||||
|
if !h.regexp.MatchString(oldURL) {
|
||||||
|
next.ServeHTTP(rw, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply a rewrite regexp to the URL
|
||||||
|
newURL := h.regexp.ReplaceAllString(oldURL, h.replacement)
|
||||||
|
|
||||||
|
// replace any variables that may be in there
|
||||||
|
rewrittenURL := &bytes.Buffer{}
|
||||||
|
if err := applyString(newURL, rewrittenURL, req); err != nil {
|
||||||
|
h.errHandler.ServeHTTP(rw, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the rewritten URL and replace request URL with it
|
||||||
|
parsedURL, err := url.Parse(rewrittenURL.String())
|
||||||
|
if err != nil {
|
||||||
|
h.errHandler.ServeHTTP(rw, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if newURL != oldURL {
|
||||||
|
handler := &moveHandler{location: parsedURL, permanent: h.permanent}
|
||||||
|
handler.ServeHTTP(rw, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.URL = parsedURL
|
||||||
|
|
||||||
|
// make sure the request URI corresponds the rewritten URL
|
||||||
|
req.RequestURI = req.URL.RequestURI()
|
||||||
|
next.ServeHTTP(rw, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
type moveHandler struct {
|
||||||
|
location *url.URL
|
||||||
|
permanent bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *moveHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
rw.Header().Set("Location", m.location.String())
|
||||||
|
status := http.StatusFound
|
||||||
|
if m.permanent {
|
||||||
|
status = http.StatusMovedPermanently
|
||||||
|
}
|
||||||
|
rw.WriteHeader(status)
|
||||||
|
rw.Write([]byte(http.StatusText(status)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func rawURL(request *http.Request) string {
|
||||||
|
scheme := "http"
|
||||||
|
if request.TLS != nil || isXForwardedHTTPS(request) {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join([]string{scheme, "://", request.Host, request.RequestURI}, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isXForwardedHTTPS(request *http.Request) bool {
|
||||||
|
xForwardedProto := request.Header.Get("X-Forwarded-Proto")
|
||||||
|
|
||||||
|
return len(xForwardedProto) > 0 && xForwardedProto == "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyString(in string, out io.Writer, request *http.Request) error {
|
||||||
|
t, err := template.New("t").Parse(in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := struct {
|
||||||
|
Request *http.Request
|
||||||
|
}{
|
||||||
|
Request: request,
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.Execute(out, data)
|
||||||
|
}
|
175
middlewares/redirect/redirect_test.go
Normal file
175
middlewares/redirect/redirect_test.go
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
package redirect
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/configuration"
|
||||||
|
"github.com/containous/traefik/testhelpers"
|
||||||
|
"github.com/containous/traefik/tls"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewEntryPointHandler(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
entryPoint *configuration.EntryPoint
|
||||||
|
permanent bool
|
||||||
|
url string
|
||||||
|
expectedURL string
|
||||||
|
expectedStatus int
|
||||||
|
errorExpected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "HTTP to HTTPS",
|
||||||
|
entryPoint: &configuration.EntryPoint{Address: ":443", TLS: &tls.TLS{}},
|
||||||
|
url: "http://foo:80",
|
||||||
|
expectedURL: "https://foo:443",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTPS to HTTP",
|
||||||
|
entryPoint: &configuration.EntryPoint{Address: ":80"},
|
||||||
|
url: "https://foo:443",
|
||||||
|
expectedURL: "http://foo:80",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTP to HTTP",
|
||||||
|
entryPoint: &configuration.EntryPoint{Address: ":88"},
|
||||||
|
url: "http://foo:80",
|
||||||
|
expectedURL: "http://foo:88",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTP to HTTPS permanent",
|
||||||
|
entryPoint: &configuration.EntryPoint{Address: ":443", TLS: &tls.TLS{}},
|
||||||
|
permanent: true,
|
||||||
|
url: "http://foo:80",
|
||||||
|
expectedURL: "https://foo:443",
|
||||||
|
expectedStatus: http.StatusMovedPermanently,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTPS to HTTP permanent",
|
||||||
|
entryPoint: &configuration.EntryPoint{Address: ":80"},
|
||||||
|
permanent: true,
|
||||||
|
url: "https://foo:443",
|
||||||
|
expectedURL: "http://foo:80",
|
||||||
|
expectedStatus: http.StatusMovedPermanently,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid address",
|
||||||
|
entryPoint: &configuration.EntryPoint{Address: ":foo", TLS: &tls.TLS{}},
|
||||||
|
url: "http://foo:80",
|
||||||
|
errorExpected: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
handler, err := NewEntryPointHandler(test.entryPoint, test.permanent)
|
||||||
|
|
||||||
|
if test.errorExpected {
|
||||||
|
require.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
r := testhelpers.MustNewRequest(http.MethodGet, test.url, nil)
|
||||||
|
handler.ServeHTTP(recorder, r, nil)
|
||||||
|
|
||||||
|
location, err := recorder.Result().Location()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expectedURL, location.String())
|
||||||
|
assert.Equal(t, test.expectedStatus, recorder.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewRegexHandler(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
regex string
|
||||||
|
replacement string
|
||||||
|
permanent bool
|
||||||
|
url string
|
||||||
|
expectedURL string
|
||||||
|
expectedStatus int
|
||||||
|
errorExpected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "simple redirection",
|
||||||
|
regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`,
|
||||||
|
replacement: "https://$1{{\"bar\"}}$2:443$4",
|
||||||
|
url: "http://foo.com:80",
|
||||||
|
expectedURL: "https://foobar.com:443",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "URL doesn't match regex",
|
||||||
|
regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`,
|
||||||
|
replacement: "https://$1{{\"bar\"}}$2:443$4",
|
||||||
|
url: "http://bar.com:80",
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid rewritten URL",
|
||||||
|
regex: `^(.*)$`,
|
||||||
|
replacement: "http://192.168.0.%31/",
|
||||||
|
url: "http://foo.com:80",
|
||||||
|
expectedStatus: http.StatusBadGateway,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid regex",
|
||||||
|
regex: `^(.*`,
|
||||||
|
replacement: "$1",
|
||||||
|
url: "http://foo.com:80",
|
||||||
|
errorExpected: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
handler, err := NewRegexHandler(test.regex, test.replacement, test.permanent)
|
||||||
|
|
||||||
|
if test.errorExpected {
|
||||||
|
require.Nil(t, handler)
|
||||||
|
fmt.Println(err == nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NotNil(t, handler)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
r := testhelpers.MustNewRequest(http.MethodGet, test.url, nil)
|
||||||
|
next := func(rw http.ResponseWriter, req *http.Request) {}
|
||||||
|
handler.ServeHTTP(recorder, r, next)
|
||||||
|
|
||||||
|
if test.expectedStatus == http.StatusMovedPermanently || test.expectedStatus == http.StatusFound {
|
||||||
|
assert.Equal(t, test.expectedStatus, recorder.Code)
|
||||||
|
|
||||||
|
location, err := recorder.Result().Location()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expectedURL, location.String())
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, test.expectedStatus, recorder.Code)
|
||||||
|
|
||||||
|
location, err := recorder.Result().Location()
|
||||||
|
require.Error(t, err, "ghf %v", location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,32 +0,0 @@
|
||||||
package middlewares
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/containous/traefik/log"
|
|
||||||
"github.com/vulcand/vulcand/plugin/rewrite"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Rewrite is a middleware that allows redirections
|
|
||||||
type Rewrite struct {
|
|
||||||
rewriter *rewrite.Rewrite
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRewrite creates a Rewrite middleware
|
|
||||||
func NewRewrite(regex, replacement string, redirect bool) (*Rewrite, error) {
|
|
||||||
rewriter, err := rewrite.NewRewrite(regex, replacement, false, redirect)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Rewrite{rewriter: rewriter}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
func (rewrite *Rewrite) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
|
||||||
handler, err := rewrite.rewriter.NewHandler(next)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error in rewrite middleware ", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
handler.ServeHTTP(rw, r)
|
|
||||||
}
|
|
|
@ -312,9 +312,12 @@ func (p *Provider) getBuffering(tags []string) *types.Buffering {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) getRedirect(tags []string) *types.Redirect {
|
func (p *Provider) getRedirect(tags []string) *types.Redirect {
|
||||||
|
permanent := p.getBoolAttribute(label.SuffixFrontendRedirectPermanent, tags, false)
|
||||||
|
|
||||||
if p.hasAttribute(label.SuffixFrontendRedirectEntryPoint, tags) {
|
if p.hasAttribute(label.SuffixFrontendRedirectEntryPoint, tags) {
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
EntryPoint: p.getAttribute(label.SuffixFrontendRedirectEntryPoint, tags, ""),
|
EntryPoint: p.getAttribute(label.SuffixFrontendRedirectEntryPoint, tags, ""),
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,6 +325,7 @@ func (p *Provider) getRedirect(tags []string) *types.Redirect {
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
Regex: p.getAttribute(label.SuffixFrontendRedirectRegex, tags, ""),
|
Regex: p.getAttribute(label.SuffixFrontendRedirectRegex, tags, ""),
|
||||||
Replacement: p.getAttribute(label.SuffixFrontendRedirectReplacement, tags, ""),
|
Replacement: p.getAttribute(label.SuffixFrontendRedirectReplacement, tags, ""),
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1083,6 +1083,17 @@ func TestProviderGetRedirect(t *testing.T) {
|
||||||
EntryPoint: "https",
|
EntryPoint: "https",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "should return a struct when entry point redirect tags (permanent)",
|
||||||
|
tags: []string{
|
||||||
|
label.TraefikFrontendRedirectEntryPoint + "=https",
|
||||||
|
label.TraefikFrontendRedirectPermanent + "=true",
|
||||||
|
},
|
||||||
|
expected: &types.Redirect{
|
||||||
|
EntryPoint: "https",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "should return a struct when regex redirect tags",
|
desc: "should return a struct when regex redirect tags",
|
||||||
tags: []string{
|
tags: []string{
|
||||||
|
@ -1094,6 +1105,19 @@ func TestProviderGetRedirect(t *testing.T) {
|
||||||
Replacement: "$1",
|
Replacement: "$1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "should return a struct when regex redirect tags (permanent)",
|
||||||
|
tags: []string{
|
||||||
|
label.TraefikFrontendRedirectRegex + "=(.*)",
|
||||||
|
label.TraefikFrontendRedirectReplacement + "=$1",
|
||||||
|
label.TraefikFrontendRedirectPermanent + "=true",
|
||||||
|
},
|
||||||
|
expected: &types.Redirect{
|
||||||
|
Regex: "(.*)",
|
||||||
|
Replacement: "$1",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
|
|
@ -229,9 +229,12 @@ func getBuffering(container dockerData) *types.Buffering {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRedirect(container dockerData) *types.Redirect {
|
func getRedirect(container dockerData) *types.Redirect {
|
||||||
|
permanent := label.GetBoolValue(container.Labels, label.TraefikFrontendRedirectPermanent, false)
|
||||||
|
|
||||||
if label.Has(container.Labels, label.TraefikFrontendRedirectEntryPoint) {
|
if label.Has(container.Labels, label.TraefikFrontendRedirectEntryPoint) {
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
EntryPoint: label.GetStringValue(container.Labels, label.TraefikFrontendRedirectEntryPoint, ""),
|
EntryPoint: label.GetStringValue(container.Labels, label.TraefikFrontendRedirectEntryPoint, ""),
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,6 +243,7 @@ func getRedirect(container dockerData) *types.Redirect {
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
Regex: label.GetStringValue(container.Labels, label.TraefikFrontendRedirectRegex, ""),
|
Regex: label.GetStringValue(container.Labels, label.TraefikFrontendRedirectRegex, ""),
|
||||||
Replacement: label.GetStringValue(container.Labels, label.TraefikFrontendRedirectReplacement, ""),
|
Replacement: label.GetStringValue(container.Labels, label.TraefikFrontendRedirectReplacement, ""),
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -122,6 +122,7 @@ func TestDockerBuildConfiguration(t *testing.T) {
|
||||||
label.TraefikFrontendRedirectEntryPoint: "https",
|
label.TraefikFrontendRedirectEntryPoint: "https",
|
||||||
label.TraefikFrontendRedirectRegex: "nope",
|
label.TraefikFrontendRedirectRegex: "nope",
|
||||||
label.TraefikFrontendRedirectReplacement: "nope",
|
label.TraefikFrontendRedirectReplacement: "nope",
|
||||||
|
label.TraefikFrontendRedirectPermanent: "true",
|
||||||
label.TraefikFrontendRule: "Host:traefik.io",
|
label.TraefikFrontendRule: "Host:traefik.io",
|
||||||
label.TraefikFrontendWhitelistSourceRange: "10.10.10.10",
|
label.TraefikFrontendWhitelistSourceRange: "10.10.10.10",
|
||||||
|
|
||||||
|
@ -259,6 +260,7 @@ func TestDockerBuildConfiguration(t *testing.T) {
|
||||||
EntryPoint: "https",
|
EntryPoint: "https",
|
||||||
Regex: "",
|
Regex: "",
|
||||||
Replacement: "",
|
Replacement: "",
|
||||||
|
Permanent: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1221,6 +1223,20 @@ func TestDockerGetRedirect(t *testing.T) {
|
||||||
EntryPoint: "https",
|
EntryPoint: "https",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "should return a struct when entry point redirect label (permanent)",
|
||||||
|
container: containerJSON(
|
||||||
|
name("test1"),
|
||||||
|
labels(map[string]string{
|
||||||
|
label.TraefikFrontendRedirectEntryPoint: "https",
|
||||||
|
label.TraefikFrontendRedirectPermanent: "true",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
expected: &types.Redirect{
|
||||||
|
EntryPoint: "https",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "should return a struct when regex redirect labels",
|
desc: "should return a struct when regex redirect labels",
|
||||||
container: containerJSON(
|
container: containerJSON(
|
||||||
|
@ -1235,6 +1251,22 @@ func TestDockerGetRedirect(t *testing.T) {
|
||||||
Replacement: "$1",
|
Replacement: "$1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "should return a struct when regex redirect tags (permanent)",
|
||||||
|
container: containerJSON(
|
||||||
|
name("test1"),
|
||||||
|
labels(map[string]string{
|
||||||
|
label.TraefikFrontendRedirectRegex: "(.*)",
|
||||||
|
label.TraefikFrontendRedirectReplacement: "$1",
|
||||||
|
label.TraefikFrontendRedirectPermanent: "true",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
expected: &types.Redirect{
|
||||||
|
Regex: "(.*)",
|
||||||
|
Replacement: "$1",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
|
|
@ -100,9 +100,12 @@ func getServicePort(container dockerData, serviceName string) string {
|
||||||
func getServiceRedirect(container dockerData, serviceName string) *types.Redirect {
|
func getServiceRedirect(container dockerData, serviceName string) *types.Redirect {
|
||||||
serviceLabels := getServiceLabels(container, serviceName)
|
serviceLabels := getServiceLabels(container, serviceName)
|
||||||
|
|
||||||
|
permanent := getServiceBoolValue(container, serviceLabels, label.SuffixFrontendRedirectPermanent, false)
|
||||||
|
|
||||||
if hasStrictServiceLabel(serviceLabels, label.SuffixFrontendRedirectEntryPoint) {
|
if hasStrictServiceLabel(serviceLabels, label.SuffixFrontendRedirectEntryPoint) {
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
EntryPoint: getStrictServiceStringValue(serviceLabels, label.SuffixFrontendRedirectEntryPoint, label.DefaultFrontendRedirectEntryPoint),
|
EntryPoint: getStrictServiceStringValue(serviceLabels, label.SuffixFrontendRedirectEntryPoint, label.DefaultFrontendRedirectEntryPoint),
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,6 +114,7 @@ func getServiceRedirect(container dockerData, serviceName string) *types.Redirec
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
Regex: getStrictServiceStringValue(serviceLabels, label.SuffixFrontendRedirectRegex, ""),
|
Regex: getStrictServiceStringValue(serviceLabels, label.SuffixFrontendRedirectRegex, ""),
|
||||||
Replacement: getStrictServiceStringValue(serviceLabels, label.SuffixFrontendRedirectReplacement, ""),
|
Replacement: getStrictServiceStringValue(serviceLabels, label.SuffixFrontendRedirectReplacement, ""),
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,7 @@ func TestDockerServiceBuildConfiguration(t *testing.T) {
|
||||||
label.Prefix + "service." + label.SuffixFrontendRedirectEntryPoint: "https",
|
label.Prefix + "service." + label.SuffixFrontendRedirectEntryPoint: "https",
|
||||||
label.Prefix + "service." + label.SuffixFrontendRedirectRegex: "nope",
|
label.Prefix + "service." + label.SuffixFrontendRedirectRegex: "nope",
|
||||||
label.Prefix + "service." + label.SuffixFrontendRedirectReplacement: "nope",
|
label.Prefix + "service." + label.SuffixFrontendRedirectReplacement: "nope",
|
||||||
|
label.Prefix + "service." + label.SuffixFrontendRedirectPermanent: "true",
|
||||||
label.Prefix + "service." + label.SuffixFrontendWhitelistSourceRange: "10.10.10.10",
|
label.Prefix + "service." + label.SuffixFrontendWhitelistSourceRange: "10.10.10.10",
|
||||||
|
|
||||||
label.Prefix + "service." + label.SuffixFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
|
label.Prefix + "service." + label.SuffixFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
|
||||||
|
@ -218,6 +219,7 @@ func TestDockerServiceBuildConfiguration(t *testing.T) {
|
||||||
EntryPoint: "https",
|
EntryPoint: "https",
|
||||||
Regex: "",
|
Regex: "",
|
||||||
Replacement: "",
|
Replacement: "",
|
||||||
|
Permanent: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
|
|
|
@ -212,16 +212,21 @@ func getServers(instances []ecsInstance) map[string]types.Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRedirect(instance ecsInstance) *types.Redirect {
|
func getRedirect(instance ecsInstance) *types.Redirect {
|
||||||
|
permanent := getBoolValue(instance, label.TraefikFrontendRedirectPermanent, false)
|
||||||
|
|
||||||
if hasLabel(instance, label.TraefikFrontendRedirectEntryPoint) {
|
if hasLabel(instance, label.TraefikFrontendRedirectEntryPoint) {
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
EntryPoint: getStringValue(instance, label.TraefikFrontendRedirectEntryPoint, ""),
|
EntryPoint: getStringValue(instance, label.TraefikFrontendRedirectEntryPoint, ""),
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasLabel(instance, label.TraefikFrontendRedirectRegex) &&
|
if hasLabel(instance, label.TraefikFrontendRedirectRegex) &&
|
||||||
hasLabel(instance, label.TraefikFrontendRedirectReplacement) {
|
hasLabel(instance, label.TraefikFrontendRedirectReplacement) {
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
Regex: getStringValue(instance, label.TraefikFrontendRedirectRegex, ""),
|
Regex: getStringValue(instance, label.TraefikFrontendRedirectRegex, ""),
|
||||||
Replacement: getStringValue(instance, label.TraefikFrontendRedirectReplacement, ""),
|
Replacement: getStringValue(instance, label.TraefikFrontendRedirectReplacement, ""),
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -150,6 +150,7 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
label.TraefikFrontendRedirectEntryPoint: aws.String("https"),
|
label.TraefikFrontendRedirectEntryPoint: aws.String("https"),
|
||||||
label.TraefikFrontendRedirectRegex: aws.String("nope"),
|
label.TraefikFrontendRedirectRegex: aws.String("nope"),
|
||||||
label.TraefikFrontendRedirectReplacement: aws.String("nope"),
|
label.TraefikFrontendRedirectReplacement: aws.String("nope"),
|
||||||
|
label.TraefikFrontendRedirectPermanent: aws.String("true"),
|
||||||
label.TraefikFrontendRule: aws.String("Host:traefik.io"),
|
label.TraefikFrontendRule: aws.String("Host:traefik.io"),
|
||||||
label.TraefikFrontendWhitelistSourceRange: aws.String("10.10.10.10"),
|
label.TraefikFrontendWhitelistSourceRange: aws.String("10.10.10.10"),
|
||||||
|
|
||||||
|
@ -333,6 +334,7 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
EntryPoint: "https",
|
EntryPoint: "https",
|
||||||
Regex: "",
|
Regex: "",
|
||||||
Replacement: "",
|
Replacement: "",
|
||||||
|
Permanent: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1152,6 +1154,20 @@ func TestGetRedirect(t *testing.T) {
|
||||||
EntryPoint: "https",
|
EntryPoint: "https",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "should return a struct when entry point redirect label (permanent)",
|
||||||
|
instance: ecsInstance{
|
||||||
|
containerDefinition: &ecs.ContainerDefinition{
|
||||||
|
DockerLabels: map[string]*string{
|
||||||
|
label.TraefikFrontendRedirectEntryPoint: aws.String("https"),
|
||||||
|
label.TraefikFrontendRedirectPermanent: aws.String("true"),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expected: &types.Redirect{
|
||||||
|
EntryPoint: "https",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "should return a struct when regex redirect labels",
|
desc: "should return a struct when regex redirect labels",
|
||||||
instance: ecsInstance{
|
instance: ecsInstance{
|
||||||
|
@ -1166,6 +1182,22 @@ func TestGetRedirect(t *testing.T) {
|
||||||
Replacement: "$1",
|
Replacement: "$1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "should return a struct when regex redirect tags (permanent)",
|
||||||
|
instance: ecsInstance{
|
||||||
|
containerDefinition: &ecs.ContainerDefinition{
|
||||||
|
DockerLabels: map[string]*string{
|
||||||
|
label.TraefikFrontendRedirectRegex: aws.String("(.*)"),
|
||||||
|
label.TraefikFrontendRedirectReplacement: aws.String("$1"),
|
||||||
|
label.TraefikFrontendRedirectPermanent: aws.String("true"),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expected: &types.Redirect{
|
||||||
|
Regex: "(.*)",
|
||||||
|
Replacement: "$1",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
|
|
@ -21,6 +21,7 @@ const (
|
||||||
annotationKubernetesSessionCookieName = "ingress.kubernetes.io/session-cookie-name"
|
annotationKubernetesSessionCookieName = "ingress.kubernetes.io/session-cookie-name"
|
||||||
annotationKubernetesRuleType = "ingress.kubernetes.io/rule-type"
|
annotationKubernetesRuleType = "ingress.kubernetes.io/rule-type"
|
||||||
annotationKubernetesRedirectEntryPoint = "ingress.kubernetes.io/redirect-entry-point"
|
annotationKubernetesRedirectEntryPoint = "ingress.kubernetes.io/redirect-entry-point"
|
||||||
|
annotationKubernetesRedirectPermanent = "ingress.kubernetes.io/redirect-permanent"
|
||||||
annotationKubernetesRedirectRegex = "ingress.kubernetes.io/redirect-regex"
|
annotationKubernetesRedirectRegex = "ingress.kubernetes.io/redirect-regex"
|
||||||
annotationKubernetesRedirectReplacement = "ingress.kubernetes.io/redirect-replacement"
|
annotationKubernetesRedirectReplacement = "ingress.kubernetes.io/redirect-replacement"
|
||||||
annotationKubernetesMaxConnAmount = "ingress.kubernetes.io/max-conn-amount"
|
annotationKubernetesMaxConnAmount = "ingress.kubernetes.io/max-conn-amount"
|
||||||
|
|
|
@ -456,10 +456,13 @@ func shouldProcessIngress(ingressClass string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFrontendRedirect(i *v1beta1.Ingress) *types.Redirect {
|
func getFrontendRedirect(i *v1beta1.Ingress) *types.Redirect {
|
||||||
|
permanent := getBoolValue(i.Annotations, annotationKubernetesRedirectPermanent, false)
|
||||||
|
|
||||||
redirectEntryPoint := getStringValue(i.Annotations, annotationKubernetesRedirectEntryPoint, "")
|
redirectEntryPoint := getStringValue(i.Annotations, annotationKubernetesRedirectEntryPoint, "")
|
||||||
if len(redirectEntryPoint) > 0 {
|
if len(redirectEntryPoint) > 0 {
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
EntryPoint: redirectEntryPoint,
|
EntryPoint: redirectEntryPoint,
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -469,6 +472,7 @@ func getFrontendRedirect(i *v1beta1.Ingress) *types.Redirect {
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
Regex: redirectRegex,
|
Regex: redirectRegex,
|
||||||
Replacement: redirectReplacement,
|
Replacement: redirectReplacement,
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ const (
|
||||||
pathFrontendRedirectEntryPoint = "/redirect/entrypoint"
|
pathFrontendRedirectEntryPoint = "/redirect/entrypoint"
|
||||||
pathFrontendRedirectRegex = "/redirect/regex"
|
pathFrontendRedirectRegex = "/redirect/regex"
|
||||||
pathFrontendRedirectReplacement = "/redirect/replacement"
|
pathFrontendRedirectReplacement = "/redirect/replacement"
|
||||||
|
pathFrontendRedirectPermanent = "/redirect/permanent"
|
||||||
pathFrontendErrorPages = "/errors/"
|
pathFrontendErrorPages = "/errors/"
|
||||||
pathFrontendErrorPagesBackend = "/backend"
|
pathFrontendErrorPagesBackend = "/backend"
|
||||||
pathFrontendErrorPagesQuery = "/query"
|
pathFrontendErrorPagesQuery = "/query"
|
||||||
|
|
|
@ -108,9 +108,12 @@ func (p *Provider) getStickinessCookieName(rootPath string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) getRedirect(rootPath string) *types.Redirect {
|
func (p *Provider) getRedirect(rootPath string) *types.Redirect {
|
||||||
|
permanent := p.getBool(false, rootPath, pathFrontendRedirectPermanent)
|
||||||
|
|
||||||
if p.has(rootPath, pathFrontendRedirectEntryPoint) {
|
if p.has(rootPath, pathFrontendRedirectEntryPoint) {
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
EntryPoint: p.get("", rootPath, pathFrontendRedirectEntryPoint),
|
EntryPoint: p.get("", rootPath, pathFrontendRedirectEntryPoint),
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,6 +121,7 @@ func (p *Provider) getRedirect(rootPath string) *types.Redirect {
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
Regex: p.get("", rootPath, pathFrontendRedirectRegex),
|
Regex: p.get("", rootPath, pathFrontendRedirectRegex),
|
||||||
Replacement: p.get("", rootPath, pathFrontendRedirectReplacement),
|
Replacement: p.get("", rootPath, pathFrontendRedirectReplacement),
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,7 @@ func TestProviderBuildConfiguration(t *testing.T) {
|
||||||
withPair(pathFrontendRedirectEntryPoint, "https"),
|
withPair(pathFrontendRedirectEntryPoint, "https"),
|
||||||
withPair(pathFrontendRedirectRegex, "nope"),
|
withPair(pathFrontendRedirectRegex, "nope"),
|
||||||
withPair(pathFrontendRedirectReplacement, "nope"),
|
withPair(pathFrontendRedirectReplacement, "nope"),
|
||||||
|
withPair(pathFrontendRedirectPermanent, "true"),
|
||||||
withErrorPage("foo", "error", "/test1", "500-501, 503-599"),
|
withErrorPage("foo", "error", "/test1", "500-501, 503-599"),
|
||||||
withErrorPage("bar", "error", "/test2", "400-405"),
|
withErrorPage("bar", "error", "/test2", "400-405"),
|
||||||
withRateLimit("client.ip",
|
withRateLimit("client.ip",
|
||||||
|
@ -186,6 +187,7 @@ func TestProviderBuildConfiguration(t *testing.T) {
|
||||||
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
|
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
|
||||||
Redirect: &types.Redirect{
|
Redirect: &types.Redirect{
|
||||||
EntryPoint: "https",
|
EntryPoint: "https",
|
||||||
|
Permanent: true,
|
||||||
},
|
},
|
||||||
Errors: map[string]*types.ErrorPage{
|
Errors: map[string]*types.ErrorPage{
|
||||||
"foo": {
|
"foo": {
|
||||||
|
@ -1044,6 +1046,18 @@ func TestProviderGetRedirect(t *testing.T) {
|
||||||
EntryPoint: "https",
|
EntryPoint: "https",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "should use entry point when entry point key is valued in the store (permanent)",
|
||||||
|
rootPath: "traefik/frontends/foo",
|
||||||
|
kvPairs: filler("traefik",
|
||||||
|
frontend("foo",
|
||||||
|
withPair(pathFrontendRedirectEntryPoint, "https"),
|
||||||
|
withPair(pathFrontendRedirectPermanent, "true"))),
|
||||||
|
expected: &types.Redirect{
|
||||||
|
EntryPoint: "https",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "should use regex when regex keys are valued in the store",
|
desc: "should use regex when regex keys are valued in the store",
|
||||||
rootPath: "traefik/frontends/foo",
|
rootPath: "traefik/frontends/foo",
|
||||||
|
@ -1056,6 +1070,20 @@ func TestProviderGetRedirect(t *testing.T) {
|
||||||
Replacement: "$1",
|
Replacement: "$1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "should use regex when regex keys are valued in the store (permanent)",
|
||||||
|
rootPath: "traefik/frontends/foo",
|
||||||
|
kvPairs: filler("traefik",
|
||||||
|
frontend("foo",
|
||||||
|
withPair(pathFrontendRedirectRegex, "(.*)"),
|
||||||
|
withPair(pathFrontendRedirectReplacement, "$1"),
|
||||||
|
withPair(pathFrontendRedirectPermanent, "true"))),
|
||||||
|
expected: &types.Redirect{
|
||||||
|
Regex: "(.*)",
|
||||||
|
Replacement: "$1",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "should only use entry point when entry point and regex base are valued in the store",
|
desc: "should only use entry point when entry point and regex base are valued in the store",
|
||||||
rootPath: "traefik/frontends/foo",
|
rootPath: "traefik/frontends/foo",
|
||||||
|
|
|
@ -62,6 +62,7 @@ const (
|
||||||
SuffixFrontendRedirectEntryPoint = "frontend.redirect.entryPoint"
|
SuffixFrontendRedirectEntryPoint = "frontend.redirect.entryPoint"
|
||||||
SuffixFrontendRedirectRegex = "frontend.redirect.regex"
|
SuffixFrontendRedirectRegex = "frontend.redirect.regex"
|
||||||
SuffixFrontendRedirectReplacement = "frontend.redirect.replacement"
|
SuffixFrontendRedirectReplacement = "frontend.redirect.replacement"
|
||||||
|
SuffixFrontendRedirectPermanent = "frontend.redirect.permanent"
|
||||||
SuffixFrontendRule = "frontend.rule"
|
SuffixFrontendRule = "frontend.rule"
|
||||||
SuffixFrontendRuleType = "frontend.rule.type"
|
SuffixFrontendRuleType = "frontend.rule.type"
|
||||||
SuffixFrontendWhitelistSourceRange = "frontend.whitelistSourceRange"
|
SuffixFrontendWhitelistSourceRange = "frontend.whitelistSourceRange"
|
||||||
|
@ -102,6 +103,7 @@ const (
|
||||||
TraefikFrontendRedirectEntryPoint = Prefix + SuffixFrontendRedirectEntryPoint
|
TraefikFrontendRedirectEntryPoint = Prefix + SuffixFrontendRedirectEntryPoint
|
||||||
TraefikFrontendRedirectRegex = Prefix + SuffixFrontendRedirectRegex
|
TraefikFrontendRedirectRegex = Prefix + SuffixFrontendRedirectRegex
|
||||||
TraefikFrontendRedirectReplacement = Prefix + SuffixFrontendRedirectReplacement
|
TraefikFrontendRedirectReplacement = Prefix + SuffixFrontendRedirectReplacement
|
||||||
|
TraefikFrontendRedirectPermanent = Prefix + SuffixFrontendRedirectPermanent
|
||||||
TraefikFrontendRule = Prefix + SuffixFrontendRule
|
TraefikFrontendRule = Prefix + SuffixFrontendRule
|
||||||
TraefikFrontendRuleType = Prefix + SuffixFrontendRuleType // k8s only
|
TraefikFrontendRuleType = Prefix + SuffixFrontendRuleType // k8s only
|
||||||
TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange
|
TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange
|
||||||
|
|
|
@ -501,16 +501,21 @@ func (p *Provider) getServers(application marathon.Application, serviceName stri
|
||||||
func getRedirect(application marathon.Application, serviceName string) *types.Redirect {
|
func getRedirect(application marathon.Application, serviceName string) *types.Redirect {
|
||||||
labels := getLabels(application, serviceName)
|
labels := getLabels(application, serviceName)
|
||||||
|
|
||||||
|
permanent := label.GetBoolValue(labels, getLabelName(serviceName, label.SuffixFrontendRedirectPermanent), false)
|
||||||
|
|
||||||
if label.Has(labels, getLabelName(serviceName, label.SuffixFrontendRedirectEntryPoint)) {
|
if label.Has(labels, getLabelName(serviceName, label.SuffixFrontendRedirectEntryPoint)) {
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
EntryPoint: label.GetStringValue(labels, getLabelName(serviceName, label.SuffixFrontendRedirectEntryPoint), ""),
|
EntryPoint: label.GetStringValue(labels, getLabelName(serviceName, label.SuffixFrontendRedirectEntryPoint), ""),
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if label.Has(labels, getLabelName(serviceName, label.SuffixFrontendRedirectRegex)) &&
|
if label.Has(labels, getLabelName(serviceName, label.SuffixFrontendRedirectRegex)) &&
|
||||||
label.Has(labels, getLabelName(serviceName, label.SuffixFrontendRedirectReplacement)) {
|
label.Has(labels, getLabelName(serviceName, label.SuffixFrontendRedirectReplacement)) {
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
Regex: label.GetStringValue(labels, getLabelName(serviceName, label.SuffixFrontendRedirectRegex), ""),
|
Regex: label.GetStringValue(labels, getLabelName(serviceName, label.SuffixFrontendRedirectRegex), ""),
|
||||||
Replacement: label.GetStringValue(labels, getLabelName(serviceName, label.SuffixFrontendRedirectReplacement), ""),
|
Replacement: label.GetStringValue(labels, getLabelName(serviceName, label.SuffixFrontendRedirectReplacement), ""),
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -204,6 +204,7 @@ func TestBuildConfigurationNonAPIErrors(t *testing.T) {
|
||||||
withLabel(label.TraefikFrontendRedirectEntryPoint, "https"),
|
withLabel(label.TraefikFrontendRedirectEntryPoint, "https"),
|
||||||
withLabel(label.TraefikFrontendRedirectRegex, "nope"),
|
withLabel(label.TraefikFrontendRedirectRegex, "nope"),
|
||||||
withLabel(label.TraefikFrontendRedirectReplacement, "nope"),
|
withLabel(label.TraefikFrontendRedirectReplacement, "nope"),
|
||||||
|
withLabel(label.TraefikFrontendRedirectPermanent, "true"),
|
||||||
withLabel(label.TraefikFrontendRule, "Host:traefik.io"),
|
withLabel(label.TraefikFrontendRule, "Host:traefik.io"),
|
||||||
withLabel(label.TraefikFrontendWhitelistSourceRange, "10.10.10.10"),
|
withLabel(label.TraefikFrontendWhitelistSourceRange, "10.10.10.10"),
|
||||||
|
|
||||||
|
@ -342,6 +343,7 @@ func TestBuildConfigurationNonAPIErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
Redirect: &types.Redirect{
|
Redirect: &types.Redirect{
|
||||||
EntryPoint: "https",
|
EntryPoint: "https",
|
||||||
|
Permanent: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -524,6 +526,7 @@ func TestBuildConfigurationServicesNonAPIErrors(t *testing.T) {
|
||||||
withServiceLabel(label.TraefikFrontendRedirectEntryPoint, "https", "containous"),
|
withServiceLabel(label.TraefikFrontendRedirectEntryPoint, "https", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendRedirectRegex, "nope", "containous"),
|
withServiceLabel(label.TraefikFrontendRedirectRegex, "nope", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendRedirectReplacement, "nope", "containous"),
|
withServiceLabel(label.TraefikFrontendRedirectReplacement, "nope", "containous"),
|
||||||
|
withServiceLabel(label.TraefikFrontendRedirectPermanent, "true", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"),
|
withServiceLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendWhitelistSourceRange, "10.10.10.10", "containous"),
|
withServiceLabel(label.TraefikFrontendWhitelistSourceRange, "10.10.10.10", "containous"),
|
||||||
|
|
||||||
|
@ -661,6 +664,7 @@ func TestBuildConfigurationServicesNonAPIErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
Redirect: &types.Redirect{
|
Redirect: &types.Redirect{
|
||||||
EntryPoint: "https",
|
EntryPoint: "https",
|
||||||
|
Permanent: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1698,6 +1702,18 @@ func TestGetRedirect(t *testing.T) {
|
||||||
EntryPoint: "https",
|
EntryPoint: "https",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "should return a struct when entry point redirect label (permanent)",
|
||||||
|
application: application(
|
||||||
|
appPorts(80),
|
||||||
|
withLabel(label.TraefikFrontendRedirectEntryPoint, "https"),
|
||||||
|
withLabel(label.TraefikFrontendRedirectPermanent, "true"),
|
||||||
|
),
|
||||||
|
expected: &types.Redirect{
|
||||||
|
EntryPoint: "https",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "should return a struct when regex redirect labels",
|
desc: "should return a struct when regex redirect labels",
|
||||||
application: application(
|
application: application(
|
||||||
|
@ -1748,6 +1764,21 @@ func TestGetRedirect(t *testing.T) {
|
||||||
Replacement: "$1",
|
Replacement: "$1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "should return a struct when regex redirect labels on service (permanent)",
|
||||||
|
application: application(
|
||||||
|
appPorts(80),
|
||||||
|
withLabel(label.Prefix+"containous."+label.SuffixFrontendRedirectRegex, "(.*)"),
|
||||||
|
withLabel(label.Prefix+"containous."+label.SuffixFrontendRedirectReplacement, "$1"),
|
||||||
|
withLabel(label.Prefix+"containous."+label.SuffixFrontendRedirectPermanent, "true"),
|
||||||
|
),
|
||||||
|
serviceName: "containous",
|
||||||
|
expected: &types.Redirect{
|
||||||
|
Regex: "(.*)",
|
||||||
|
Replacement: "$1",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
|
|
@ -338,9 +338,12 @@ func (p *Provider) getServers(tasks []state.Task) map[string]types.Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRedirect(task state.Task) *types.Redirect {
|
func getRedirect(task state.Task) *types.Redirect {
|
||||||
|
permanent := getBoolValue(task, label.TraefikFrontendRedirectPermanent, false)
|
||||||
|
|
||||||
if hasLabel(task, label.TraefikFrontendRedirectEntryPoint) {
|
if hasLabel(task, label.TraefikFrontendRedirectEntryPoint) {
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
EntryPoint: getStringValue(task, label.TraefikFrontendRedirectEntryPoint, ""),
|
EntryPoint: getStringValue(task, label.TraefikFrontendRedirectEntryPoint, ""),
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,6 +352,7 @@ func getRedirect(task state.Task) *types.Redirect {
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
Regex: getStringValue(task, label.TraefikFrontendRedirectRegex, ""),
|
Regex: getStringValue(task, label.TraefikFrontendRedirectRegex, ""),
|
||||||
Replacement: getStringValue(task, label.TraefikFrontendRedirectReplacement, ""),
|
Replacement: getStringValue(task, label.TraefikFrontendRedirectReplacement, ""),
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -146,6 +146,7 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
withLabel(label.TraefikFrontendRedirectEntryPoint, "https"),
|
withLabel(label.TraefikFrontendRedirectEntryPoint, "https"),
|
||||||
withLabel(label.TraefikFrontendRedirectRegex, "nope"),
|
withLabel(label.TraefikFrontendRedirectRegex, "nope"),
|
||||||
withLabel(label.TraefikFrontendRedirectReplacement, "nope"),
|
withLabel(label.TraefikFrontendRedirectReplacement, "nope"),
|
||||||
|
withLabel(label.TraefikFrontendRedirectPermanent, "true"),
|
||||||
withLabel(label.TraefikFrontendRule, "Host:traefik.io"),
|
withLabel(label.TraefikFrontendRule, "Host:traefik.io"),
|
||||||
withLabel(label.TraefikFrontendWhitelistSourceRange, "10.10.10.10"),
|
withLabel(label.TraefikFrontendWhitelistSourceRange, "10.10.10.10"),
|
||||||
|
|
||||||
|
@ -283,6 +284,7 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
EntryPoint: "https",
|
EntryPoint: "https",
|
||||||
Regex: "",
|
Regex: "",
|
||||||
Replacement: "",
|
Replacement: "",
|
||||||
|
Permanent: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -991,6 +993,20 @@ func TestGetRedirect(t *testing.T) {
|
||||||
EntryPoint: "https",
|
EntryPoint: "https",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "should return a struct when entry point redirect label (permanent)",
|
||||||
|
task: aTask("ID1",
|
||||||
|
withLabel(label.TraefikFrontendRedirectEntryPoint, "https"),
|
||||||
|
withLabel(label.TraefikFrontendRedirectPermanent, "true"),
|
||||||
|
withIP("10.10.10.10"),
|
||||||
|
withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
|
||||||
|
withDefaultStatus(),
|
||||||
|
),
|
||||||
|
expected: &types.Redirect{
|
||||||
|
EntryPoint: "https",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "should return a struct when regex redirect labels",
|
desc: "should return a struct when regex redirect labels",
|
||||||
task: aTask("ID1",
|
task: aTask("ID1",
|
||||||
|
@ -1005,6 +1021,22 @@ func TestGetRedirect(t *testing.T) {
|
||||||
Replacement: "$1",
|
Replacement: "$1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "should return a struct when regex redirect labels (permanent)",
|
||||||
|
task: aTask("ID1",
|
||||||
|
withLabel(label.TraefikFrontendRedirectRegex, "(.*)"),
|
||||||
|
withLabel(label.TraefikFrontendRedirectReplacement, "$1"),
|
||||||
|
withLabel(label.TraefikFrontendRedirectPermanent, "true"),
|
||||||
|
withIP("10.10.10.10"),
|
||||||
|
withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
|
||||||
|
withDefaultStatus(),
|
||||||
|
),
|
||||||
|
expected: &types.Redirect{
|
||||||
|
Regex: "(.*)",
|
||||||
|
Replacement: "$1",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
|
|
@ -273,9 +273,12 @@ func getServers(service rancherData) map[string]types.Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRedirect(service rancherData) *types.Redirect {
|
func getRedirect(service rancherData) *types.Redirect {
|
||||||
|
permanent := label.GetBoolValue(service.Labels, label.TraefikFrontendRedirectPermanent, false)
|
||||||
|
|
||||||
if label.Has(service.Labels, label.TraefikFrontendRedirectEntryPoint) {
|
if label.Has(service.Labels, label.TraefikFrontendRedirectEntryPoint) {
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
EntryPoint: label.GetStringValue(service.Labels, label.TraefikFrontendRedirectEntryPoint, ""),
|
EntryPoint: label.GetStringValue(service.Labels, label.TraefikFrontendRedirectEntryPoint, ""),
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,6 +287,7 @@ func getRedirect(service rancherData) *types.Redirect {
|
||||||
return &types.Redirect{
|
return &types.Redirect{
|
||||||
Regex: label.GetStringValue(service.Labels, label.TraefikFrontendRedirectRegex, ""),
|
Regex: label.GetStringValue(service.Labels, label.TraefikFrontendRedirectRegex, ""),
|
||||||
Replacement: label.GetStringValue(service.Labels, label.TraefikFrontendRedirectReplacement, ""),
|
Replacement: label.GetStringValue(service.Labels, label.TraefikFrontendRedirectReplacement, ""),
|
||||||
|
Permanent: permanent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ func TestProviderBuildConfiguration(t *testing.T) {
|
||||||
label.TraefikFrontendRedirectEntryPoint: "https",
|
label.TraefikFrontendRedirectEntryPoint: "https",
|
||||||
label.TraefikFrontendRedirectRegex: "nope",
|
label.TraefikFrontendRedirectRegex: "nope",
|
||||||
label.TraefikFrontendRedirectReplacement: "nope",
|
label.TraefikFrontendRedirectReplacement: "nope",
|
||||||
|
label.TraefikFrontendRedirectPermanent: "true",
|
||||||
label.TraefikFrontendRule: "Host:traefik.io",
|
label.TraefikFrontendRule: "Host:traefik.io",
|
||||||
label.TraefikFrontendWhitelistSourceRange: "10.10.10.10",
|
label.TraefikFrontendWhitelistSourceRange: "10.10.10.10",
|
||||||
|
|
||||||
|
@ -199,6 +200,7 @@ func TestProviderBuildConfiguration(t *testing.T) {
|
||||||
EntryPoint: "https",
|
EntryPoint: "https",
|
||||||
Regex: "",
|
Regex: "",
|
||||||
Replacement: "",
|
Replacement: "",
|
||||||
|
Permanent: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1041,6 +1043,21 @@ func TestGetRedirect(t *testing.T) {
|
||||||
EntryPoint: "https",
|
EntryPoint: "https",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "should return a struct when entry point redirect label (permanent)",
|
||||||
|
service: rancherData{
|
||||||
|
Labels: map[string]string{
|
||||||
|
label.TraefikFrontendRedirectEntryPoint: "https",
|
||||||
|
label.TraefikFrontendRedirectPermanent: "true",
|
||||||
|
},
|
||||||
|
Health: "healthy",
|
||||||
|
State: "active",
|
||||||
|
},
|
||||||
|
expected: &types.Redirect{
|
||||||
|
EntryPoint: "https",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "should return a struct when regex redirect labels",
|
desc: "should return a struct when regex redirect labels",
|
||||||
service: rancherData{
|
service: rancherData{
|
||||||
|
@ -1056,6 +1073,23 @@ func TestGetRedirect(t *testing.T) {
|
||||||
Replacement: "$1",
|
Replacement: "$1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "should return a struct when regex redirect labels (permanent)",
|
||||||
|
service: rancherData{
|
||||||
|
Labels: map[string]string{
|
||||||
|
label.TraefikFrontendRedirectRegex: "(.*)",
|
||||||
|
label.TraefikFrontendRedirectReplacement: "$1",
|
||||||
|
label.TraefikFrontendRedirectPermanent: "true",
|
||||||
|
},
|
||||||
|
Health: "healthy",
|
||||||
|
State: "active",
|
||||||
|
},
|
||||||
|
expected: &types.Redirect{
|
||||||
|
Regex: "(.*)",
|
||||||
|
Replacement: "$1",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"github.com/containous/traefik/middlewares"
|
"github.com/containous/traefik/middlewares"
|
||||||
"github.com/containous/traefik/middlewares/accesslog"
|
"github.com/containous/traefik/middlewares/accesslog"
|
||||||
mauth "github.com/containous/traefik/middlewares/auth"
|
mauth "github.com/containous/traefik/middlewares/auth"
|
||||||
|
"github.com/containous/traefik/middlewares/redirect"
|
||||||
"github.com/containous/traefik/middlewares/tracing"
|
"github.com/containous/traefik/middlewares/tracing"
|
||||||
"github.com/containous/traefik/provider"
|
"github.com/containous/traefik/provider"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
|
@ -51,10 +52,6 @@ import (
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
defaultRedirectRegex = `^(?:https?:\/\/)?([\w\._-]+)(?::\d+)?(.*)$`
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
httpServerLogger = stdlog.New(log.WriterLevel(logrus.DebugLevel), "", 0)
|
httpServerLogger = stdlog.New(log.WriterLevel(logrus.DebugLevel), "", 0)
|
||||||
)
|
)
|
||||||
|
@ -1117,10 +1114,11 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
|
||||||
rewrite, err := s.buildRedirectHandler(entryPointName, frontend.Redirect)
|
rewrite, err := s.buildRedirectHandler(entryPointName, frontend.Redirect)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error creating Frontend Redirect: %v", err)
|
log.Errorf("Error creating Frontend Redirect: %v", err)
|
||||||
}
|
} else {
|
||||||
n.Use(s.wrapNegroniHandlerWithAccessLog(rewrite, fmt.Sprintf("frontend redirect for %s", frontendName)))
|
n.Use(s.wrapNegroniHandlerWithAccessLog(rewrite, fmt.Sprintf("frontend redirect for %s", frontendName)))
|
||||||
log.Debugf("Frontend %s redirect created", frontendName)
|
log.Debugf("Frontend %s redirect created", frontendName)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(frontend.BasicAuth) > 0 {
|
if len(frontend.BasicAuth) > 0 {
|
||||||
users := types.Users{}
|
users := types.Users{}
|
||||||
|
@ -1282,57 +1280,25 @@ func (s *Server) wireFrontendBackend(serverRoute *serverRoute, handler http.Hand
|
||||||
serverRoute.route.Handler(handler)
|
serverRoute.route.Handler(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) buildRedirectHandler(srcEntryPointName string, redirect *types.Redirect) (*middlewares.Rewrite, error) {
|
func (s *Server) buildRedirectHandler(srcEntryPointName string, opt *types.Redirect) (negroni.Handler, error) {
|
||||||
// entry point redirect
|
// entry point redirect
|
||||||
if len(redirect.EntryPoint) > 0 {
|
if len(opt.EntryPoint) > 0 {
|
||||||
return s.buildEntryPointRedirect(srcEntryPointName, redirect.EntryPoint)
|
entryPoint := s.globalConfiguration.EntryPoints[opt.EntryPoint]
|
||||||
|
if entryPoint == nil {
|
||||||
|
return nil, fmt.Errorf("unknown target entrypoint %q", srcEntryPointName)
|
||||||
|
}
|
||||||
|
log.Debugf("Creating entry point redirect %s -> %s", srcEntryPointName, opt.EntryPoint)
|
||||||
|
return redirect.NewEntryPointHandler(entryPoint, opt.Permanent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// regex redirect
|
// regex redirect
|
||||||
rewrite, err := middlewares.NewRewrite(redirect.Regex, redirect.Replacement, true)
|
redirection, err := redirect.NewRegexHandler(opt.Regex, opt.Replacement, opt.Permanent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log.Debugf("Creating entryPoint redirect %s -> %s -> %s", srcEntryPointName, redirect.Regex, redirect.Replacement)
|
log.Debugf("Creating regex redirect %s -> %s -> %s", srcEntryPointName, opt.Regex, opt.Replacement)
|
||||||
|
|
||||||
return rewrite, nil
|
return redirection, nil
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) buildEntryPointRedirect(srcEntryPointName string, redirectEntryPoint string) (*middlewares.Rewrite, error) {
|
|
||||||
regex, replacement, err := s.buildRedirect(redirectEntryPoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rewrite, err := middlewares.NewRewrite(regex, replacement, true)
|
|
||||||
if err != nil {
|
|
||||||
// Impossible case because error is always nil
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debugf("Creating entryPoint redirect %s -> %s : %s -> %s", srcEntryPointName, redirectEntryPoint, regex, replacement)
|
|
||||||
|
|
||||||
return rewrite, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) buildRedirect(entryPointName string) (string, string, error) {
|
|
||||||
entryPoint := s.globalConfiguration.EntryPoints[entryPointName]
|
|
||||||
if entryPoint == nil {
|
|
||||||
return "", "", fmt.Errorf("unknown target entrypoint %q", entryPointName)
|
|
||||||
}
|
|
||||||
|
|
||||||
exp := regexp.MustCompile(`(:\d+)`)
|
|
||||||
match := exp.FindStringSubmatch(entryPoint.Address)
|
|
||||||
if len(match) == 0 {
|
|
||||||
return "", "", fmt.Errorf("bad Address format %q", entryPoint.Address)
|
|
||||||
}
|
|
||||||
|
|
||||||
var protocol = "http"
|
|
||||||
if s.globalConfiguration.EntryPoints[entryPointName].TLS != nil {
|
|
||||||
protocol = "https"
|
|
||||||
}
|
|
||||||
|
|
||||||
replacement := protocol + "://$1" + match[0] + "$2"
|
|
||||||
return defaultRedirectRegex, replacement, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) buildDefaultHTTPRouter() *mux.Router {
|
func (s *Server) buildDefaultHTTPRouter() *mux.Router {
|
||||||
|
@ -1440,7 +1406,7 @@ func registerMetricClients(metricsConfig *types.Metrics) metrics.Registry {
|
||||||
return metrics.NewVoidRegistry()
|
return metrics.NewVoidRegistry()
|
||||||
}
|
}
|
||||||
|
|
||||||
registries := []metrics.Registry{}
|
var registries []metrics.Registry
|
||||||
if metricsConfig.Prometheus != nil {
|
if metricsConfig.Prometheus != nil {
|
||||||
registries = append(registries, metrics.RegisterPrometheus(metricsConfig.Prometheus))
|
registries = append(registries, metrics.RegisterPrometheus(metricsConfig.Prometheus))
|
||||||
log.Debug("Configured Prometheus metrics")
|
log.Debug("Configured Prometheus metrics")
|
||||||
|
|
|
@ -923,7 +923,7 @@ func TestServerResponseEmptyBackend(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildEntryPointRedirect(t *testing.T) {
|
func TestBuildRedirectHandler(t *testing.T) {
|
||||||
srv := Server{
|
srv := Server{
|
||||||
globalConfiguration: configuration.GlobalConfiguration{
|
globalConfiguration: configuration.GlobalConfiguration{
|
||||||
EntryPoints: configuration.EntryPoints{
|
EntryPoints: configuration.EntryPoints{
|
||||||
|
@ -1016,133 +1016,6 @@ func TestBuildEntryPointRedirect(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServerBuildEntryPointRedirect(t *testing.T) {
|
|
||||||
srv := Server{
|
|
||||||
globalConfiguration: configuration.GlobalConfiguration{
|
|
||||||
EntryPoints: configuration.EntryPoints{
|
|
||||||
"http": &configuration.EntryPoint{Address: ":80"},
|
|
||||||
"https": &configuration.EntryPoint{Address: ":443", TLS: &tls.TLS{}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
srcEntryPointName string
|
|
||||||
redirectEntryPoint string
|
|
||||||
url string
|
|
||||||
expectedURL string
|
|
||||||
errorExpected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "existing redirect entry point",
|
|
||||||
srcEntryPointName: "http",
|
|
||||||
redirectEntryPoint: "https",
|
|
||||||
url: "http://foo:80",
|
|
||||||
expectedURL: "https://foo:443",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "non-existing redirect entry point",
|
|
||||||
srcEntryPointName: "http",
|
|
||||||
redirectEntryPoint: "foo",
|
|
||||||
url: "http://foo:80",
|
|
||||||
errorExpected: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range testCases {
|
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
rewrite, err := srv.buildEntryPointRedirect(test.srcEntryPointName, test.redirectEntryPoint)
|
|
||||||
if test.errorExpected {
|
|
||||||
require.Error(t, err)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
r := testhelpers.MustNewRequest(http.MethodGet, test.url, nil)
|
|
||||||
rewrite.ServeHTTP(recorder, r, nil)
|
|
||||||
|
|
||||||
location, err := recorder.Result().Location()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, test.expectedURL, location.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServerBuildRedirect(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
globalConfiguration configuration.GlobalConfiguration
|
|
||||||
redirectEntryPointName string
|
|
||||||
expectedReplacement string
|
|
||||||
errorExpected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "Redirect endpoint http to https with HTTPS protocol",
|
|
||||||
redirectEntryPointName: "https",
|
|
||||||
globalConfiguration: configuration.GlobalConfiguration{
|
|
||||||
EntryPoints: configuration.EntryPoints{
|
|
||||||
"http": &configuration.EntryPoint{Address: ":80"},
|
|
||||||
"https": &configuration.EntryPoint{Address: ":443", TLS: &tls.TLS{}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedReplacement: "https://$1:443$2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Redirect endpoint http to http02 with HTTP protocol",
|
|
||||||
redirectEntryPointName: "http02",
|
|
||||||
globalConfiguration: configuration.GlobalConfiguration{
|
|
||||||
EntryPoints: configuration.EntryPoints{
|
|
||||||
"http": &configuration.EntryPoint{Address: ":80"},
|
|
||||||
"http02": &configuration.EntryPoint{Address: ":88"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedReplacement: "http://$1:88$2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Redirect endpoint to non-existent entry point",
|
|
||||||
redirectEntryPointName: "foobar",
|
|
||||||
globalConfiguration: configuration.GlobalConfiguration{
|
|
||||||
EntryPoints: configuration.EntryPoints{
|
|
||||||
"http": &configuration.EntryPoint{Address: ":80"},
|
|
||||||
"http02": &configuration.EntryPoint{Address: ":88"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
errorExpected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Redirect endpoint to an entry point with a malformed address",
|
|
||||||
redirectEntryPointName: "http02",
|
|
||||||
globalConfiguration: configuration.GlobalConfiguration{
|
|
||||||
EntryPoints: configuration.EntryPoints{
|
|
||||||
"http": &configuration.EntryPoint{Address: ":80"},
|
|
||||||
"http02": &configuration.EntryPoint{Address: "88"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
errorExpected: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range testCases {
|
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
srv := Server{globalConfiguration: test.globalConfiguration}
|
|
||||||
|
|
||||||
_, replacement, err := srv.buildRedirect(test.redirectEntryPointName)
|
|
||||||
|
|
||||||
require.Equal(t, test.errorExpected, err != nil, "Expected an error but don't have error, or Expected no error but have an error: %v", err)
|
|
||||||
assert.Equal(t, test.expectedReplacement, replacement, "build redirect does not return the right replacement pattern")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildDynamicConfig(dynamicConfigBuilders ...func(*types.Configuration)) *types.Configuration {
|
func buildDynamicConfig(dynamicConfigBuilders ...func(*types.Configuration)) *types.Configuration {
|
||||||
config := &types.Configuration{
|
config := &types.Configuration{
|
||||||
Frontends: make(map[string]*types.Frontend),
|
Frontends: make(map[string]*types.Frontend),
|
||||||
|
|
|
@ -83,6 +83,7 @@
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
regex = "{{ $redirect.Regex }}"
|
regex = "{{ $redirect.Regex }}"
|
||||||
replacement = "{{ $redirect.Replacement }}"
|
replacement = "{{ $redirect.Replacement }}"
|
||||||
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if hasErrorPages $service.Attributes }}
|
{{if hasErrorPages $service.Attributes }}
|
||||||
|
|
|
@ -99,6 +99,7 @@
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
regex = "{{ $redirect.Regex }}"
|
regex = "{{ $redirect.Regex }}"
|
||||||
replacement = "{{ $redirect.Replacement }}"
|
replacement = "{{ $redirect.Replacement }}"
|
||||||
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getServiceErrorPages $container $serviceName }}
|
{{ $errorPages := getServiceErrorPages $container $serviceName }}
|
||||||
|
@ -214,6 +215,7 @@
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
regex = "{{ $redirect.Regex }}"
|
regex = "{{ $redirect.Regex }}"
|
||||||
replacement = "{{ $redirect.Replacement }}"
|
replacement = "{{ $redirect.Replacement }}"
|
||||||
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getErrorPages $container }}
|
{{ $errorPages := getErrorPages $container }}
|
||||||
|
|
|
@ -84,6 +84,7 @@
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
regex = "{{ $redirect.Regex }}"
|
regex = "{{ $redirect.Regex }}"
|
||||||
replacement = "{{ $redirect.Replacement }}"
|
replacement = "{{ $redirect.Replacement }}"
|
||||||
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getErrorPages $instance }}
|
{{ $errorPages := getErrorPages $instance }}
|
||||||
|
|
|
@ -83,6 +83,7 @@
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
regex = "{{ $redirect.Regex }}"
|
regex = "{{ $redirect.Regex }}"
|
||||||
replacement = "{{ $redirect.Replacement }}"
|
replacement = "{{ $redirect.Replacement }}"
|
||||||
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getErrorPages $frontend }}
|
{{ $errorPages := getErrorPages $frontend }}
|
||||||
|
|
|
@ -90,6 +90,7 @@
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
regex = "{{ $redirect.Regex }}"
|
regex = "{{ $redirect.Regex }}"
|
||||||
replacement = "{{ $redirect.Replacement }}"
|
replacement = "{{ $redirect.Replacement }}"
|
||||||
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getErrorPages $app $serviceName }}
|
{{ $errorPages := getErrorPages $app $serviceName }}
|
||||||
|
|
|
@ -86,6 +86,7 @@
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
regex = "{{ $redirect.Regex }}"
|
regex = "{{ $redirect.Regex }}"
|
||||||
replacement = "{{ $redirect.Replacement }}"
|
replacement = "{{ $redirect.Replacement }}"
|
||||||
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getErrorPages $app }}
|
{{ $errorPages := getErrorPages $app }}
|
||||||
|
|
|
@ -84,6 +84,7 @@
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
regex = "{{ $redirect.Regex }}"
|
regex = "{{ $redirect.Regex }}"
|
||||||
replacement = "{{ $redirect.Replacement }}"
|
replacement = "{{ $redirect.Replacement }}"
|
||||||
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getErrorPages $service }}
|
{{ $errorPages := getErrorPages $service }}
|
||||||
|
|
|
@ -171,6 +171,7 @@ type Redirect struct {
|
||||||
EntryPoint string `json:"entryPoint,omitempty"`
|
EntryPoint string `json:"entryPoint,omitempty"`
|
||||||
Regex string `json:"regex,omitempty"`
|
Regex string `json:"regex,omitempty"`
|
||||||
Replacement string `json:"replacement,omitempty"`
|
Replacement string `json:"replacement,omitempty"`
|
||||||
|
Permanent bool `json:"permanent,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadBalancerMethod holds the method of load balancing to use.
|
// LoadBalancerMethod holds the method of load balancing to use.
|
||||||
|
|
21
vendor/github.com/codegangsta/cli/LICENSE
generated
vendored
21
vendor/github.com/codegangsta/cli/LICENSE
generated
vendored
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2016 Jeremy Saenz & Contributors
|
|
||||||
|
|
||||||
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.
|
|
497
vendor/github.com/codegangsta/cli/app.go
generated
vendored
497
vendor/github.com/codegangsta/cli/app.go
generated
vendored
|
@ -1,497 +0,0 @@
|
||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md"
|
|
||||||
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
|
|
||||||
runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL)
|
|
||||||
|
|
||||||
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
|
|
||||||
|
|
||||||
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
|
|
||||||
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
|
|
||||||
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
|
|
||||||
)
|
|
||||||
|
|
||||||
// App is the main structure of a cli application. It is recommended that
|
|
||||||
// an app be created with the cli.NewApp() function
|
|
||||||
type App struct {
|
|
||||||
// The name of the program. Defaults to path.Base(os.Args[0])
|
|
||||||
Name string
|
|
||||||
// Full name of command for help, defaults to Name
|
|
||||||
HelpName string
|
|
||||||
// Description of the program.
|
|
||||||
Usage string
|
|
||||||
// Text to override the USAGE section of help
|
|
||||||
UsageText string
|
|
||||||
// Description of the program argument format.
|
|
||||||
ArgsUsage string
|
|
||||||
// Version of the program
|
|
||||||
Version string
|
|
||||||
// Description of the program
|
|
||||||
Description string
|
|
||||||
// List of commands to execute
|
|
||||||
Commands []Command
|
|
||||||
// List of flags to parse
|
|
||||||
Flags []Flag
|
|
||||||
// Boolean to enable bash completion commands
|
|
||||||
EnableBashCompletion bool
|
|
||||||
// Boolean to hide built-in help command
|
|
||||||
HideHelp bool
|
|
||||||
// Boolean to hide built-in version flag and the VERSION section of help
|
|
||||||
HideVersion bool
|
|
||||||
// Populate on app startup, only gettable through method Categories()
|
|
||||||
categories CommandCategories
|
|
||||||
// An action to execute when the bash-completion flag is set
|
|
||||||
BashComplete BashCompleteFunc
|
|
||||||
// An action to execute before any subcommands are run, but after the context is ready
|
|
||||||
// If a non-nil error is returned, no subcommands are run
|
|
||||||
Before BeforeFunc
|
|
||||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
|
||||||
// It is run even if Action() panics
|
|
||||||
After AfterFunc
|
|
||||||
|
|
||||||
// The action to execute when no subcommands are specified
|
|
||||||
// Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}`
|
|
||||||
// *Note*: support for the deprecated `Action` signature will be removed in a future version
|
|
||||||
Action interface{}
|
|
||||||
|
|
||||||
// Execute this function if the proper command cannot be found
|
|
||||||
CommandNotFound CommandNotFoundFunc
|
|
||||||
// Execute this function if an usage error occurs
|
|
||||||
OnUsageError OnUsageErrorFunc
|
|
||||||
// Compilation date
|
|
||||||
Compiled time.Time
|
|
||||||
// List of all authors who contributed
|
|
||||||
Authors []Author
|
|
||||||
// Copyright of the binary if any
|
|
||||||
Copyright string
|
|
||||||
// Name of Author (Note: Use App.Authors, this is deprecated)
|
|
||||||
Author string
|
|
||||||
// Email of Author (Note: Use App.Authors, this is deprecated)
|
|
||||||
Email string
|
|
||||||
// Writer writer to write output to
|
|
||||||
Writer io.Writer
|
|
||||||
// ErrWriter writes error output
|
|
||||||
ErrWriter io.Writer
|
|
||||||
// Other custom info
|
|
||||||
Metadata map[string]interface{}
|
|
||||||
// Carries a function which returns app specific info.
|
|
||||||
ExtraInfo func() map[string]string
|
|
||||||
// CustomAppHelpTemplate the text template for app help topic.
|
|
||||||
// cli.go uses text/template to render templates. You can
|
|
||||||
// render custom help text by setting this variable.
|
|
||||||
CustomAppHelpTemplate string
|
|
||||||
|
|
||||||
didSetup bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tries to find out when this binary was compiled.
|
|
||||||
// Returns the current time if it fails to find it.
|
|
||||||
func compileTime() time.Time {
|
|
||||||
info, err := os.Stat(os.Args[0])
|
|
||||||
if err != nil {
|
|
||||||
return time.Now()
|
|
||||||
}
|
|
||||||
return info.ModTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewApp creates a new cli Application with some reasonable defaults for Name,
|
|
||||||
// Usage, Version and Action.
|
|
||||||
func NewApp() *App {
|
|
||||||
return &App{
|
|
||||||
Name: filepath.Base(os.Args[0]),
|
|
||||||
HelpName: filepath.Base(os.Args[0]),
|
|
||||||
Usage: "A new cli application",
|
|
||||||
UsageText: "",
|
|
||||||
Version: "0.0.0",
|
|
||||||
BashComplete: DefaultAppComplete,
|
|
||||||
Action: helpCommand.Action,
|
|
||||||
Compiled: compileTime(),
|
|
||||||
Writer: os.Stdout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup runs initialization code to ensure all data structures are ready for
|
|
||||||
// `Run` or inspection prior to `Run`. It is internally called by `Run`, but
|
|
||||||
// will return early if setup has already happened.
|
|
||||||
func (a *App) Setup() {
|
|
||||||
if a.didSetup {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
a.didSetup = true
|
|
||||||
|
|
||||||
if a.Author != "" || a.Email != "" {
|
|
||||||
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
|
|
||||||
}
|
|
||||||
|
|
||||||
newCmds := []Command{}
|
|
||||||
for _, c := range a.Commands {
|
|
||||||
if c.HelpName == "" {
|
|
||||||
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
|
|
||||||
}
|
|
||||||
newCmds = append(newCmds, c)
|
|
||||||
}
|
|
||||||
a.Commands = newCmds
|
|
||||||
|
|
||||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
|
||||||
a.Commands = append(a.Commands, helpCommand)
|
|
||||||
if (HelpFlag != BoolFlag{}) {
|
|
||||||
a.appendFlag(HelpFlag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !a.HideVersion {
|
|
||||||
a.appendFlag(VersionFlag)
|
|
||||||
}
|
|
||||||
|
|
||||||
a.categories = CommandCategories{}
|
|
||||||
for _, command := range a.Commands {
|
|
||||||
a.categories = a.categories.AddCommand(command.Category, command)
|
|
||||||
}
|
|
||||||
sort.Sort(a.categories)
|
|
||||||
|
|
||||||
if a.Metadata == nil {
|
|
||||||
a.Metadata = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Writer == nil {
|
|
||||||
a.Writer = os.Stdout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run is the entry point to the cli app. Parses the arguments slice and routes
|
|
||||||
// to the proper flag/args combination
|
|
||||||
func (a *App) Run(arguments []string) (err error) {
|
|
||||||
a.Setup()
|
|
||||||
|
|
||||||
// handle the completion flag separately from the flagset since
|
|
||||||
// completion could be attempted after a flag, but before its value was put
|
|
||||||
// on the command line. this causes the flagset to interpret the completion
|
|
||||||
// flag name as the value of the flag before it which is undesirable
|
|
||||||
// note that we can only do this because the shell autocomplete function
|
|
||||||
// always appends the completion flag at the end of the command
|
|
||||||
shellComplete, arguments := checkShellCompleteFlag(a, arguments)
|
|
||||||
|
|
||||||
// parse flags
|
|
||||||
set, err := flagSet(a.Name, a.Flags)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
set.SetOutput(ioutil.Discard)
|
|
||||||
err = set.Parse(arguments[1:])
|
|
||||||
nerr := normalizeFlags(a.Flags, set)
|
|
||||||
context := NewContext(a, set, nil)
|
|
||||||
if nerr != nil {
|
|
||||||
fmt.Fprintln(a.Writer, nerr)
|
|
||||||
ShowAppHelp(context)
|
|
||||||
return nerr
|
|
||||||
}
|
|
||||||
context.shellComplete = shellComplete
|
|
||||||
|
|
||||||
if checkCompletions(context) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if a.OnUsageError != nil {
|
|
||||||
err := a.OnUsageError(context, err, false)
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
|
||||||
ShowAppHelp(context)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !a.HideHelp && checkHelp(context) {
|
|
||||||
ShowAppHelp(context)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !a.HideVersion && checkVersion(context) {
|
|
||||||
ShowVersion(context)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.After != nil {
|
|
||||||
defer func() {
|
|
||||||
if afterErr := a.After(context); afterErr != nil {
|
|
||||||
if err != nil {
|
|
||||||
err = NewMultiError(err, afterErr)
|
|
||||||
} else {
|
|
||||||
err = afterErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Before != nil {
|
|
||||||
beforeErr := a.Before(context)
|
|
||||||
if beforeErr != nil {
|
|
||||||
ShowAppHelp(context)
|
|
||||||
HandleExitCoder(beforeErr)
|
|
||||||
err = beforeErr
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
args := context.Args()
|
|
||||||
if args.Present() {
|
|
||||||
name := args.First()
|
|
||||||
c := a.Command(name)
|
|
||||||
if c != nil {
|
|
||||||
return c.Run(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Action == nil {
|
|
||||||
a.Action = helpCommand.Action
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run default Action
|
|
||||||
err = HandleAction(a.Action, context)
|
|
||||||
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunAndExitOnError calls .Run() and exits non-zero if an error was returned
|
|
||||||
//
|
|
||||||
// Deprecated: instead you should return an error that fulfills cli.ExitCoder
|
|
||||||
// to cli.App.Run. This will cause the application to exit with the given eror
|
|
||||||
// code in the cli.ExitCoder
|
|
||||||
func (a *App) RunAndExitOnError() {
|
|
||||||
if err := a.Run(os.Args); err != nil {
|
|
||||||
fmt.Fprintln(a.errWriter(), err)
|
|
||||||
OsExiter(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
|
|
||||||
// generate command-specific flags
|
|
||||||
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
|
||||||
// append help to commands
|
|
||||||
if len(a.Commands) > 0 {
|
|
||||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
|
||||||
a.Commands = append(a.Commands, helpCommand)
|
|
||||||
if (HelpFlag != BoolFlag{}) {
|
|
||||||
a.appendFlag(HelpFlag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newCmds := []Command{}
|
|
||||||
for _, c := range a.Commands {
|
|
||||||
if c.HelpName == "" {
|
|
||||||
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
|
|
||||||
}
|
|
||||||
newCmds = append(newCmds, c)
|
|
||||||
}
|
|
||||||
a.Commands = newCmds
|
|
||||||
|
|
||||||
// parse flags
|
|
||||||
set, err := flagSet(a.Name, a.Flags)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
set.SetOutput(ioutil.Discard)
|
|
||||||
err = set.Parse(ctx.Args().Tail())
|
|
||||||
nerr := normalizeFlags(a.Flags, set)
|
|
||||||
context := NewContext(a, set, ctx)
|
|
||||||
|
|
||||||
if nerr != nil {
|
|
||||||
fmt.Fprintln(a.Writer, nerr)
|
|
||||||
fmt.Fprintln(a.Writer)
|
|
||||||
if len(a.Commands) > 0 {
|
|
||||||
ShowSubcommandHelp(context)
|
|
||||||
} else {
|
|
||||||
ShowCommandHelp(ctx, context.Args().First())
|
|
||||||
}
|
|
||||||
return nerr
|
|
||||||
}
|
|
||||||
|
|
||||||
if checkCompletions(context) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if a.OnUsageError != nil {
|
|
||||||
err = a.OnUsageError(context, err, true)
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
|
||||||
ShowSubcommandHelp(context)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(a.Commands) > 0 {
|
|
||||||
if checkSubcommandHelp(context) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if checkCommandHelp(ctx, context.Args().First()) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.After != nil {
|
|
||||||
defer func() {
|
|
||||||
afterErr := a.After(context)
|
|
||||||
if afterErr != nil {
|
|
||||||
HandleExitCoder(err)
|
|
||||||
if err != nil {
|
|
||||||
err = NewMultiError(err, afterErr)
|
|
||||||
} else {
|
|
||||||
err = afterErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Before != nil {
|
|
||||||
beforeErr := a.Before(context)
|
|
||||||
if beforeErr != nil {
|
|
||||||
HandleExitCoder(beforeErr)
|
|
||||||
err = beforeErr
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
args := context.Args()
|
|
||||||
if args.Present() {
|
|
||||||
name := args.First()
|
|
||||||
c := a.Command(name)
|
|
||||||
if c != nil {
|
|
||||||
return c.Run(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run default Action
|
|
||||||
err = HandleAction(a.Action, context)
|
|
||||||
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the named command on App. Returns nil if the command does not exist
|
|
||||||
func (a *App) Command(name string) *Command {
|
|
||||||
for _, c := range a.Commands {
|
|
||||||
if c.HasName(name) {
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Categories returns a slice containing all the categories with the commands they contain
|
|
||||||
func (a *App) Categories() CommandCategories {
|
|
||||||
return a.categories
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleCategories returns a slice of categories and commands that are
|
|
||||||
// Hidden=false
|
|
||||||
func (a *App) VisibleCategories() []*CommandCategory {
|
|
||||||
ret := []*CommandCategory{}
|
|
||||||
for _, category := range a.categories {
|
|
||||||
if visible := func() *CommandCategory {
|
|
||||||
for _, command := range category.Commands {
|
|
||||||
if !command.Hidden {
|
|
||||||
return category
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); visible != nil {
|
|
||||||
ret = append(ret, visible)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
|
||||||
func (a *App) VisibleCommands() []Command {
|
|
||||||
ret := []Command{}
|
|
||||||
for _, command := range a.Commands {
|
|
||||||
if !command.Hidden {
|
|
||||||
ret = append(ret, command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
|
||||||
func (a *App) VisibleFlags() []Flag {
|
|
||||||
return visibleFlags(a.Flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) hasFlag(flag Flag) bool {
|
|
||||||
for _, f := range a.Flags {
|
|
||||||
if flag == f {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) errWriter() io.Writer {
|
|
||||||
|
|
||||||
// When the app ErrWriter is nil use the package level one.
|
|
||||||
if a.ErrWriter == nil {
|
|
||||||
return ErrWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.ErrWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) appendFlag(flag Flag) {
|
|
||||||
if !a.hasFlag(flag) {
|
|
||||||
a.Flags = append(a.Flags, flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Author represents someone who has contributed to a cli project.
|
|
||||||
type Author struct {
|
|
||||||
Name string // The Authors name
|
|
||||||
Email string // The Authors email
|
|
||||||
}
|
|
||||||
|
|
||||||
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
|
|
||||||
func (a Author) String() string {
|
|
||||||
e := ""
|
|
||||||
if a.Email != "" {
|
|
||||||
e = " <" + a.Email + ">"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%v%v", a.Name, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleAction attempts to figure out which Action signature was used. If
|
|
||||||
// it's an ActionFunc or a func with the legacy signature for Action, the func
|
|
||||||
// is run!
|
|
||||||
func HandleAction(action interface{}, context *Context) (err error) {
|
|
||||||
if a, ok := action.(ActionFunc); ok {
|
|
||||||
return a(context)
|
|
||||||
} else if a, ok := action.(func(*Context) error); ok {
|
|
||||||
return a(context)
|
|
||||||
} else if a, ok := action.(func(*Context)); ok { // deprecated function signature
|
|
||||||
a(context)
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return errInvalidActionType
|
|
||||||
}
|
|
||||||
}
|
|
44
vendor/github.com/codegangsta/cli/category.go
generated
vendored
44
vendor/github.com/codegangsta/cli/category.go
generated
vendored
|
@ -1,44 +0,0 @@
|
||||||
package cli
|
|
||||||
|
|
||||||
// CommandCategories is a slice of *CommandCategory.
|
|
||||||
type CommandCategories []*CommandCategory
|
|
||||||
|
|
||||||
// CommandCategory is a category containing commands.
|
|
||||||
type CommandCategory struct {
|
|
||||||
Name string
|
|
||||||
Commands Commands
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CommandCategories) Less(i, j int) bool {
|
|
||||||
return c[i].Name < c[j].Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CommandCategories) Len() int {
|
|
||||||
return len(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CommandCategories) Swap(i, j int) {
|
|
||||||
c[i], c[j] = c[j], c[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddCommand adds a command to a category.
|
|
||||||
func (c CommandCategories) AddCommand(category string, command Command) CommandCategories {
|
|
||||||
for _, commandCategory := range c {
|
|
||||||
if commandCategory.Name == category {
|
|
||||||
commandCategory.Commands = append(commandCategory.Commands, command)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return append(c, &CommandCategory{Name: category, Commands: []Command{command}})
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
|
||||||
func (c *CommandCategory) VisibleCommands() []Command {
|
|
||||||
ret := []Command{}
|
|
||||||
for _, command := range c.Commands {
|
|
||||||
if !command.Hidden {
|
|
||||||
ret = append(ret, command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
22
vendor/github.com/codegangsta/cli/cli.go
generated
vendored
22
vendor/github.com/codegangsta/cli/cli.go
generated
vendored
|
@ -1,22 +0,0 @@
|
||||||
// Package cli provides a minimal framework for creating and organizing command line
|
|
||||||
// Go applications. cli is designed to be easy to understand and write, the most simple
|
|
||||||
// cli application can be written as follows:
|
|
||||||
// func main() {
|
|
||||||
// cli.NewApp().Run(os.Args)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Of course this application does not do much, so let's make this an actual application:
|
|
||||||
// func main() {
|
|
||||||
// app := cli.NewApp()
|
|
||||||
// app.Name = "greet"
|
|
||||||
// app.Usage = "say a greeting"
|
|
||||||
// app.Action = func(c *cli.Context) error {
|
|
||||||
// println("Greetings")
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// app.Run(os.Args)
|
|
||||||
// }
|
|
||||||
package cli
|
|
||||||
|
|
||||||
//go:generate python ./generate-flag-types cli -i flag-types.json -o flag_generated.go
|
|
304
vendor/github.com/codegangsta/cli/command.go
generated
vendored
304
vendor/github.com/codegangsta/cli/command.go
generated
vendored
|
@ -1,304 +0,0 @@
|
||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Command is a subcommand for a cli.App.
|
|
||||||
type Command struct {
|
|
||||||
// The name of the command
|
|
||||||
Name string
|
|
||||||
// short name of the command. Typically one character (deprecated, use `Aliases`)
|
|
||||||
ShortName string
|
|
||||||
// A list of aliases for the command
|
|
||||||
Aliases []string
|
|
||||||
// A short description of the usage of this command
|
|
||||||
Usage string
|
|
||||||
// Custom text to show on USAGE section of help
|
|
||||||
UsageText string
|
|
||||||
// A longer explanation of how the command works
|
|
||||||
Description string
|
|
||||||
// A short description of the arguments of this command
|
|
||||||
ArgsUsage string
|
|
||||||
// The category the command is part of
|
|
||||||
Category string
|
|
||||||
// The function to call when checking for bash command completions
|
|
||||||
BashComplete BashCompleteFunc
|
|
||||||
// An action to execute before any sub-subcommands are run, but after the context is ready
|
|
||||||
// If a non-nil error is returned, no sub-subcommands are run
|
|
||||||
Before BeforeFunc
|
|
||||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
|
||||||
// It is run even if Action() panics
|
|
||||||
After AfterFunc
|
|
||||||
// The function to call when this command is invoked
|
|
||||||
Action interface{}
|
|
||||||
// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
|
|
||||||
// of deprecation period has passed, maybe?
|
|
||||||
|
|
||||||
// Execute this function if a usage error occurs.
|
|
||||||
OnUsageError OnUsageErrorFunc
|
|
||||||
// List of child commands
|
|
||||||
Subcommands Commands
|
|
||||||
// List of flags to parse
|
|
||||||
Flags []Flag
|
|
||||||
// Treat all flags as normal arguments if true
|
|
||||||
SkipFlagParsing bool
|
|
||||||
// Skip argument reordering which attempts to move flags before arguments,
|
|
||||||
// but only works if all flags appear after all arguments. This behavior was
|
|
||||||
// removed n version 2 since it only works under specific conditions so we
|
|
||||||
// backport here by exposing it as an option for compatibility.
|
|
||||||
SkipArgReorder bool
|
|
||||||
// Boolean to hide built-in help command
|
|
||||||
HideHelp bool
|
|
||||||
// Boolean to hide this command from help or completion
|
|
||||||
Hidden bool
|
|
||||||
|
|
||||||
// Full name of command for help, defaults to full command name, including parent commands.
|
|
||||||
HelpName string
|
|
||||||
commandNamePath []string
|
|
||||||
|
|
||||||
// CustomHelpTemplate the text template for the command help topic.
|
|
||||||
// cli.go uses text/template to render templates. You can
|
|
||||||
// render custom help text by setting this variable.
|
|
||||||
CustomHelpTemplate string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandsByName []Command
|
|
||||||
|
|
||||||
func (c CommandsByName) Len() int {
|
|
||||||
return len(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CommandsByName) Less(i, j int) bool {
|
|
||||||
return c[i].Name < c[j].Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CommandsByName) Swap(i, j int) {
|
|
||||||
c[i], c[j] = c[j], c[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullName returns the full name of the command.
|
|
||||||
// For subcommands this ensures that parent commands are part of the command path
|
|
||||||
func (c Command) FullName() string {
|
|
||||||
if c.commandNamePath == nil {
|
|
||||||
return c.Name
|
|
||||||
}
|
|
||||||
return strings.Join(c.commandNamePath, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commands is a slice of Command
|
|
||||||
type Commands []Command
|
|
||||||
|
|
||||||
// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
|
||||||
func (c Command) Run(ctx *Context) (err error) {
|
|
||||||
if len(c.Subcommands) > 0 {
|
|
||||||
return c.startApp(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
|
|
||||||
// append help to flags
|
|
||||||
c.Flags = append(
|
|
||||||
c.Flags,
|
|
||||||
HelpFlag,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
set, err := flagSet(c.Name, c.Flags)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
set.SetOutput(ioutil.Discard)
|
|
||||||
|
|
||||||
if c.SkipFlagParsing {
|
|
||||||
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
|
|
||||||
} else if !c.SkipArgReorder {
|
|
||||||
firstFlagIndex := -1
|
|
||||||
terminatorIndex := -1
|
|
||||||
for index, arg := range ctx.Args() {
|
|
||||||
if arg == "--" {
|
|
||||||
terminatorIndex = index
|
|
||||||
break
|
|
||||||
} else if arg == "-" {
|
|
||||||
// Do nothing. A dash alone is not really a flag.
|
|
||||||
continue
|
|
||||||
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
|
|
||||||
firstFlagIndex = index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if firstFlagIndex > -1 {
|
|
||||||
args := ctx.Args()
|
|
||||||
regularArgs := make([]string, len(args[1:firstFlagIndex]))
|
|
||||||
copy(regularArgs, args[1:firstFlagIndex])
|
|
||||||
|
|
||||||
var flagArgs []string
|
|
||||||
if terminatorIndex > -1 {
|
|
||||||
flagArgs = args[firstFlagIndex:terminatorIndex]
|
|
||||||
regularArgs = append(regularArgs, args[terminatorIndex:]...)
|
|
||||||
} else {
|
|
||||||
flagArgs = args[firstFlagIndex:]
|
|
||||||
}
|
|
||||||
|
|
||||||
err = set.Parse(append(flagArgs, regularArgs...))
|
|
||||||
} else {
|
|
||||||
err = set.Parse(ctx.Args().Tail())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = set.Parse(ctx.Args().Tail())
|
|
||||||
}
|
|
||||||
|
|
||||||
nerr := normalizeFlags(c.Flags, set)
|
|
||||||
if nerr != nil {
|
|
||||||
fmt.Fprintln(ctx.App.Writer, nerr)
|
|
||||||
fmt.Fprintln(ctx.App.Writer)
|
|
||||||
ShowCommandHelp(ctx, c.Name)
|
|
||||||
return nerr
|
|
||||||
}
|
|
||||||
|
|
||||||
context := NewContext(ctx.App, set, ctx)
|
|
||||||
context.Command = c
|
|
||||||
if checkCommandCompletions(context, c.Name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if c.OnUsageError != nil {
|
|
||||||
err := c.OnUsageError(context, err, false)
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
|
|
||||||
fmt.Fprintln(context.App.Writer)
|
|
||||||
ShowCommandHelp(context, c.Name)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if checkCommandHelp(context, c.Name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.After != nil {
|
|
||||||
defer func() {
|
|
||||||
afterErr := c.After(context)
|
|
||||||
if afterErr != nil {
|
|
||||||
HandleExitCoder(err)
|
|
||||||
if err != nil {
|
|
||||||
err = NewMultiError(err, afterErr)
|
|
||||||
} else {
|
|
||||||
err = afterErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Before != nil {
|
|
||||||
err = c.Before(context)
|
|
||||||
if err != nil {
|
|
||||||
ShowCommandHelp(context, c.Name)
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Action == nil {
|
|
||||||
c.Action = helpSubcommand.Action
|
|
||||||
}
|
|
||||||
|
|
||||||
err = HandleAction(c.Action, context)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
HandleExitCoder(err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Names returns the names including short names and aliases.
|
|
||||||
func (c Command) Names() []string {
|
|
||||||
names := []string{c.Name}
|
|
||||||
|
|
||||||
if c.ShortName != "" {
|
|
||||||
names = append(names, c.ShortName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(names, c.Aliases...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasName returns true if Command.Name or Command.ShortName matches given name
|
|
||||||
func (c Command) HasName(name string) bool {
|
|
||||||
for _, n := range c.Names() {
|
|
||||||
if n == name {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Command) startApp(ctx *Context) error {
|
|
||||||
app := NewApp()
|
|
||||||
app.Metadata = ctx.App.Metadata
|
|
||||||
// set the name and usage
|
|
||||||
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
|
|
||||||
if c.HelpName == "" {
|
|
||||||
app.HelpName = c.HelpName
|
|
||||||
} else {
|
|
||||||
app.HelpName = app.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
app.Usage = c.Usage
|
|
||||||
app.Description = c.Description
|
|
||||||
app.ArgsUsage = c.ArgsUsage
|
|
||||||
|
|
||||||
// set CommandNotFound
|
|
||||||
app.CommandNotFound = ctx.App.CommandNotFound
|
|
||||||
app.CustomAppHelpTemplate = c.CustomHelpTemplate
|
|
||||||
|
|
||||||
// set the flags and commands
|
|
||||||
app.Commands = c.Subcommands
|
|
||||||
app.Flags = c.Flags
|
|
||||||
app.HideHelp = c.HideHelp
|
|
||||||
|
|
||||||
app.Version = ctx.App.Version
|
|
||||||
app.HideVersion = ctx.App.HideVersion
|
|
||||||
app.Compiled = ctx.App.Compiled
|
|
||||||
app.Author = ctx.App.Author
|
|
||||||
app.Email = ctx.App.Email
|
|
||||||
app.Writer = ctx.App.Writer
|
|
||||||
app.ErrWriter = ctx.App.ErrWriter
|
|
||||||
|
|
||||||
app.categories = CommandCategories{}
|
|
||||||
for _, command := range c.Subcommands {
|
|
||||||
app.categories = app.categories.AddCommand(command.Category, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(app.categories)
|
|
||||||
|
|
||||||
// bash completion
|
|
||||||
app.EnableBashCompletion = ctx.App.EnableBashCompletion
|
|
||||||
if c.BashComplete != nil {
|
|
||||||
app.BashComplete = c.BashComplete
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the actions
|
|
||||||
app.Before = c.Before
|
|
||||||
app.After = c.After
|
|
||||||
if c.Action != nil {
|
|
||||||
app.Action = c.Action
|
|
||||||
} else {
|
|
||||||
app.Action = helpSubcommand.Action
|
|
||||||
}
|
|
||||||
app.OnUsageError = c.OnUsageError
|
|
||||||
|
|
||||||
for index, cc := range app.Commands {
|
|
||||||
app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
|
|
||||||
}
|
|
||||||
|
|
||||||
return app.RunAsSubcommand(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
|
||||||
func (c Command) VisibleFlags() []Flag {
|
|
||||||
return visibleFlags(c.Flags)
|
|
||||||
}
|
|
278
vendor/github.com/codegangsta/cli/context.go
generated
vendored
278
vendor/github.com/codegangsta/cli/context.go
generated
vendored
|
@ -1,278 +0,0 @@
|
||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"flag"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Context is a type that is passed through to
|
|
||||||
// each Handler action in a cli application. Context
|
|
||||||
// can be used to retrieve context-specific Args and
|
|
||||||
// parsed command-line options.
|
|
||||||
type Context struct {
|
|
||||||
App *App
|
|
||||||
Command Command
|
|
||||||
shellComplete bool
|
|
||||||
flagSet *flag.FlagSet
|
|
||||||
setFlags map[string]bool
|
|
||||||
parentContext *Context
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewContext creates a new context. For use in when invoking an App or Command action.
|
|
||||||
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
|
|
||||||
c := &Context{App: app, flagSet: set, parentContext: parentCtx}
|
|
||||||
|
|
||||||
if parentCtx != nil {
|
|
||||||
c.shellComplete = parentCtx.shellComplete
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumFlags returns the number of flags set
|
|
||||||
func (c *Context) NumFlags() int {
|
|
||||||
return c.flagSet.NFlag()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set sets a context flag to a value.
|
|
||||||
func (c *Context) Set(name, value string) error {
|
|
||||||
c.setFlags = nil
|
|
||||||
return c.flagSet.Set(name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalSet sets a context flag to a value on the global flagset
|
|
||||||
func (c *Context) GlobalSet(name, value string) error {
|
|
||||||
globalContext(c).setFlags = nil
|
|
||||||
return globalContext(c).flagSet.Set(name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet determines if the flag was actually set
|
|
||||||
func (c *Context) IsSet(name string) bool {
|
|
||||||
if c.setFlags == nil {
|
|
||||||
c.setFlags = make(map[string]bool)
|
|
||||||
|
|
||||||
c.flagSet.Visit(func(f *flag.Flag) {
|
|
||||||
c.setFlags[f.Name] = true
|
|
||||||
})
|
|
||||||
|
|
||||||
c.flagSet.VisitAll(func(f *flag.Flag) {
|
|
||||||
if _, ok := c.setFlags[f.Name]; ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.setFlags[f.Name] = false
|
|
||||||
})
|
|
||||||
|
|
||||||
// XXX hack to support IsSet for flags with EnvVar
|
|
||||||
//
|
|
||||||
// There isn't an easy way to do this with the current implementation since
|
|
||||||
// whether a flag was set via an environment variable is very difficult to
|
|
||||||
// determine here. Instead, we intend to introduce a backwards incompatible
|
|
||||||
// change in version 2 to add `IsSet` to the Flag interface to push the
|
|
||||||
// responsibility closer to where the information required to determine
|
|
||||||
// whether a flag is set by non-standard means such as environment
|
|
||||||
// variables is avaliable.
|
|
||||||
//
|
|
||||||
// See https://github.com/urfave/cli/issues/294 for additional discussion
|
|
||||||
flags := c.Command.Flags
|
|
||||||
if c.Command.Name == "" { // cannot == Command{} since it contains slice types
|
|
||||||
if c.App != nil {
|
|
||||||
flags = c.App.Flags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, f := range flags {
|
|
||||||
eachName(f.GetName(), func(name string) {
|
|
||||||
if isSet, ok := c.setFlags[name]; isSet || !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val := reflect.ValueOf(f)
|
|
||||||
if val.Kind() == reflect.Ptr {
|
|
||||||
val = val.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
envVarValue := val.FieldByName("EnvVar")
|
|
||||||
if !envVarValue.IsValid() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(envVarValue.String(), func(envVar string) {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if _, ok := syscall.Getenv(envVar); ok {
|
|
||||||
c.setFlags[name] = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.setFlags[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalIsSet determines if the global flag was actually set
|
|
||||||
func (c *Context) GlobalIsSet(name string) bool {
|
|
||||||
ctx := c
|
|
||||||
if ctx.parentContext != nil {
|
|
||||||
ctx = ctx.parentContext
|
|
||||||
}
|
|
||||||
|
|
||||||
for ; ctx != nil; ctx = ctx.parentContext {
|
|
||||||
if ctx.IsSet(name) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlagNames returns a slice of flag names used in this context.
|
|
||||||
func (c *Context) FlagNames() (names []string) {
|
|
||||||
for _, flag := range c.Command.Flags {
|
|
||||||
name := strings.Split(flag.GetName(), ",")[0]
|
|
||||||
if name == "help" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalFlagNames returns a slice of global flag names used by the app.
|
|
||||||
func (c *Context) GlobalFlagNames() (names []string) {
|
|
||||||
for _, flag := range c.App.Flags {
|
|
||||||
name := strings.Split(flag.GetName(), ",")[0]
|
|
||||||
if name == "help" || name == "version" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parent returns the parent context, if any
|
|
||||||
func (c *Context) Parent() *Context {
|
|
||||||
return c.parentContext
|
|
||||||
}
|
|
||||||
|
|
||||||
// value returns the value of the flag coressponding to `name`
|
|
||||||
func (c *Context) value(name string) interface{} {
|
|
||||||
return c.flagSet.Lookup(name).Value.(flag.Getter).Get()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Args contains apps console arguments
|
|
||||||
type Args []string
|
|
||||||
|
|
||||||
// Args returns the command line arguments associated with the context.
|
|
||||||
func (c *Context) Args() Args {
|
|
||||||
args := Args(c.flagSet.Args())
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
// NArg returns the number of the command line arguments.
|
|
||||||
func (c *Context) NArg() int {
|
|
||||||
return len(c.Args())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the nth argument, or else a blank string
|
|
||||||
func (a Args) Get(n int) string {
|
|
||||||
if len(a) > n {
|
|
||||||
return a[n]
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// First returns the first argument, or else a blank string
|
|
||||||
func (a Args) First() string {
|
|
||||||
return a.Get(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tail returns the rest of the arguments (not the first one)
|
|
||||||
// or else an empty string slice
|
|
||||||
func (a Args) Tail() []string {
|
|
||||||
if len(a) >= 2 {
|
|
||||||
return []string(a)[1:]
|
|
||||||
}
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Present checks if there are any arguments present
|
|
||||||
func (a Args) Present() bool {
|
|
||||||
return len(a) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap swaps arguments at the given indexes
|
|
||||||
func (a Args) Swap(from, to int) error {
|
|
||||||
if from >= len(a) || to >= len(a) {
|
|
||||||
return errors.New("index out of range")
|
|
||||||
}
|
|
||||||
a[from], a[to] = a[to], a[from]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func globalContext(ctx *Context) *Context {
|
|
||||||
if ctx == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
if ctx.parentContext == nil {
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
ctx = ctx.parentContext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
|
|
||||||
if ctx.parentContext != nil {
|
|
||||||
ctx = ctx.parentContext
|
|
||||||
}
|
|
||||||
for ; ctx != nil; ctx = ctx.parentContext {
|
|
||||||
if f := ctx.flagSet.Lookup(name); f != nil {
|
|
||||||
return ctx.flagSet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
|
|
||||||
switch ff.Value.(type) {
|
|
||||||
case *StringSlice:
|
|
||||||
default:
|
|
||||||
set.Set(name, ff.Value.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
|
|
||||||
visited := make(map[string]bool)
|
|
||||||
set.Visit(func(f *flag.Flag) {
|
|
||||||
visited[f.Name] = true
|
|
||||||
})
|
|
||||||
for _, f := range flags {
|
|
||||||
parts := strings.Split(f.GetName(), ",")
|
|
||||||
if len(parts) == 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var ff *flag.Flag
|
|
||||||
for _, name := range parts {
|
|
||||||
name = strings.Trim(name, " ")
|
|
||||||
if visited[name] {
|
|
||||||
if ff != nil {
|
|
||||||
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
|
|
||||||
}
|
|
||||||
ff = set.Lookup(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ff == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, name := range parts {
|
|
||||||
name = strings.Trim(name, " ")
|
|
||||||
if !visited[name] {
|
|
||||||
copyFlag(name, ff, set)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
115
vendor/github.com/codegangsta/cli/errors.go
generated
vendored
115
vendor/github.com/codegangsta/cli/errors.go
generated
vendored
|
@ -1,115 +0,0 @@
|
||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OsExiter is the function used when the app exits. If not set defaults to os.Exit.
|
|
||||||
var OsExiter = os.Exit
|
|
||||||
|
|
||||||
// ErrWriter is used to write errors to the user. This can be anything
|
|
||||||
// implementing the io.Writer interface and defaults to os.Stderr.
|
|
||||||
var ErrWriter io.Writer = os.Stderr
|
|
||||||
|
|
||||||
// MultiError is an error that wraps multiple errors.
|
|
||||||
type MultiError struct {
|
|
||||||
Errors []error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMultiError creates a new MultiError. Pass in one or more errors.
|
|
||||||
func NewMultiError(err ...error) MultiError {
|
|
||||||
return MultiError{Errors: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error implements the error interface.
|
|
||||||
func (m MultiError) Error() string {
|
|
||||||
errs := make([]string, len(m.Errors))
|
|
||||||
for i, err := range m.Errors {
|
|
||||||
errs[i] = err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(errs, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
type ErrorFormatter interface {
|
|
||||||
Format(s fmt.State, verb rune)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExitCoder is the interface checked by `App` and `Command` for a custom exit
|
|
||||||
// code
|
|
||||||
type ExitCoder interface {
|
|
||||||
error
|
|
||||||
ExitCode() int
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExitError fulfills both the builtin `error` interface and `ExitCoder`
|
|
||||||
type ExitError struct {
|
|
||||||
exitCode int
|
|
||||||
message interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewExitError makes a new *ExitError
|
|
||||||
func NewExitError(message interface{}, exitCode int) *ExitError {
|
|
||||||
return &ExitError{
|
|
||||||
exitCode: exitCode,
|
|
||||||
message: message,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns the string message, fulfilling the interface required by
|
|
||||||
// `error`
|
|
||||||
func (ee *ExitError) Error() string {
|
|
||||||
return fmt.Sprintf("%v", ee.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExitCode returns the exit code, fulfilling the interface required by
|
|
||||||
// `ExitCoder`
|
|
||||||
func (ee *ExitError) ExitCode() int {
|
|
||||||
return ee.exitCode
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
|
|
||||||
// so prints the error to stderr (if it is non-empty) and calls OsExiter with the
|
|
||||||
// given exit code. If the given error is a MultiError, then this func is
|
|
||||||
// called on all members of the Errors slice and calls OsExiter with the last exit code.
|
|
||||||
func HandleExitCoder(err error) {
|
|
||||||
if err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if exitErr, ok := err.(ExitCoder); ok {
|
|
||||||
if err.Error() != "" {
|
|
||||||
if _, ok := exitErr.(ErrorFormatter); ok {
|
|
||||||
fmt.Fprintf(ErrWriter, "%+v\n", err)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintln(ErrWriter, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OsExiter(exitErr.ExitCode())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if multiErr, ok := err.(MultiError); ok {
|
|
||||||
code := handleMultiError(multiErr)
|
|
||||||
OsExiter(code)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleMultiError(multiErr MultiError) int {
|
|
||||||
code := 1
|
|
||||||
for _, merr := range multiErr.Errors {
|
|
||||||
if multiErr2, ok := merr.(MultiError); ok {
|
|
||||||
code = handleMultiError(multiErr2)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintln(ErrWriter, merr)
|
|
||||||
if exitErr, ok := merr.(ExitCoder); ok {
|
|
||||||
code = exitErr.ExitCode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
799
vendor/github.com/codegangsta/cli/flag.go
generated
vendored
799
vendor/github.com/codegangsta/cli/flag.go
generated
vendored
|
@ -1,799 +0,0 @@
|
||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultPlaceholder = "value"
|
|
||||||
|
|
||||||
// BashCompletionFlag enables bash-completion for all commands and subcommands
|
|
||||||
var BashCompletionFlag Flag = BoolFlag{
|
|
||||||
Name: "generate-bash-completion",
|
|
||||||
Hidden: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// VersionFlag prints the version for the application
|
|
||||||
var VersionFlag Flag = BoolFlag{
|
|
||||||
Name: "version, v",
|
|
||||||
Usage: "print the version",
|
|
||||||
}
|
|
||||||
|
|
||||||
// HelpFlag prints the help for all commands and subcommands
|
|
||||||
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
|
|
||||||
// unless HideHelp is set to true)
|
|
||||||
var HelpFlag Flag = BoolFlag{
|
|
||||||
Name: "help, h",
|
|
||||||
Usage: "show help",
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlagStringer converts a flag definition to a string. This is used by help
|
|
||||||
// to display a flag.
|
|
||||||
var FlagStringer FlagStringFunc = stringifyFlag
|
|
||||||
|
|
||||||
// FlagsByName is a slice of Flag.
|
|
||||||
type FlagsByName []Flag
|
|
||||||
|
|
||||||
func (f FlagsByName) Len() int {
|
|
||||||
return len(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f FlagsByName) Less(i, j int) bool {
|
|
||||||
return f[i].GetName() < f[j].GetName()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f FlagsByName) Swap(i, j int) {
|
|
||||||
f[i], f[j] = f[j], f[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flag is a common interface related to parsing flags in cli.
|
|
||||||
// For more advanced flag parsing techniques, it is recommended that
|
|
||||||
// this interface be implemented.
|
|
||||||
type Flag interface {
|
|
||||||
fmt.Stringer
|
|
||||||
// Apply Flag settings to the given flag set
|
|
||||||
Apply(*flag.FlagSet)
|
|
||||||
GetName() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// errorableFlag is an interface that allows us to return errors during apply
|
|
||||||
// it allows flags defined in this library to return errors in a fashion backwards compatible
|
|
||||||
// TODO remove in v2 and modify the existing Flag interface to return errors
|
|
||||||
type errorableFlag interface {
|
|
||||||
Flag
|
|
||||||
|
|
||||||
ApplyWithError(*flag.FlagSet) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
|
|
||||||
set := flag.NewFlagSet(name, flag.ContinueOnError)
|
|
||||||
|
|
||||||
for _, f := range flags {
|
|
||||||
//TODO remove in v2 when errorableFlag is removed
|
|
||||||
if ef, ok := f.(errorableFlag); ok {
|
|
||||||
if err := ef.ApplyWithError(set); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
f.Apply(set)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return set, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func eachName(longName string, fn func(string)) {
|
|
||||||
parts := strings.Split(longName, ",")
|
|
||||||
for _, name := range parts {
|
|
||||||
name = strings.Trim(name, " ")
|
|
||||||
fn(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic is a generic parseable type identified by a specific flag
|
|
||||||
type Generic interface {
|
|
||||||
Set(value string) error
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply takes the flagset and calls Set on the generic flag with the value
|
|
||||||
// provided by the user for parsing by the flag
|
|
||||||
// Ignores parsing errors
|
|
||||||
func (f GenericFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError takes the flagset and calls Set on the generic flag with the value
|
|
||||||
// provided by the user for parsing by the flag
|
|
||||||
func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
val := f.Value
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
if err := val.Set(envVal); err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
set.Var(f.Value, name, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter
|
|
||||||
type StringSlice []string
|
|
||||||
|
|
||||||
// Set appends the string value to the list of values
|
|
||||||
func (f *StringSlice) Set(value string) error {
|
|
||||||
*f = append(*f, value)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value (for usage defaults)
|
|
||||||
func (f *StringSlice) String() string {
|
|
||||||
return fmt.Sprintf("%s", *f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the slice of strings set by this flag
|
|
||||||
func (f *StringSlice) Value() []string {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the slice of strings set by this flag
|
|
||||||
func (f *StringSlice) Get() interface{} {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
newVal := &StringSlice{}
|
|
||||||
for _, s := range strings.Split(envVal, ",") {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if err := newVal.Set(s); err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.Value = newVal
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Value == nil {
|
|
||||||
f.Value = &StringSlice{}
|
|
||||||
}
|
|
||||||
set.Var(f.Value, name, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter
|
|
||||||
type IntSlice []int
|
|
||||||
|
|
||||||
// Set parses the value into an integer and appends it to the list of values
|
|
||||||
func (f *IntSlice) Set(value string) error {
|
|
||||||
tmp, err := strconv.Atoi(value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*f = append(*f, tmp)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value (for usage defaults)
|
|
||||||
func (f *IntSlice) String() string {
|
|
||||||
return fmt.Sprintf("%#v", *f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the slice of ints set by this flag
|
|
||||||
func (f *IntSlice) Value() []int {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the slice of ints set by this flag
|
|
||||||
func (f *IntSlice) Get() interface{} {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
newVal := &IntSlice{}
|
|
||||||
for _, s := range strings.Split(envVal, ",") {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if err := newVal.Set(s); err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.Value = newVal
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Value == nil {
|
|
||||||
f.Value = &IntSlice{}
|
|
||||||
}
|
|
||||||
set.Var(f.Value, name, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter
|
|
||||||
type Int64Slice []int64
|
|
||||||
|
|
||||||
// Set parses the value into an integer and appends it to the list of values
|
|
||||||
func (f *Int64Slice) Set(value string) error {
|
|
||||||
tmp, err := strconv.ParseInt(value, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*f = append(*f, tmp)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value (for usage defaults)
|
|
||||||
func (f *Int64Slice) String() string {
|
|
||||||
return fmt.Sprintf("%#v", *f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the slice of ints set by this flag
|
|
||||||
func (f *Int64Slice) Value() []int64 {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the slice of ints set by this flag
|
|
||||||
func (f *Int64Slice) Get() interface{} {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
newVal := &Int64Slice{}
|
|
||||||
for _, s := range strings.Split(envVal, ",") {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if err := newVal.Set(s); err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.Value = newVal
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Value == nil {
|
|
||||||
f.Value = &Int64Slice{}
|
|
||||||
}
|
|
||||||
set.Var(f.Value, name, f.Usage)
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f BoolFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
val := false
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
if envVal == "" {
|
|
||||||
val = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
envValBool, err := strconv.ParseBool(envVal)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
val = envValBool
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.BoolVar(f.Destination, name, val, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Bool(name, val, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f BoolTFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
val := true
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
if envVal == "" {
|
|
||||||
val = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
envValBool, err := strconv.ParseBool(envVal)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
val = envValBool
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.BoolVar(f.Destination, name, val, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Bool(name, val, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f StringFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f StringFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
f.Value = envVal
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.StringVar(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.String(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f IntFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f IntFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
f.Value = int(envValInt)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.IntVar(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Int(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f Int64Flag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Value = envValInt
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.Int64Var(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Int64(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f UintFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f UintFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValInt, err := strconv.ParseUint(envVal, 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Value = uint(envValInt)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.UintVar(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Uint(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f Uint64Flag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValInt, err := strconv.ParseUint(envVal, 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Value = uint64(envValInt)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.Uint64Var(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Uint64(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f DurationFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValDuration, err := time.ParseDuration(envVal)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Value = envValDuration
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.DurationVar(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Duration(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f Float64Flag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValFloat, err := strconv.ParseFloat(envVal, 10)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Value = float64(envValFloat)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.Float64Var(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Float64(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func visibleFlags(fl []Flag) []Flag {
|
|
||||||
visible := []Flag{}
|
|
||||||
for _, flag := range fl {
|
|
||||||
field := flagValue(flag).FieldByName("Hidden")
|
|
||||||
if !field.IsValid() || !field.Bool() {
|
|
||||||
visible = append(visible, flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return visible
|
|
||||||
}
|
|
||||||
|
|
||||||
func prefixFor(name string) (prefix string) {
|
|
||||||
if len(name) == 1 {
|
|
||||||
prefix = "-"
|
|
||||||
} else {
|
|
||||||
prefix = "--"
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the placeholder, if any, and the unquoted usage string.
|
|
||||||
func unquoteUsage(usage string) (string, string) {
|
|
||||||
for i := 0; i < len(usage); i++ {
|
|
||||||
if usage[i] == '`' {
|
|
||||||
for j := i + 1; j < len(usage); j++ {
|
|
||||||
if usage[j] == '`' {
|
|
||||||
name := usage[i+1 : j]
|
|
||||||
usage = usage[:i] + name + usage[j+1:]
|
|
||||||
return name, usage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", usage
|
|
||||||
}
|
|
||||||
|
|
||||||
func prefixedNames(fullName, placeholder string) string {
|
|
||||||
var prefixed string
|
|
||||||
parts := strings.Split(fullName, ",")
|
|
||||||
for i, name := range parts {
|
|
||||||
name = strings.Trim(name, " ")
|
|
||||||
prefixed += prefixFor(name) + name
|
|
||||||
if placeholder != "" {
|
|
||||||
prefixed += " " + placeholder
|
|
||||||
}
|
|
||||||
if i < len(parts)-1 {
|
|
||||||
prefixed += ", "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return prefixed
|
|
||||||
}
|
|
||||||
|
|
||||||
func withEnvHint(envVar, str string) string {
|
|
||||||
envText := ""
|
|
||||||
if envVar != "" {
|
|
||||||
prefix := "$"
|
|
||||||
suffix := ""
|
|
||||||
sep := ", $"
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
prefix = "%"
|
|
||||||
suffix = "%"
|
|
||||||
sep = "%, %"
|
|
||||||
}
|
|
||||||
envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix)
|
|
||||||
}
|
|
||||||
return str + envText
|
|
||||||
}
|
|
||||||
|
|
||||||
func flagValue(f Flag) reflect.Value {
|
|
||||||
fv := reflect.ValueOf(f)
|
|
||||||
for fv.Kind() == reflect.Ptr {
|
|
||||||
fv = reflect.Indirect(fv)
|
|
||||||
}
|
|
||||||
return fv
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifyFlag(f Flag) string {
|
|
||||||
fv := flagValue(f)
|
|
||||||
|
|
||||||
switch f.(type) {
|
|
||||||
case IntSliceFlag:
|
|
||||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
|
||||||
stringifyIntSliceFlag(f.(IntSliceFlag)))
|
|
||||||
case Int64SliceFlag:
|
|
||||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
|
||||||
stringifyInt64SliceFlag(f.(Int64SliceFlag)))
|
|
||||||
case StringSliceFlag:
|
|
||||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
|
||||||
stringifyStringSliceFlag(f.(StringSliceFlag)))
|
|
||||||
}
|
|
||||||
|
|
||||||
placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String())
|
|
||||||
|
|
||||||
needsPlaceholder := false
|
|
||||||
defaultValueString := ""
|
|
||||||
|
|
||||||
if val := fv.FieldByName("Value"); val.IsValid() {
|
|
||||||
needsPlaceholder = true
|
|
||||||
defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface())
|
|
||||||
|
|
||||||
if val.Kind() == reflect.String && val.String() != "" {
|
|
||||||
defaultValueString = fmt.Sprintf(" (default: %q)", val.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if defaultValueString == " (default: )" {
|
|
||||||
defaultValueString = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if needsPlaceholder && placeholder == "" {
|
|
||||||
placeholder = defaultPlaceholder
|
|
||||||
}
|
|
||||||
|
|
||||||
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString))
|
|
||||||
|
|
||||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
|
||||||
fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault))
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifyIntSliceFlag(f IntSliceFlag) string {
|
|
||||||
defaultVals := []string{}
|
|
||||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
|
||||||
for _, i := range f.Value.Value() {
|
|
||||||
defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifyInt64SliceFlag(f Int64SliceFlag) string {
|
|
||||||
defaultVals := []string{}
|
|
||||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
|
||||||
for _, i := range f.Value.Value() {
|
|
||||||
defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifyStringSliceFlag(f StringSliceFlag) string {
|
|
||||||
defaultVals := []string{}
|
|
||||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
|
||||||
for _, s := range f.Value.Value() {
|
|
||||||
if len(s) > 0 {
|
|
||||||
defaultVals = append(defaultVals, fmt.Sprintf("%q", s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifySliceFlag(usage, name string, defaultVals []string) string {
|
|
||||||
placeholder, usage := unquoteUsage(usage)
|
|
||||||
if placeholder == "" {
|
|
||||||
placeholder = defaultPlaceholder
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultVal := ""
|
|
||||||
if len(defaultVals) > 0 {
|
|
||||||
defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal))
|
|
||||||
return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault)
|
|
||||||
}
|
|
627
vendor/github.com/codegangsta/cli/flag_generated.go
generated
vendored
627
vendor/github.com/codegangsta/cli/flag_generated.go
generated
vendored
|
@ -1,627 +0,0 @@
|
||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WARNING: This file is generated!
|
|
||||||
|
|
||||||
// BoolFlag is a flag with type bool
|
|
||||||
type BoolFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Destination *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f BoolFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f BoolFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bool looks up the value of a local BoolFlag, returns
|
|
||||||
// false if not found
|
|
||||||
func (c *Context) Bool(name string) bool {
|
|
||||||
return lookupBool(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalBool looks up the value of a global BoolFlag, returns
|
|
||||||
// false if not found
|
|
||||||
func (c *Context) GlobalBool(name string) bool {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupBool(name, fs)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupBool(name string, set *flag.FlagSet) bool {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseBool(f.Value.String())
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolTFlag is a flag with type bool that is true by default
|
|
||||||
type BoolTFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Destination *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f BoolTFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f BoolTFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolT looks up the value of a local BoolTFlag, returns
|
|
||||||
// false if not found
|
|
||||||
func (c *Context) BoolT(name string) bool {
|
|
||||||
return lookupBoolT(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalBoolT looks up the value of a global BoolTFlag, returns
|
|
||||||
// false if not found
|
|
||||||
func (c *Context) GlobalBoolT(name string) bool {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupBoolT(name, fs)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupBoolT(name string, set *flag.FlagSet) bool {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseBool(f.Value.String())
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
|
|
||||||
type DurationFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value time.Duration
|
|
||||||
Destination *time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f DurationFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f DurationFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duration looks up the value of a local DurationFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Duration(name string) time.Duration {
|
|
||||||
return lookupDuration(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalDuration looks up the value of a global DurationFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalDuration(name string) time.Duration {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupDuration(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := time.ParseDuration(f.Value.String())
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64Flag is a flag with type float64
|
|
||||||
type Float64Flag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value float64
|
|
||||||
Destination *float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f Float64Flag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f Float64Flag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64 looks up the value of a local Float64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Float64(name string) float64 {
|
|
||||||
return lookupFloat64(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalFloat64 looks up the value of a global Float64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalFloat64(name string) float64 {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupFloat64(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupFloat64(name string, set *flag.FlagSet) float64 {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseFloat(f.Value.String(), 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenericFlag is a flag with type Generic
|
|
||||||
type GenericFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value Generic
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f GenericFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f GenericFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic looks up the value of a local GenericFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) Generic(name string) interface{} {
|
|
||||||
return lookupGeneric(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalGeneric looks up the value of a global GenericFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) GlobalGeneric(name string) interface{} {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupGeneric(name, fs)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := f.Value, error(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64Flag is a flag with type int64
|
|
||||||
type Int64Flag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value int64
|
|
||||||
Destination *int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f Int64Flag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f Int64Flag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64 looks up the value of a local Int64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Int64(name string) int64 {
|
|
||||||
return lookupInt64(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalInt64 looks up the value of a global Int64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalInt64(name string) int64 {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupInt64(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupInt64(name string, set *flag.FlagSet) int64 {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntFlag is a flag with type int
|
|
||||||
type IntFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value int
|
|
||||||
Destination *int
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f IntFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f IntFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int looks up the value of a local IntFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Int(name string) int {
|
|
||||||
return lookupInt(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalInt looks up the value of a global IntFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalInt(name string) int {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupInt(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupInt(name string, set *flag.FlagSet) int {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(parsed)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntSliceFlag is a flag with type *IntSlice
|
|
||||||
type IntSliceFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value *IntSlice
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f IntSliceFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f IntSliceFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntSlice looks up the value of a local IntSliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) IntSlice(name string) []int {
|
|
||||||
return lookupIntSlice(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalIntSlice looks up the value of a global IntSliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) GlobalIntSlice(name string) []int {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupIntSlice(name, fs)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupIntSlice(name string, set *flag.FlagSet) []int {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := (f.Value.(*IntSlice)).Value(), error(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64SliceFlag is a flag with type *Int64Slice
|
|
||||||
type Int64SliceFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value *Int64Slice
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f Int64SliceFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f Int64SliceFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64Slice looks up the value of a local Int64SliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) Int64Slice(name string) []int64 {
|
|
||||||
return lookupInt64Slice(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) GlobalInt64Slice(name string) []int64 {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupInt64Slice(name, fs)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringFlag is a flag with type string
|
|
||||||
type StringFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value string
|
|
||||||
Destination *string
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f StringFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f StringFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// String looks up the value of a local StringFlag, returns
|
|
||||||
// "" if not found
|
|
||||||
func (c *Context) String(name string) string {
|
|
||||||
return lookupString(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalString looks up the value of a global StringFlag, returns
|
|
||||||
// "" if not found
|
|
||||||
func (c *Context) GlobalString(name string) string {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupString(name, fs)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupString(name string, set *flag.FlagSet) string {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := f.Value.String(), error(nil)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringSliceFlag is a flag with type *StringSlice
|
|
||||||
type StringSliceFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value *StringSlice
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f StringSliceFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f StringSliceFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringSlice looks up the value of a local StringSliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) StringSlice(name string) []string {
|
|
||||||
return lookupStringSlice(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalStringSlice looks up the value of a global StringSliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) GlobalStringSlice(name string) []string {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupStringSlice(name, fs)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupStringSlice(name string, set *flag.FlagSet) []string {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := (f.Value.(*StringSlice)).Value(), error(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint64Flag is a flag with type uint64
|
|
||||||
type Uint64Flag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value uint64
|
|
||||||
Destination *uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f Uint64Flag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f Uint64Flag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint64 looks up the value of a local Uint64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Uint64(name string) uint64 {
|
|
||||||
return lookupUint64(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalUint64 looks up the value of a global Uint64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalUint64(name string) uint64 {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupUint64(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupUint64(name string, set *flag.FlagSet) uint64 {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// UintFlag is a flag with type uint
|
|
||||||
type UintFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value uint
|
|
||||||
Destination *uint
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f UintFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f UintFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint looks up the value of a local UintFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Uint(name string) uint {
|
|
||||||
return lookupUint(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalUint looks up the value of a global UintFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalUint(name string) uint {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupUint(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupUint(name string, set *flag.FlagSet) uint {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return uint(parsed)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
28
vendor/github.com/codegangsta/cli/funcs.go
generated
vendored
28
vendor/github.com/codegangsta/cli/funcs.go
generated
vendored
|
@ -1,28 +0,0 @@
|
||||||
package cli
|
|
||||||
|
|
||||||
// BashCompleteFunc is an action to execute when the bash-completion flag is set
|
|
||||||
type BashCompleteFunc func(*Context)
|
|
||||||
|
|
||||||
// BeforeFunc is an action to execute before any subcommands are run, but after
|
|
||||||
// the context is ready if a non-nil error is returned, no subcommands are run
|
|
||||||
type BeforeFunc func(*Context) error
|
|
||||||
|
|
||||||
// AfterFunc is an action to execute after any subcommands are run, but after the
|
|
||||||
// subcommand has finished it is run even if Action() panics
|
|
||||||
type AfterFunc func(*Context) error
|
|
||||||
|
|
||||||
// ActionFunc is the action to execute when no subcommands are specified
|
|
||||||
type ActionFunc func(*Context) error
|
|
||||||
|
|
||||||
// CommandNotFoundFunc is executed if the proper command cannot be found
|
|
||||||
type CommandNotFoundFunc func(*Context, string)
|
|
||||||
|
|
||||||
// OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying
|
|
||||||
// customized usage error messages. This function is able to replace the
|
|
||||||
// original error messages. If this function is not set, the "Incorrect usage"
|
|
||||||
// is displayed and the execution is interrupted.
|
|
||||||
type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error
|
|
||||||
|
|
||||||
// FlagStringFunc is used by the help generation to display a flag, which is
|
|
||||||
// expected to be a single line.
|
|
||||||
type FlagStringFunc func(Flag) string
|
|
338
vendor/github.com/codegangsta/cli/help.go
generated
vendored
338
vendor/github.com/codegangsta/cli/help.go
generated
vendored
|
@ -1,338 +0,0 @@
|
||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AppHelpTemplate is the text template for the Default help topic.
|
|
||||||
// cli.go uses text/template to render templates. You can
|
|
||||||
// render custom help text by setting this variable.
|
|
||||||
var AppHelpTemplate = `NAME:
|
|
||||||
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
|
|
||||||
|
|
||||||
USAGE:
|
|
||||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
|
|
||||||
|
|
||||||
VERSION:
|
|
||||||
{{.Version}}{{end}}{{end}}{{if .Description}}
|
|
||||||
|
|
||||||
DESCRIPTION:
|
|
||||||
{{.Description}}{{end}}{{if len .Authors}}
|
|
||||||
|
|
||||||
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
|
|
||||||
{{range $index, $author := .Authors}}{{if $index}}
|
|
||||||
{{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
|
|
||||||
|
|
||||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
|
||||||
{{.Name}}:{{end}}{{range .VisibleCommands}}
|
|
||||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
|
|
||||||
|
|
||||||
GLOBAL OPTIONS:
|
|
||||||
{{range $index, $option := .VisibleFlags}}{{if $index}}
|
|
||||||
{{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
|
|
||||||
|
|
||||||
COPYRIGHT:
|
|
||||||
{{.Copyright}}{{end}}
|
|
||||||
`
|
|
||||||
|
|
||||||
// CommandHelpTemplate is the text template for the command help topic.
|
|
||||||
// cli.go uses text/template to render templates. You can
|
|
||||||
// render custom help text by setting this variable.
|
|
||||||
var CommandHelpTemplate = `NAME:
|
|
||||||
{{.HelpName}} - {{.Usage}}
|
|
||||||
|
|
||||||
USAGE:
|
|
||||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
|
|
||||||
|
|
||||||
CATEGORY:
|
|
||||||
{{.Category}}{{end}}{{if .Description}}
|
|
||||||
|
|
||||||
DESCRIPTION:
|
|
||||||
{{.Description}}{{end}}{{if .VisibleFlags}}
|
|
||||||
|
|
||||||
OPTIONS:
|
|
||||||
{{range .VisibleFlags}}{{.}}
|
|
||||||
{{end}}{{end}}
|
|
||||||
`
|
|
||||||
|
|
||||||
// SubcommandHelpTemplate is the text template for the subcommand help topic.
|
|
||||||
// cli.go uses text/template to render templates. You can
|
|
||||||
// render custom help text by setting this variable.
|
|
||||||
var SubcommandHelpTemplate = `NAME:
|
|
||||||
{{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}}
|
|
||||||
|
|
||||||
USAGE:
|
|
||||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
|
|
||||||
|
|
||||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
|
||||||
{{.Name}}:{{end}}{{range .VisibleCommands}}
|
|
||||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}
|
|
||||||
{{end}}{{if .VisibleFlags}}
|
|
||||||
OPTIONS:
|
|
||||||
{{range .VisibleFlags}}{{.}}
|
|
||||||
{{end}}{{end}}
|
|
||||||
`
|
|
||||||
|
|
||||||
var helpCommand = Command{
|
|
||||||
Name: "help",
|
|
||||||
Aliases: []string{"h"},
|
|
||||||
Usage: "Shows a list of commands or help for one command",
|
|
||||||
ArgsUsage: "[command]",
|
|
||||||
Action: func(c *Context) error {
|
|
||||||
args := c.Args()
|
|
||||||
if args.Present() {
|
|
||||||
return ShowCommandHelp(c, args.First())
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowAppHelp(c)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var helpSubcommand = Command{
|
|
||||||
Name: "help",
|
|
||||||
Aliases: []string{"h"},
|
|
||||||
Usage: "Shows a list of commands or help for one command",
|
|
||||||
ArgsUsage: "[command]",
|
|
||||||
Action: func(c *Context) error {
|
|
||||||
args := c.Args()
|
|
||||||
if args.Present() {
|
|
||||||
return ShowCommandHelp(c, args.First())
|
|
||||||
}
|
|
||||||
|
|
||||||
return ShowSubcommandHelp(c)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prints help for the App or Command
|
|
||||||
type helpPrinter func(w io.Writer, templ string, data interface{})
|
|
||||||
|
|
||||||
// Prints help for the App or Command with custom template function.
|
|
||||||
type helpPrinterCustom func(w io.Writer, templ string, data interface{}, customFunc map[string]interface{})
|
|
||||||
|
|
||||||
// HelpPrinter is a function that writes the help output. If not set a default
|
|
||||||
// is used. The function signature is:
|
|
||||||
// func(w io.Writer, templ string, data interface{})
|
|
||||||
var HelpPrinter helpPrinter = printHelp
|
|
||||||
|
|
||||||
// HelpPrinterCustom is same as HelpPrinter but
|
|
||||||
// takes a custom function for template function map.
|
|
||||||
var HelpPrinterCustom helpPrinterCustom = printHelpCustom
|
|
||||||
|
|
||||||
// VersionPrinter prints the version for the App
|
|
||||||
var VersionPrinter = printVersion
|
|
||||||
|
|
||||||
// ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code.
|
|
||||||
func ShowAppHelpAndExit(c *Context, exitCode int) {
|
|
||||||
ShowAppHelp(c)
|
|
||||||
os.Exit(exitCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowAppHelp is an action that displays the help.
|
|
||||||
func ShowAppHelp(c *Context) (err error) {
|
|
||||||
if c.App.CustomAppHelpTemplate == "" {
|
|
||||||
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
customAppData := func() map[string]interface{} {
|
|
||||||
if c.App.ExtraInfo == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return map[string]interface{}{
|
|
||||||
"ExtraInfo": c.App.ExtraInfo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HelpPrinterCustom(c.App.Writer, c.App.CustomAppHelpTemplate, c.App, customAppData())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultAppComplete prints the list of subcommands as the default app completion method
|
|
||||||
func DefaultAppComplete(c *Context) {
|
|
||||||
for _, command := range c.App.Commands {
|
|
||||||
if command.Hidden {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, name := range command.Names() {
|
|
||||||
fmt.Fprintln(c.App.Writer, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowCommandHelpAndExit - exits with code after showing help
|
|
||||||
func ShowCommandHelpAndExit(c *Context, command string, code int) {
|
|
||||||
ShowCommandHelp(c, command)
|
|
||||||
os.Exit(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowCommandHelp prints help for the given command
|
|
||||||
func ShowCommandHelp(ctx *Context, command string) error {
|
|
||||||
// show the subcommand help for a command with subcommands
|
|
||||||
if command == "" {
|
|
||||||
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range ctx.App.Commands {
|
|
||||||
if c.HasName(command) {
|
|
||||||
if c.CustomHelpTemplate != "" {
|
|
||||||
HelpPrinterCustom(ctx.App.Writer, c.CustomHelpTemplate, c, nil)
|
|
||||||
} else {
|
|
||||||
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.App.CommandNotFound == nil {
|
|
||||||
return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.App.CommandNotFound(ctx, command)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowSubcommandHelp prints help for the given subcommand
|
|
||||||
func ShowSubcommandHelp(c *Context) error {
|
|
||||||
return ShowCommandHelp(c, c.Command.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowVersion prints the version number of the App
|
|
||||||
func ShowVersion(c *Context) {
|
|
||||||
VersionPrinter(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func printVersion(c *Context) {
|
|
||||||
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowCompletions prints the lists of commands within a given context
|
|
||||||
func ShowCompletions(c *Context) {
|
|
||||||
a := c.App
|
|
||||||
if a != nil && a.BashComplete != nil {
|
|
||||||
a.BashComplete(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowCommandCompletions prints the custom completions for a given command
|
|
||||||
func ShowCommandCompletions(ctx *Context, command string) {
|
|
||||||
c := ctx.App.Command(command)
|
|
||||||
if c != nil && c.BashComplete != nil {
|
|
||||||
c.BashComplete(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc map[string]interface{}) {
|
|
||||||
funcMap := template.FuncMap{
|
|
||||||
"join": strings.Join,
|
|
||||||
}
|
|
||||||
if customFunc != nil {
|
|
||||||
for key, value := range customFunc {
|
|
||||||
funcMap[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
|
|
||||||
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
|
|
||||||
err := t.Execute(w, data)
|
|
||||||
if err != nil {
|
|
||||||
// If the writer is closed, t.Execute will fail, and there's nothing
|
|
||||||
// we can do to recover.
|
|
||||||
if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
|
|
||||||
fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func printHelp(out io.Writer, templ string, data interface{}) {
|
|
||||||
printHelpCustom(out, templ, data, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkVersion(c *Context) bool {
|
|
||||||
found := false
|
|
||||||
if VersionFlag.GetName() != "" {
|
|
||||||
eachName(VersionFlag.GetName(), func(name string) {
|
|
||||||
if c.GlobalBool(name) || c.Bool(name) {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkHelp(c *Context) bool {
|
|
||||||
found := false
|
|
||||||
if HelpFlag.GetName() != "" {
|
|
||||||
eachName(HelpFlag.GetName(), func(name string) {
|
|
||||||
if c.GlobalBool(name) || c.Bool(name) {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCommandHelp(c *Context, name string) bool {
|
|
||||||
if c.Bool("h") || c.Bool("help") {
|
|
||||||
ShowCommandHelp(c, name)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkSubcommandHelp(c *Context) bool {
|
|
||||||
if c.Bool("h") || c.Bool("help") {
|
|
||||||
ShowSubcommandHelp(c)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) {
|
|
||||||
if !a.EnableBashCompletion {
|
|
||||||
return false, arguments
|
|
||||||
}
|
|
||||||
|
|
||||||
pos := len(arguments) - 1
|
|
||||||
lastArg := arguments[pos]
|
|
||||||
|
|
||||||
if lastArg != "--"+BashCompletionFlag.GetName() {
|
|
||||||
return false, arguments
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, arguments[:pos]
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCompletions(c *Context) bool {
|
|
||||||
if !c.shellComplete {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if args := c.Args(); args.Present() {
|
|
||||||
name := args.First()
|
|
||||||
if cmd := c.App.Command(name); cmd != nil {
|
|
||||||
// let the command handle the completion
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowCompletions(c)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCommandCompletions(c *Context, name string) bool {
|
|
||||||
if !c.shellComplete {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowCommandCompletions(c, name)
|
|
||||||
return true
|
|
||||||
}
|
|
202
vendor/github.com/vulcand/route/LICENSE
generated
vendored
202
vendor/github.com/vulcand/route/LICENSE
generated
vendored
|
@ -1,202 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
88
vendor/github.com/vulcand/route/iter.go
generated
vendored
88
vendor/github.com/vulcand/route/iter.go
generated
vendored
|
@ -1,88 +0,0 @@
|
||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// charPos stores the position in the iterator
|
|
||||||
type charPos struct {
|
|
||||||
i int
|
|
||||||
si int
|
|
||||||
}
|
|
||||||
|
|
||||||
// charIter is a iterator over sequence of strings, returns byte-by-byte characters in string by string
|
|
||||||
type charIter struct {
|
|
||||||
i int // position in the current string
|
|
||||||
si int // position in the array of strings
|
|
||||||
|
|
||||||
seq []string // sequence of strings, e.g. ["GET", "/path"]
|
|
||||||
sep []byte // every string in the sequence has an associated separator used for trie matching, e.g. path uses '/' for separator
|
|
||||||
// so sequence ["a.host", "/path "]has acoompanying separators ['.', '/']
|
|
||||||
}
|
|
||||||
|
|
||||||
func newIter(seq []string, sep []byte) *charIter {
|
|
||||||
return &charIter{
|
|
||||||
i: 0,
|
|
||||||
si: 0,
|
|
||||||
seq: seq,
|
|
||||||
sep: sep,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *charIter) level() int {
|
|
||||||
return r.si
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *charIter) String() string {
|
|
||||||
if r.isEnd() {
|
|
||||||
return "<end>"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("<%d:%v>", r.i, r.seq[r.si])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *charIter) isEnd() bool {
|
|
||||||
return len(r.seq) == 0 || // no data at all
|
|
||||||
(r.si >= len(r.seq)-1 && r.i >= len(r.seq[r.si])) || // we are at the last char of last seq
|
|
||||||
(len(r.seq[r.si]) == 0) // empty input
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *charIter) position() charPos {
|
|
||||||
return charPos{i: r.i, si: r.si}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *charIter) setPosition(p charPos) {
|
|
||||||
r.i = p.i
|
|
||||||
r.si = p.si
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *charIter) pushBack() {
|
|
||||||
if r.i == 0 && r.si == 0 { // this is start
|
|
||||||
return
|
|
||||||
} else if r.i == 0 && r.si != 0 { // this is start of the next string
|
|
||||||
r.si--
|
|
||||||
r.i = len(r.seq[r.si]) - 1
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.i--
|
|
||||||
}
|
|
||||||
|
|
||||||
// next returns current byte in the sequence, separator corresponding to that byte, and boolean idicator of whether it's the end of the sequence
|
|
||||||
func (r *charIter) next() (byte, byte, bool) {
|
|
||||||
// we have reached the last string in the index, end
|
|
||||||
if r.isEnd() {
|
|
||||||
return 0, 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
b := r.seq[r.si][r.i]
|
|
||||||
sep := r.sep[r.si]
|
|
||||||
r.i++
|
|
||||||
|
|
||||||
// current string index exceeded the last char of the current string
|
|
||||||
// move to the next string if it's present
|
|
||||||
if r.i >= len(r.seq[r.si]) && r.si < len(r.seq)-1 {
|
|
||||||
r.si++
|
|
||||||
r.i = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return b, sep, true
|
|
||||||
}
|
|
186
vendor/github.com/vulcand/route/mapper.go
generated
vendored
186
vendor/github.com/vulcand/route/mapper.go
generated
vendored
|
@ -1,186 +0,0 @@
|
||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// requestMapper maps the request to string e.g. maps request to it's hostname, or request to header
|
|
||||||
type requestMapper interface {
|
|
||||||
// separator returns the separator that makes sense for this request, e.g. / for urls or . for domains
|
|
||||||
separator() byte
|
|
||||||
// equals returns the equivalent mapper if the two mappers are equivalent, e.g. map to the same sequence
|
|
||||||
// mappers are also equivalent if one mapper is subset of another, e.g. combined mapper (host, path) is equivalent of (host) mapper
|
|
||||||
equivalent(requestMapper) requestMapper
|
|
||||||
// mapRequest maps request to string, e.g. request to it's URL path
|
|
||||||
mapRequest(r *http.Request) string
|
|
||||||
// newIter returns the iterator instead of string for stream matchers
|
|
||||||
newIter(r *http.Request) *charIter
|
|
||||||
}
|
|
||||||
|
|
||||||
type methodMapper struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *methodMapper) separator() byte {
|
|
||||||
return methodSep
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *methodMapper) equivalent(o requestMapper) requestMapper {
|
|
||||||
_, ok := o.(*methodMapper)
|
|
||||||
if ok {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *methodMapper) mapRequest(r *http.Request) string {
|
|
||||||
return r.Method
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *methodMapper) newIter(r *http.Request) *charIter {
|
|
||||||
return newIter([]string{m.mapRequest(r)}, []byte{m.separator()})
|
|
||||||
}
|
|
||||||
|
|
||||||
type pathMapper struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *pathMapper) separator() byte {
|
|
||||||
return pathSep
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pathMapper) equivalent(o requestMapper) requestMapper {
|
|
||||||
_, ok := o.(*pathMapper)
|
|
||||||
if ok {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pathMapper) newIter(r *http.Request) *charIter {
|
|
||||||
return newIter([]string{p.mapRequest(r)}, []byte{p.separator()})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pathMapper) mapRequest(r *http.Request) string {
|
|
||||||
return rawPath(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
type hostMapper struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *hostMapper) equivalent(o requestMapper) requestMapper {
|
|
||||||
_, ok := o.(*hostMapper)
|
|
||||||
if ok {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *hostMapper) separator() byte {
|
|
||||||
return domainSep
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hostMapper) mapRequest(r *http.Request) string {
|
|
||||||
return strings.Split(strings.ToLower(r.Host), ":")[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *hostMapper) newIter(r *http.Request) *charIter {
|
|
||||||
return newIter([]string{p.mapRequest(r)}, []byte{p.separator()})
|
|
||||||
}
|
|
||||||
|
|
||||||
type headerMapper struct {
|
|
||||||
header string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *headerMapper) equivalent(o requestMapper) requestMapper {
|
|
||||||
hm, ok := o.(*headerMapper)
|
|
||||||
if ok && hm.header == h.header {
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *headerMapper) separator() byte {
|
|
||||||
return headerSep
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *headerMapper) mapRequest(r *http.Request) string {
|
|
||||||
return r.Header.Get(h.header)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *headerMapper) newIter(r *http.Request) *charIter {
|
|
||||||
return newIter([]string{h.mapRequest(r)}, []byte{h.separator()})
|
|
||||||
}
|
|
||||||
|
|
||||||
type seqMapper struct {
|
|
||||||
seq []requestMapper
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSeqMapper(seq ...requestMapper) *seqMapper {
|
|
||||||
var out []requestMapper
|
|
||||||
for _, s := range seq {
|
|
||||||
switch m := s.(type) {
|
|
||||||
case *seqMapper:
|
|
||||||
out = append(out, m.seq...)
|
|
||||||
default:
|
|
||||||
out = append(out, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &seqMapper{seq: out}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seqMapper) newIter(r *http.Request) *charIter {
|
|
||||||
out := make([]string, len(s.seq))
|
|
||||||
for i := range s.seq {
|
|
||||||
out[i] = s.seq[i].mapRequest(r)
|
|
||||||
}
|
|
||||||
seps := make([]byte, len(s.seq))
|
|
||||||
for i := range s.seq {
|
|
||||||
seps[i] = s.seq[i].separator()
|
|
||||||
}
|
|
||||||
return newIter(out, seps)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seqMapper) mapRequest(r *http.Request) string {
|
|
||||||
out := make([]string, len(s.seq))
|
|
||||||
for i := range s.seq {
|
|
||||||
out[i] = s.seq[i].mapRequest(r)
|
|
||||||
}
|
|
||||||
return strings.Join(out, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seqMapper) separator() byte {
|
|
||||||
return s.seq[0].separator()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seqMapper) equivalent(o requestMapper) requestMapper {
|
|
||||||
so, ok := o.(*seqMapper)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var longer, shorter *seqMapper
|
|
||||||
if len(s.seq) > len(so.seq) {
|
|
||||||
longer = s
|
|
||||||
shorter = so
|
|
||||||
} else {
|
|
||||||
longer = so
|
|
||||||
shorter = s
|
|
||||||
}
|
|
||||||
for i, _ := range longer.seq {
|
|
||||||
// shorter is subset of longer, return longer sequence mapper
|
|
||||||
if i >= len(shorter.seq)-1 {
|
|
||||||
return longer
|
|
||||||
}
|
|
||||||
if longer.seq[i].equivalent(shorter.seq[i]) == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return longer
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
pathSep = '/'
|
|
||||||
domainSep = '.'
|
|
||||||
headerSep = '/'
|
|
||||||
methodSep = ' '
|
|
||||||
)
|
|
155
vendor/github.com/vulcand/route/matcher.go
generated
vendored
155
vendor/github.com/vulcand/route/matcher.go
generated
vendored
|
@ -1,155 +0,0 @@
|
||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type matcher interface {
|
|
||||||
match(*http.Request) *match
|
|
||||||
setMatch(match *match)
|
|
||||||
|
|
||||||
canMerge(matcher) bool
|
|
||||||
merge(matcher) (matcher, error)
|
|
||||||
|
|
||||||
canChain(matcher) bool
|
|
||||||
chain(matcher) (matcher, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hostTrieMatcher(hostname string) (matcher, error) {
|
|
||||||
return newTrieMatcher(strings.ToLower(hostname), &hostMapper{}, &match{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func hostRegexpMatcher(hostname string) (matcher, error) {
|
|
||||||
return newRegexpMatcher(strings.ToLower(hostname), &hostMapper{}, &match{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func methodTrieMatcher(method string) (matcher, error) {
|
|
||||||
return newTrieMatcher(method, &methodMapper{}, &match{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func methodRegexpMatcher(method string) (matcher, error) {
|
|
||||||
return newRegexpMatcher(method, &methodMapper{}, &match{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func pathTrieMatcher(path string) (matcher, error) {
|
|
||||||
return newTrieMatcher(path, &pathMapper{}, &match{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func pathRegexpMatcher(path string) (matcher, error) {
|
|
||||||
return newRegexpMatcher(path, &pathMapper{}, &match{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func headerTrieMatcher(name, value string) (matcher, error) {
|
|
||||||
return newTrieMatcher(value, &headerMapper{header: name}, &match{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func headerRegexpMatcher(name, value string) (matcher, error) {
|
|
||||||
return newRegexpMatcher(value, &headerMapper{header: name}, &match{})
|
|
||||||
}
|
|
||||||
|
|
||||||
type match struct {
|
|
||||||
val interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type andMatcher struct {
|
|
||||||
a matcher
|
|
||||||
b matcher
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAndMatcher(a, b matcher) matcher {
|
|
||||||
if a.canChain(b) {
|
|
||||||
m, err := a.chain(b)
|
|
||||||
if err == nil {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &andMatcher{
|
|
||||||
a: a, b: b,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *andMatcher) canChain(matcher) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *andMatcher) chain(matcher) (matcher, error) {
|
|
||||||
return nil, fmt.Errorf("not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *andMatcher) String() string {
|
|
||||||
return fmt.Sprintf("andMatcher(%v, %v)", a.a, a.b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *andMatcher) setMatch(m *match) {
|
|
||||||
a.a.setMatch(m)
|
|
||||||
a.b.setMatch(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *andMatcher) canMerge(o matcher) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *andMatcher) merge(o matcher) (matcher, error) {
|
|
||||||
return nil, fmt.Errorf("Method not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *andMatcher) match(req *http.Request) *match {
|
|
||||||
result := a.a.match(req)
|
|
||||||
if result == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return a.b.match(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regular expression matcher, takes a regular expression and requestMapper
|
|
||||||
type regexpMatcher struct {
|
|
||||||
// Uses this mapper to extract a string from a request to match against
|
|
||||||
mapper requestMapper
|
|
||||||
// Compiled regular expression
|
|
||||||
expr *regexp.Regexp
|
|
||||||
// match result
|
|
||||||
result *match
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *regexpMatcher) canChain(matcher) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *regexpMatcher) chain(matcher) (matcher, error) {
|
|
||||||
return nil, fmt.Errorf("not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *regexpMatcher) String() string {
|
|
||||||
return fmt.Sprintf("regexpMatcher(%v)", m.expr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *regexpMatcher) setMatch(result *match) {
|
|
||||||
m.result = result
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRegexpMatcher(expr string, mapper requestMapper, m *match) (matcher, error) {
|
|
||||||
r, err := regexp.Compile(expr)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Bad regular expression: %s %s", expr, err)
|
|
||||||
}
|
|
||||||
return ®expMatcher{expr: r, mapper: mapper, result: m}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *regexpMatcher) canMerge(matcher) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *regexpMatcher) merge(matcher) (matcher, error) {
|
|
||||||
return nil, fmt.Errorf("Method not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *regexpMatcher) match(req *http.Request) *match {
|
|
||||||
if m.expr.MatchString(m.mapper.mapRequest(req)) {
|
|
||||||
return m.result
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
74
vendor/github.com/vulcand/route/mux.go
generated
vendored
74
vendor/github.com/vulcand/route/mux.go
generated
vendored
|
@ -1,74 +0,0 @@
|
||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Mux implements router compatible with http.Handler
|
|
||||||
type Mux struct {
|
|
||||||
// NotFound sets handler for routes that are not found
|
|
||||||
notFound http.Handler
|
|
||||||
router Router
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMux returns new Mux router
|
|
||||||
func NewMux() *Mux {
|
|
||||||
return &Mux{
|
|
||||||
router: New(),
|
|
||||||
notFound: ¬Found{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle adds http handler for route expression
|
|
||||||
func (m *Mux) Handle(expr string, handler http.Handler) error {
|
|
||||||
return m.router.UpsertRoute(expr, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle adds http handler function for route expression
|
|
||||||
func (m *Mux) HandleFunc(expr string, handler func(http.ResponseWriter, *http.Request)) error {
|
|
||||||
return m.Handle(expr, http.HandlerFunc(handler))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Mux) Remove(expr string) error {
|
|
||||||
return m.router.RemoveRoute(expr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP routes the request and passes it to handler
|
|
||||||
func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
h, err := m.router.Route(r)
|
|
||||||
if err != nil || h == nil {
|
|
||||||
m.notFound.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.(http.Handler).ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Mux) SetNotFound(n http.Handler) error {
|
|
||||||
if n == nil {
|
|
||||||
return fmt.Errorf("Not Found handler cannot be nil. Operation rejected.")
|
|
||||||
}
|
|
||||||
m.notFound = n
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Mux) GetNotFound() http.Handler {
|
|
||||||
return m.notFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Mux) IsValid(expr string) bool {
|
|
||||||
return IsValid(expr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotFound is a generic http.Handler for request
|
|
||||||
type notFound struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP returns a simple 404 Not found response
|
|
||||||
func (notFound) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
fmt.Fprint(w, "Not found")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
47
vendor/github.com/vulcand/route/parse.go
generated
vendored
47
vendor/github.com/vulcand/route/parse.go
generated
vendored
|
@ -1,47 +0,0 @@
|
||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/vulcand/predicate"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsValid checks whether expression is valid
|
|
||||||
func IsValid(expr string) bool {
|
|
||||||
_, err := parse(expr, &match{})
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parse(expression string, result *match) (matcher, error) {
|
|
||||||
p, err := predicate.NewParser(predicate.Def{
|
|
||||||
Functions: map[string]interface{}{
|
|
||||||
"Host": hostTrieMatcher,
|
|
||||||
"HostRegexp": hostRegexpMatcher,
|
|
||||||
|
|
||||||
"Path": pathTrieMatcher,
|
|
||||||
"PathRegexp": pathRegexpMatcher,
|
|
||||||
|
|
||||||
"Method": methodTrieMatcher,
|
|
||||||
"MethodRegexp": methodRegexpMatcher,
|
|
||||||
|
|
||||||
"Header": headerTrieMatcher,
|
|
||||||
"HeaderRegexp": headerRegexpMatcher,
|
|
||||||
},
|
|
||||||
Operators: predicate.Operators{
|
|
||||||
AND: newAndMatcher,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
out, err := p.Parse(expression)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m, ok := out.(matcher)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unknown result type: %T", out)
|
|
||||||
}
|
|
||||||
m.setMatch(result)
|
|
||||||
return m, nil
|
|
||||||
}
|
|
195
vendor/github.com/vulcand/route/router.go
generated
vendored
195
vendor/github.com/vulcand/route/router.go
generated
vendored
|
@ -1,195 +0,0 @@
|
||||||
/*
|
|
||||||
package route provides http package-compatible routing library. It can route http requests by hostname, method, path and headers.
|
|
||||||
|
|
||||||
Route defines simple language for matching requests based on Go syntax. Route provides series of matchers that follow the syntax:
|
|
||||||
|
|
||||||
|
|
||||||
Matcher("value") // matches value using trie
|
|
||||||
Matcher("<string>.value") // uses trie-based matching for a.value and b.value
|
|
||||||
MatcherRegexp(".*value") // uses regexp-based matching
|
|
||||||
|
|
||||||
Host matcher:
|
|
||||||
|
|
||||||
Host("<subdomain>.localhost") // trie-based matcher for a.localhost, b.localhost, etc.
|
|
||||||
HostRegexp(".*localhost") // regexp based matcher
|
|
||||||
|
|
||||||
Path matcher:
|
|
||||||
|
|
||||||
Path("/hello/<value>") // trie-based matcher for raw request path
|
|
||||||
PathRegexp("/hello/.*") // regexp-based matcher for raw request path
|
|
||||||
|
|
||||||
Method matcher:
|
|
||||||
|
|
||||||
Method("GET") // trie-based matcher for request method
|
|
||||||
MethodRegexp("POST|PUT") // regexp based matcher for request method
|
|
||||||
|
|
||||||
Header matcher:
|
|
||||||
|
|
||||||
Header("Content-Type", "application/<subtype>") // trie-based matcher for headers
|
|
||||||
HeaderRegexp("Content-Type", "application/.*") // regexp based matcher for headers
|
|
||||||
|
|
||||||
Matchers can be combined using && operator:
|
|
||||||
|
|
||||||
Host("localhost") && Method("POST") && Path("/v1")
|
|
||||||
|
|
||||||
Route library will join the trie-based matchers into one trie matcher when possible, for example:
|
|
||||||
|
|
||||||
Host("localhost") && Method("POST") && Path("/v1")
|
|
||||||
Host("localhost") && Method("GET") && Path("/v2")
|
|
||||||
|
|
||||||
Will be combined into one trie for performance. If you add a third route:
|
|
||||||
|
|
||||||
Host("localhost") && Method("GET") && PathRegexp("/v2/.*")
|
|
||||||
|
|
||||||
It wont be joined ito the trie, and would be matched separatedly instead.
|
|
||||||
*/
|
|
||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Router implements http request routing and operations. It is a generic router not conforming to http.Handler interface, to get a handler
|
|
||||||
// conforming to http.Handler interface, use Mux router instead.
|
|
||||||
type Router interface {
|
|
||||||
// GetRoute returns a route by a given expression, returns nil if expresison is not found
|
|
||||||
GetRoute(string) interface{}
|
|
||||||
|
|
||||||
// AddRoute adds a route to match by expression, returns error if the expression already defined, or route expression is incorrect
|
|
||||||
AddRoute(string, interface{}) error
|
|
||||||
|
|
||||||
// RemoveRoute removes a route for a given expression
|
|
||||||
RemoveRoute(string) error
|
|
||||||
|
|
||||||
// UpsertRoute updates an existing route or adds a new route by given expression
|
|
||||||
UpsertRoute(string, interface{}) error
|
|
||||||
|
|
||||||
// Route takes a request and matches it against requests, returns matched route in case if found, nil if there's no matching route or error in case of internal error.
|
|
||||||
Route(*http.Request) (interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type router struct {
|
|
||||||
mutex *sync.RWMutex
|
|
||||||
matchers []matcher
|
|
||||||
routes map[string]*match
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new Router instance
|
|
||||||
func New() Router {
|
|
||||||
return &router{
|
|
||||||
mutex: &sync.RWMutex{},
|
|
||||||
routes: make(map[string]*match),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *router) GetRoute(expr string) interface{} {
|
|
||||||
e.mutex.RLock()
|
|
||||||
defer e.mutex.RUnlock()
|
|
||||||
|
|
||||||
res, ok := e.routes[expr]
|
|
||||||
if ok {
|
|
||||||
return res.val
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *router) AddRoute(expr string, val interface{}) error {
|
|
||||||
e.mutex.Lock()
|
|
||||||
defer e.mutex.Unlock()
|
|
||||||
|
|
||||||
if _, ok := e.routes[expr]; ok {
|
|
||||||
return fmt.Errorf("Expression '%s' already exists", expr)
|
|
||||||
}
|
|
||||||
result := &match{val: val}
|
|
||||||
if _, err := parse(expr, result); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
e.routes[expr] = result
|
|
||||||
if err := e.compile(); err != nil {
|
|
||||||
delete(e.routes, expr)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *router) UpsertRoute(expr string, val interface{}) error {
|
|
||||||
e.mutex.Lock()
|
|
||||||
defer e.mutex.Unlock()
|
|
||||||
|
|
||||||
result := &match{val: val}
|
|
||||||
if _, err := parse(expr, result); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
prev, existed := e.routes[expr]
|
|
||||||
|
|
||||||
e.routes[expr] = result
|
|
||||||
if err := e.compile(); err != nil {
|
|
||||||
if existed {
|
|
||||||
e.routes[expr] = prev
|
|
||||||
} else {
|
|
||||||
delete(e.routes, expr)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *router) compile() error {
|
|
||||||
var exprs = []string{}
|
|
||||||
for expr, _ := range e.routes {
|
|
||||||
exprs = append(exprs, expr)
|
|
||||||
}
|
|
||||||
sort.Sort(sort.Reverse(sort.StringSlice(exprs)))
|
|
||||||
|
|
||||||
matchers := []matcher{}
|
|
||||||
i := 0
|
|
||||||
for _, expr := range exprs {
|
|
||||||
result := e.routes[expr]
|
|
||||||
matcher, err := parse(expr, result)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge the previous and new matcher if that's possible
|
|
||||||
if i > 0 && matchers[i-1].canMerge(matcher) {
|
|
||||||
m, err := matchers[i-1].merge(matcher)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
matchers[i-1] = m
|
|
||||||
} else {
|
|
||||||
matchers = append(matchers, matcher)
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
e.matchers = matchers
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *router) RemoveRoute(expr string) error {
|
|
||||||
e.mutex.Lock()
|
|
||||||
defer e.mutex.Unlock()
|
|
||||||
|
|
||||||
delete(e.routes, expr)
|
|
||||||
return e.compile()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *router) Route(req *http.Request) (interface{}, error) {
|
|
||||||
e.mutex.RLock()
|
|
||||||
defer e.mutex.RUnlock()
|
|
||||||
|
|
||||||
if len(e.matchers) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range e.matchers {
|
|
||||||
if l := m.match(req); l != nil {
|
|
||||||
return l.val, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
476
vendor/github.com/vulcand/route/trie.go
generated
vendored
476
vendor/github.com/vulcand/route/trie.go
generated
vendored
|
@ -1,476 +0,0 @@
|
||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Regular expression to match url parameters
|
|
||||||
var reParam *regexp.Regexp
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
reParam = regexp.MustCompile("^<([^>]+)>")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trie http://en.wikipedia.org/wiki/Trie for url matching with support of named parameters
|
|
||||||
type trie struct {
|
|
||||||
root *trieNode
|
|
||||||
// mapper takes the request and returns sequence that can be matched
|
|
||||||
mapper requestMapper
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *trie) canChain(o matcher) bool {
|
|
||||||
_, ok := o.(*trie)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *trie) chain(o matcher) (matcher, error) {
|
|
||||||
to, ok := o.(*trie)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("can chain only with other trie")
|
|
||||||
}
|
|
||||||
m := t.root.findMatchNode()
|
|
||||||
m.matches = nil
|
|
||||||
m.children = []*trieNode{to.root}
|
|
||||||
t.root.setLevel(-1)
|
|
||||||
return &trie{
|
|
||||||
root: t.root,
|
|
||||||
mapper: newSeqMapper(t.mapper, to.mapper),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *trie) String() string {
|
|
||||||
return fmt.Sprintf("trieMatcher()")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Takes the expression with url and the node that corresponds to this expression and returns parsed trie
|
|
||||||
func newTrieMatcher(expression string, mapper requestMapper, result *match) (*trie, error) {
|
|
||||||
t := &trie{
|
|
||||||
mapper: mapper,
|
|
||||||
}
|
|
||||||
t.root = &trieNode{trie: t}
|
|
||||||
if len(expression) == 0 {
|
|
||||||
return nil, fmt.Errorf("Empty URL expression")
|
|
||||||
}
|
|
||||||
err := t.root.parseExpression(-1, expression, result)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *trie) setMatch(result *match) {
|
|
||||||
t.root.setMatch(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tries can merge with other tries
|
|
||||||
func (t *trie) canMerge(m matcher) bool {
|
|
||||||
ot, ok := m.(*trie)
|
|
||||||
return ok && t.mapper.equivalent(ot.mapper) != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge takes the other trie and modifies itself to match the passed trie as well.
|
|
||||||
// Note that trie passed as a parameter can be only simple trie without multiple branches per node, e.g. a->b->c->
|
|
||||||
// Trie on the left is "accumulating" trie that grows.
|
|
||||||
func (p *trie) merge(m matcher) (matcher, error) {
|
|
||||||
other, ok := m.(*trie)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("Can't merge %T and %T", p, m)
|
|
||||||
}
|
|
||||||
mapper := p.mapper.equivalent(other.mapper)
|
|
||||||
if mapper == nil {
|
|
||||||
return nil, fmt.Errorf("Can't merge %T and %T", p, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
root, err := p.root.merge(other.root)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &trie{root: root, mapper: mapper}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Takes the request and returns the location if the request path matches any of it's paths
|
|
||||||
// returns nil if none of the requests matches
|
|
||||||
func (p *trie) match(r *http.Request) *match {
|
|
||||||
if p.root == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return p.root.match(p.mapper.newIter(r))
|
|
||||||
}
|
|
||||||
|
|
||||||
type trieNode struct {
|
|
||||||
trie *trie
|
|
||||||
// Matching character, can be empty in case if it's a root node
|
|
||||||
// or node with a pattern matcher
|
|
||||||
char byte
|
|
||||||
// Optional children of this node, can be empty if it's a leaf node
|
|
||||||
children []*trieNode
|
|
||||||
// If present, means that this node is a pattern matcher
|
|
||||||
patternMatcher patternMatcher
|
|
||||||
// If present it means this node contains potential match for a request, and this is a leaf node.
|
|
||||||
matches []*match
|
|
||||||
// For chained tries matching different parts of the request levels would increase for next chained trie nodes
|
|
||||||
level int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *trieNode) setMatch(m *match) {
|
|
||||||
n := e.findMatchNode()
|
|
||||||
n.matches = []*match{m}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *trieNode) setLevel(level int) {
|
|
||||||
if e.isRoot() {
|
|
||||||
level++
|
|
||||||
}
|
|
||||||
e.level = level
|
|
||||||
if len(e.matches) != 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Check for the match in child nodes
|
|
||||||
for _, c := range e.children {
|
|
||||||
c.setLevel(level)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *trieNode) findMatchNode() *trieNode {
|
|
||||||
if len(e.matches) != 0 {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
// Check for the match in child nodes
|
|
||||||
for _, c := range e.children {
|
|
||||||
if n := c.findMatchNode(); n != nil {
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *trieNode) isMatching() bool {
|
|
||||||
return len(e.matches) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *trieNode) isRoot() bool {
|
|
||||||
return e.char == byte(0) && e.patternMatcher == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *trieNode) isPatternMatcher() bool {
|
|
||||||
return e.patternMatcher != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *trieNode) isCharMatcher() bool {
|
|
||||||
return e.char != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *trieNode) String() string {
|
|
||||||
self := ""
|
|
||||||
if e.patternMatcher != nil {
|
|
||||||
self = e.patternMatcher.String()
|
|
||||||
} else {
|
|
||||||
self = fmt.Sprintf("%c", e.char)
|
|
||||||
}
|
|
||||||
if e.isMatching() {
|
|
||||||
return fmt.Sprintf("match(%d:%s)", e.level, self)
|
|
||||||
} else if e.isRoot() {
|
|
||||||
return fmt.Sprintf("root(%d)", e.level)
|
|
||||||
} else {
|
|
||||||
return fmt.Sprintf("node(%d:%s)", e.level, self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *trieNode) equals(o *trieNode) bool {
|
|
||||||
return (e.level == o.level) && // we can merge nodes that are on the same level to avoid merges for different subtrie parts
|
|
||||||
(e.char == o.char) && // chars are equal
|
|
||||||
(e.patternMatcher == nil && o.patternMatcher == nil) || // both nodes have no matchers
|
|
||||||
((e.patternMatcher != nil && o.patternMatcher != nil) && e.patternMatcher.equals(o.patternMatcher)) // both nodes have equal matchers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *trieNode) merge(o *trieNode) (*trieNode, error) {
|
|
||||||
children := make([]*trieNode, 0, len(e.children))
|
|
||||||
merged := make(map[*trieNode]bool)
|
|
||||||
|
|
||||||
// First, find the nodes with similar keys and merge them
|
|
||||||
for _, c := range e.children {
|
|
||||||
for _, c2 := range o.children {
|
|
||||||
// The nodes are equivalent, so we can merge them
|
|
||||||
if c.equals(c2) {
|
|
||||||
m, err := c.merge(c2)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
merged[c] = true
|
|
||||||
merged[c2] = true
|
|
||||||
children = append(children, m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next, append the keys that haven't been merged
|
|
||||||
for _, c := range e.children {
|
|
||||||
if !merged[c] {
|
|
||||||
children = append(children, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range o.children {
|
|
||||||
if !merged[c] {
|
|
||||||
children = append(children, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &trieNode{
|
|
||||||
level: e.level,
|
|
||||||
trie: e.trie,
|
|
||||||
char: e.char,
|
|
||||||
children: children,
|
|
||||||
patternMatcher: e.patternMatcher,
|
|
||||||
matches: append(e.matches, o.matches...),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *trieNode) parseExpression(offset int, pattern string, m *match) error {
|
|
||||||
// We are the last element, so we are the matching node
|
|
||||||
if offset >= len(pattern)-1 {
|
|
||||||
p.matches = []*match{m}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// There's a next character that exists
|
|
||||||
patternMatcher, newOffset, err := parsePatternMatcher(offset+1, pattern)
|
|
||||||
// We have found the matcher, but the syntax or parameters are wrong
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Matcher was found
|
|
||||||
if patternMatcher != nil {
|
|
||||||
node := &trieNode{patternMatcher: patternMatcher, trie: p.trie}
|
|
||||||
p.children = []*trieNode{node}
|
|
||||||
return node.parseExpression(newOffset-1, pattern, m)
|
|
||||||
} else {
|
|
||||||
// Matcher was not found, next node is just a character
|
|
||||||
node := &trieNode{char: pattern[offset+1], trie: p.trie}
|
|
||||||
p.children = []*trieNode{node}
|
|
||||||
return node.parseExpression(offset+1, pattern, m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parsePatternMatcher(offset int, pattern string) (patternMatcher, int, error) {
|
|
||||||
if pattern[offset] != '<' {
|
|
||||||
return nil, -1, nil
|
|
||||||
}
|
|
||||||
rest := pattern[offset:]
|
|
||||||
match := reParam.FindStringSubmatchIndex(rest)
|
|
||||||
if len(match) == 0 {
|
|
||||||
return nil, -1, nil
|
|
||||||
}
|
|
||||||
// Split parsed matcher parameters separated by :
|
|
||||||
values := strings.Split(rest[match[2]:match[3]], ":")
|
|
||||||
|
|
||||||
// The common syntax is <matcherType:matcherArg1:matcherArg2>
|
|
||||||
matcherType := values[0]
|
|
||||||
matcherArgs := values[1:]
|
|
||||||
|
|
||||||
// In case if there's only one <param> is implicitly converted to <string:param>
|
|
||||||
if len(values) == 1 {
|
|
||||||
matcherType = "string"
|
|
||||||
matcherArgs = values
|
|
||||||
}
|
|
||||||
matcher, err := makeMatcher(matcherType, matcherArgs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, offset, err
|
|
||||||
}
|
|
||||||
return matcher, offset + match[1], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type matchResult struct {
|
|
||||||
matcher patternMatcher
|
|
||||||
value interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type patternMatcher interface {
|
|
||||||
getName() string
|
|
||||||
match(i *charIter) bool
|
|
||||||
equals(other patternMatcher) bool
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeMatcher(matcherType string, matcherArgs []string) (patternMatcher, error) {
|
|
||||||
switch matcherType {
|
|
||||||
case "string":
|
|
||||||
return newStringMatcher(matcherArgs)
|
|
||||||
case "int":
|
|
||||||
return newIntMatcher(matcherArgs)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("unsupported matcher: %s", matcherType)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStringMatcher(args []string) (patternMatcher, error) {
|
|
||||||
if len(args) != 1 {
|
|
||||||
return nil, fmt.Errorf("expected only one parameter - variable name, got: %s", args)
|
|
||||||
}
|
|
||||||
return &stringMatcher{name: args[0]}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringMatcher struct {
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stringMatcher) String() string {
|
|
||||||
return fmt.Sprintf("<string:%s>", s.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stringMatcher) getName() string {
|
|
||||||
return s.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stringMatcher) match(i *charIter) bool {
|
|
||||||
s.grabValue(i)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stringMatcher) equals(other patternMatcher) bool {
|
|
||||||
_, ok := other.(*stringMatcher)
|
|
||||||
return ok && other.getName() == s.getName()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stringMatcher) grabValue(i *charIter) {
|
|
||||||
for {
|
|
||||||
c, sep, ok := i.next()
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c == sep {
|
|
||||||
i.pushBack()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newIntMatcher(args []string) (patternMatcher, error) {
|
|
||||||
if len(args) != 1 {
|
|
||||||
return nil, fmt.Errorf("expected only one parameter - variable name, got: %s", args)
|
|
||||||
}
|
|
||||||
return &intMatcher{name: args[0]}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type intMatcher struct {
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *intMatcher) String() string {
|
|
||||||
return fmt.Sprintf("<int:%s>", s.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *intMatcher) getName() string {
|
|
||||||
return s.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *intMatcher) match(iter *charIter) bool {
|
|
||||||
// count stores amount of consumed characters so we know how many push
|
|
||||||
// backs to do in case there is no match
|
|
||||||
var count int
|
|
||||||
|
|
||||||
for {
|
|
||||||
c, sep, ok := iter.next()
|
|
||||||
count++
|
|
||||||
|
|
||||||
// if the current character is not a number:
|
|
||||||
// - it's either a separator that means it's a match
|
|
||||||
// - it's some other character that means it's not a match
|
|
||||||
if !unicode.IsDigit(rune(c)) {
|
|
||||||
if c == sep {
|
|
||||||
iter.pushBack()
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
iter.pushBack()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it's the end of the string, it's a match
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *intMatcher) equals(other patternMatcher) bool {
|
|
||||||
_, ok := other.(*intMatcher)
|
|
||||||
return ok && other.getName() == s.getName()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *trieNode) matchNode(i *charIter) bool {
|
|
||||||
if i.level() != e.level {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if e.isRoot() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if e.isPatternMatcher() {
|
|
||||||
return e.patternMatcher.match(i)
|
|
||||||
}
|
|
||||||
c, _, ok := i.next()
|
|
||||||
if !ok {
|
|
||||||
// we have reached the end
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if c != e.char {
|
|
||||||
// no match, so don't consume the character
|
|
||||||
i.pushBack()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *trieNode) match(i *charIter) *match {
|
|
||||||
if !e.matchNode(i) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// This is a leaf node and we are at the last character of the pattern
|
|
||||||
if len(e.matches) != 0 && i.isEnd() {
|
|
||||||
return e.matches[0]
|
|
||||||
}
|
|
||||||
// Check for the match in child nodes
|
|
||||||
for _, c := range e.children {
|
|
||||||
p := i.position()
|
|
||||||
if match := c.match(i); match != nil {
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
i.setPosition(p)
|
|
||||||
}
|
|
||||||
// Child nodes did not match and we at the boundary
|
|
||||||
if len(e.matches) != 0 && i.level() > e.level {
|
|
||||||
return e.matches[0]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// printTrie is useful for debugging and test purposes, it outputs the formatted
|
|
||||||
// represenation of the trie
|
|
||||||
func printTrie(t *trie) string {
|
|
||||||
return printTrieNode(t.root)
|
|
||||||
}
|
|
||||||
|
|
||||||
func printTrieNode(e *trieNode) string {
|
|
||||||
out := &bytes.Buffer{}
|
|
||||||
printTrieNodeInner(out, e, 0)
|
|
||||||
return out.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func printTrieNodeInner(b *bytes.Buffer, e *trieNode, offset int) {
|
|
||||||
if offset == 0 {
|
|
||||||
fmt.Fprintf(b, "\n")
|
|
||||||
}
|
|
||||||
padding := strings.Repeat(" ", offset)
|
|
||||||
fmt.Fprintf(b, "%s%s\n", padding, e.String())
|
|
||||||
if len(e.children) != 0 {
|
|
||||||
for _, c := range e.children {
|
|
||||||
printTrieNodeInner(b, c, offset+1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
33
vendor/github.com/vulcand/route/utils.go
generated
vendored
33
vendor/github.com/vulcand/route/utils.go
generated
vendored
|
@ -1,33 +0,0 @@
|
||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RawPath returns escaped url path section
|
|
||||||
func rawPath(r *http.Request) string {
|
|
||||||
// If there are no escape symbols, don't extract raw path
|
|
||||||
if !strings.ContainsRune(r.RequestURI, '%') {
|
|
||||||
if len(r.URL.Path) == 0 {
|
|
||||||
return "/"
|
|
||||||
}
|
|
||||||
return r.URL.Path
|
|
||||||
}
|
|
||||||
path := r.RequestURI
|
|
||||||
if path == "" {
|
|
||||||
path = "/"
|
|
||||||
}
|
|
||||||
// This is absolute URI, split host and port
|
|
||||||
if strings.Contains(path, "://") {
|
|
||||||
vals := strings.SplitN(path, r.URL.Host, 2)
|
|
||||||
if len(vals) == 2 {
|
|
||||||
path = vals[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
idx := strings.IndexRune(path, '?')
|
|
||||||
if idx == -1 {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
return path[:idx]
|
|
||||||
}
|
|
201
vendor/github.com/vulcand/vulcand/LICENSE
generated
vendored
201
vendor/github.com/vulcand/vulcand/LICENSE
generated
vendored
|
@ -1,201 +0,0 @@
|
||||||
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.
|
|
13
vendor/github.com/vulcand/vulcand/conntracker/conntracker.go
generated
vendored
13
vendor/github.com/vulcand/vulcand/conntracker/conntracker.go
generated
vendored
|
@ -1,13 +0,0 @@
|
||||||
package conntracker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConnectionTracker interface {
|
|
||||||
RegisterStateChange(conn net.Conn, prev http.ConnState, cur http.ConnState)
|
|
||||||
Counts() ConnectionStats
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnectionStats map[http.ConnState]map[string]int64
|
|
20
vendor/github.com/vulcand/vulcand/main.go
generated
vendored
20
vendor/github.com/vulcand/vulcand/main.go
generated
vendored
|
@ -1,20 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/vulcand/vulcand/plugin/registry"
|
|
||||||
"github.com/vulcand/vulcand/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
|
||||||
if err := service.Run(registry.GetRegistry()); err != nil {
|
|
||||||
fmt.Printf("Service exited with error: %s\n", err)
|
|
||||||
os.Exit(255)
|
|
||||||
} else {
|
|
||||||
fmt.Println("Service exited gracefully")
|
|
||||||
}
|
|
||||||
}
|
|
7
vendor/github.com/vulcand/vulcand/plugin/cacheprovider/CacheProvider.go
generated
vendored
7
vendor/github.com/vulcand/vulcand/plugin/cacheprovider/CacheProvider.go
generated
vendored
|
@ -1,7 +0,0 @@
|
||||||
package cacheprovider
|
|
||||||
|
|
||||||
import "golang.org/x/crypto/acme/autocert"
|
|
||||||
|
|
||||||
type T interface {
|
|
||||||
GetAutoCertCache() autocert.Cache
|
|
||||||
}
|
|
71
vendor/github.com/vulcand/vulcand/plugin/cacheprovider/EtcdV2CacheProvider.go
generated
vendored
71
vendor/github.com/vulcand/vulcand/plugin/cacheprovider/EtcdV2CacheProvider.go
generated
vendored
|
@ -1,71 +0,0 @@
|
||||||
package cacheprovider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
etcd "github.com/coreos/etcd/client"
|
|
||||||
"golang.org/x/crypto/acme/autocert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewEtcdV2CacheProvider(kapi etcd.KeysAPI, vulcanPrefix string) T {
|
|
||||||
return &etcdv2CacheProvider{
|
|
||||||
kapi: kapi,
|
|
||||||
vulcanPrefix: vulcanPrefix,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type etcdv2CacheProvider struct {
|
|
||||||
kapi etcd.KeysAPI
|
|
||||||
vulcanPrefix string
|
|
||||||
autoCertCache autocert.Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *etcdv2CacheProvider) GetAutoCertCache() autocert.Cache {
|
|
||||||
if p.autoCertCache == nil {
|
|
||||||
p.autoCertCache = &etcdv2AutoCertCache{
|
|
||||||
kapi: p.kapi,
|
|
||||||
prefix: p.vulcanPrefix + "/autocert_cache/",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p.autoCertCache
|
|
||||||
}
|
|
||||||
|
|
||||||
type etcdv2AutoCertCache struct {
|
|
||||||
kapi etcd.KeysAPI
|
|
||||||
prefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns a certificate data for the specified key.
|
|
||||||
// If there's no such key, Get returns ErrCacheMiss.
|
|
||||||
func (ng *etcdv2AutoCertCache) Get(ctx context.Context, rawKey string) ([]byte, error) {
|
|
||||||
key := ng.normalized(rawKey)
|
|
||||||
r, err := ng.kapi.Get(ctx, key, &etcd.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if r.Node == nil {
|
|
||||||
return nil, autocert.ErrCacheMiss
|
|
||||||
}
|
|
||||||
return []byte(r.Node.Value), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put stores the data in the cache under the specified key.
|
|
||||||
// Inderlying implementations may use any data storage format,
|
|
||||||
// as long as the reverse operation, Get, results in the original data.
|
|
||||||
func (ng *etcdv2AutoCertCache) Put(ctx context.Context, rawKey string, data []byte) error {
|
|
||||||
key := ng.normalized(rawKey)
|
|
||||||
_, err := ng.kapi.Set(ctx, key, string(data), &etcd.SetOptions{})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes a certificate data from the cache under the specified key.
|
|
||||||
// If there's no such key in the cache, Delete returns nil.
|
|
||||||
func (ng *etcdv2AutoCertCache) Delete(ctx context.Context, rawKey string) error {
|
|
||||||
key := ng.normalized(rawKey)
|
|
||||||
_, err := ng.kapi.Delete(ctx, key, &etcd.DeleteOptions{})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *etcdv2AutoCertCache) normalized(key string) string {
|
|
||||||
return ng.prefix + key
|
|
||||||
}
|
|
76
vendor/github.com/vulcand/vulcand/plugin/cacheprovider/EtcdV3CacheProvider.go
generated
vendored
76
vendor/github.com/vulcand/vulcand/plugin/cacheprovider/EtcdV3CacheProvider.go
generated
vendored
|
@ -1,76 +0,0 @@
|
||||||
package cacheprovider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
etcd "github.com/coreos/etcd/clientv3"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"golang.org/x/crypto/acme/autocert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewEtcdV3CacheProvider(client *etcd.Client, vulcanPrefix string) T {
|
|
||||||
return &etcdv3CacheProvider{
|
|
||||||
client: client,
|
|
||||||
vulcanPrefix: vulcanPrefix,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type etcdv3CacheProvider struct {
|
|
||||||
client *etcd.Client
|
|
||||||
vulcanPrefix string
|
|
||||||
autoCertCache autocert.Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *etcdv3CacheProvider) GetAutoCertCache() autocert.Cache {
|
|
||||||
if p.autoCertCache == nil {
|
|
||||||
p.autoCertCache = &etcdv3AutoCertCache{
|
|
||||||
client: p.client,
|
|
||||||
prefix: p.vulcanPrefix + "/autocert_cache/",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p.autoCertCache
|
|
||||||
}
|
|
||||||
|
|
||||||
type etcdv3AutoCertCache struct {
|
|
||||||
client *etcd.Client
|
|
||||||
prefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns a certificate data for the specified key.
|
|
||||||
// If there's no such key, Get returns ErrCacheMiss.
|
|
||||||
func (ng *etcdv3AutoCertCache) Get(ctx context.Context, rawKey string) ([]byte, error) {
|
|
||||||
key := ng.normalize(rawKey)
|
|
||||||
r, err := ng.client.Get(ctx, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if r.Count == 0 {
|
|
||||||
return nil, autocert.ErrCacheMiss
|
|
||||||
}
|
|
||||||
if r.Count > 1 {
|
|
||||||
log.Errorf("Against all odds, multiple results returned from Etcd when looking for single key: %s. "+
|
|
||||||
"Returning the first one.", key)
|
|
||||||
}
|
|
||||||
return r.Kvs[0].Value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put stores the data in the cache under the specified key.
|
|
||||||
// Inderlying implementations may use any data storage format,
|
|
||||||
// as long as the reverse operation, Get, results in the original data.
|
|
||||||
func (ng *etcdv3AutoCertCache) Put(ctx context.Context, rawKey string, data []byte) error {
|
|
||||||
key := ng.normalize(rawKey)
|
|
||||||
_, err := ng.client.Put(ctx, key, string(data))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes a certificate data from the cache under the specified key.
|
|
||||||
// If there's no such key in the cache, Delete returns nil.
|
|
||||||
func (ng *etcdv3AutoCertCache) Delete(ctx context.Context, rawKey string) error {
|
|
||||||
key := ng.normalize(rawKey)
|
|
||||||
_, err := ng.client.Delete(ctx, key)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *etcdv3AutoCertCache) normalize(rawKey string) string {
|
|
||||||
return ng.prefix + rawKey
|
|
||||||
}
|
|
61
vendor/github.com/vulcand/vulcand/plugin/cacheprovider/MemCacheProvider.go
generated
vendored
61
vendor/github.com/vulcand/vulcand/plugin/cacheprovider/MemCacheProvider.go
generated
vendored
|
@ -1,61 +0,0 @@
|
||||||
package cacheprovider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/acme/autocert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewMemCacheProvider() T {
|
|
||||||
return &memCacheProvider{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type memCacheProvider struct {
|
|
||||||
autocertCache autocert.Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *memCacheProvider) GetAutoCertCache() autocert.Cache {
|
|
||||||
if p.autocertCache == nil {
|
|
||||||
p.autocertCache = &memAutoCertCache{
|
|
||||||
kv: make(map[string][]byte),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p.autocertCache
|
|
||||||
}
|
|
||||||
|
|
||||||
type memAutoCertCache struct {
|
|
||||||
kv map[string][]byte
|
|
||||||
mtx sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns a certificate data for the specified key.
|
|
||||||
// If there's no such key, Get returns ErrCacheMiss.
|
|
||||||
func (ng *memAutoCertCache) Get(ctx context.Context, key string) ([]byte, error) {
|
|
||||||
ng.mtx.Lock()
|
|
||||||
defer ng.mtx.Unlock()
|
|
||||||
val, ok := ng.kv[key]
|
|
||||||
if ok {
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
return nil, autocert.ErrCacheMiss
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put stores the data in the cache under the specified key.
|
|
||||||
// Inderlying implementations may use any data storage format,
|
|
||||||
// as long as the reverse operation, Get, results in the original data.
|
|
||||||
func (ng *memAutoCertCache) Put(ctx context.Context, key string, data []byte) error {
|
|
||||||
ng.mtx.Lock()
|
|
||||||
defer ng.mtx.Unlock()
|
|
||||||
ng.kv[key] = data
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes a certificate data from the cache under the specified key.
|
|
||||||
// If there's no such key in the cache, Delete returns nil.
|
|
||||||
func (ng *memAutoCertCache) Delete(ctx context.Context, key string) error {
|
|
||||||
ng.mtx.Lock()
|
|
||||||
defer ng.mtx.Unlock()
|
|
||||||
delete(ng.kv, key)
|
|
||||||
return nil
|
|
||||||
}
|
|
11
vendor/github.com/vulcand/vulcand/plugin/cacheprovider/NoOpCacheProvider.go
generated
vendored
11
vendor/github.com/vulcand/vulcand/plugin/cacheprovider/NoOpCacheProvider.go
generated
vendored
|
@ -1,11 +0,0 @@
|
||||||
package cacheprovider
|
|
||||||
|
|
||||||
import "golang.org/x/crypto/acme/autocert"
|
|
||||||
|
|
||||||
type noOpCacheProvider struct{}
|
|
||||||
|
|
||||||
func (*noOpCacheProvider) GetAutoCertCache() autocert.Cache { return nil }
|
|
||||||
|
|
||||||
func NoOp() T {
|
|
||||||
return &noOpCacheProvider{}
|
|
||||||
}
|
|
178
vendor/github.com/vulcand/vulcand/plugin/middleware.go
generated
vendored
178
vendor/github.com/vulcand/vulcand/plugin/middleware.go
generated
vendored
|
@ -1,178 +0,0 @@
|
||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
"github.com/vulcand/oxy/forward"
|
|
||||||
"github.com/vulcand/oxy/roundrobin"
|
|
||||||
"github.com/vulcand/route"
|
|
||||||
"github.com/vulcand/vulcand/conntracker"
|
|
||||||
"github.com/vulcand/vulcand/plugin/cacheprovider"
|
|
||||||
"github.com/vulcand/vulcand/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Middleware specification, used to construct new middlewares and plug them into CLI API and backends
|
|
||||||
type MiddlewareSpec struct {
|
|
||||||
Type string
|
|
||||||
// Reader function that returns a middleware from another middleware structure
|
|
||||||
FromOther interface{}
|
|
||||||
// Flags for CLI tool to generate interface
|
|
||||||
CliFlags []cli.Flag
|
|
||||||
// Function that construtcs a middleware from CLI parameters
|
|
||||||
FromCli CliReader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms *MiddlewareSpec) FromJSON(data []byte) (Middleware, error) {
|
|
||||||
// Get a function's type
|
|
||||||
fnType := reflect.TypeOf(ms.FromOther)
|
|
||||||
|
|
||||||
// Create a pointer to the function's first argument
|
|
||||||
ptr := reflect.New(fnType.In(0)).Interface()
|
|
||||||
err := json.Unmarshal(data, &ptr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode %T from JSON, error: %s", ptr, err)
|
|
||||||
}
|
|
||||||
// Now let's call the function to produce a middleware
|
|
||||||
fnVal := reflect.ValueOf(ms.FromOther)
|
|
||||||
results := fnVal.Call([]reflect.Value{reflect.ValueOf(ptr).Elem()})
|
|
||||||
|
|
||||||
m, out := results[0].Interface(), results[1].Interface()
|
|
||||||
if out != nil {
|
|
||||||
return nil, out.(error)
|
|
||||||
}
|
|
||||||
return m.(Middleware), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Middleware interface {
|
|
||||||
NewHandler(http.Handler) (http.Handler, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reader constructs the middleware from the CLI interface
|
|
||||||
type CliReader func(c *cli.Context) (Middleware, error)
|
|
||||||
|
|
||||||
// Function that returns middleware spec by it's type
|
|
||||||
type SpecGetter func(string) *MiddlewareSpec
|
|
||||||
|
|
||||||
// Holds a bunch of Listeners a frontend might have.
|
|
||||||
// This allows callers to consolidate all their listeners in one convenient struct.
|
|
||||||
type FrontendListeners struct {
|
|
||||||
ConnTck forward.UrlForwardingStateListener
|
|
||||||
RbRewriteListener roundrobin.RequestRewriteListener
|
|
||||||
RrRewriteListener roundrobin.RequestRewriteListener
|
|
||||||
}
|
|
||||||
|
|
||||||
// Registry contains currently registered middlewares and used to support pluggable middlewares across all modules of the vulcand
|
|
||||||
type Registry struct {
|
|
||||||
specs []*MiddlewareSpec
|
|
||||||
notFound Middleware
|
|
||||||
router router.Router
|
|
||||||
incomingConnectionTracker conntracker.ConnectionTracker
|
|
||||||
frontendListeners FrontendListeners
|
|
||||||
cacheProvider cacheprovider.T
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRegistry() *Registry {
|
|
||||||
return &Registry{
|
|
||||||
specs: []*MiddlewareSpec{},
|
|
||||||
router: route.NewMux(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) AddSpec(s *MiddlewareSpec) error {
|
|
||||||
if s == nil {
|
|
||||||
return fmt.Errorf("spec can not be nil")
|
|
||||||
}
|
|
||||||
if r.GetSpec(s.Type) != nil {
|
|
||||||
return fmt.Errorf("middleware of type %s already registered", s.Type)
|
|
||||||
}
|
|
||||||
if err := verifySignature(s.FromOther); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.specs = append(r.specs, s)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) GetSpec(middlewareType string) *MiddlewareSpec {
|
|
||||||
for _, s := range r.specs {
|
|
||||||
if s.Type == middlewareType {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) GetSpecs() []*MiddlewareSpec {
|
|
||||||
return r.specs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) AddNotFoundMiddleware(notFound Middleware) error {
|
|
||||||
r.notFound = notFound
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) GetNotFoundMiddleware() Middleware {
|
|
||||||
return r.notFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) SetRouter(router router.Router) error {
|
|
||||||
r.router = router
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) GetRouter() router.Router {
|
|
||||||
return r.router
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) SetIncomingConnectionTracker(connTracker conntracker.ConnectionTracker) error {
|
|
||||||
r.incomingConnectionTracker = connTracker
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) GetIncomingConnectionTracker() conntracker.ConnectionTracker {
|
|
||||||
return r.incomingConnectionTracker
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) GetFrontendListeners() FrontendListeners {
|
|
||||||
return r.frontendListeners
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) SetFrontendListeners(frontendListeners FrontendListeners) error {
|
|
||||||
r.frontendListeners = frontendListeners
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) GetCacheProvider() cacheprovider.T {
|
|
||||||
return r.cacheProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) SetCacheProvider(cacheprovider cacheprovider.T) error {
|
|
||||||
r.cacheProvider = cacheprovider
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifySignature(fn interface{}) error {
|
|
||||||
t := reflect.TypeOf(fn)
|
|
||||||
if t == nil || t.Kind() != reflect.Func {
|
|
||||||
return fmt.Errorf("expected function, got %s", t)
|
|
||||||
}
|
|
||||||
if t.NumIn() != 1 {
|
|
||||||
return fmt.Errorf("expected function with one input argument, got %d", t.NumIn())
|
|
||||||
}
|
|
||||||
if t.In(0).Kind() != reflect.Struct {
|
|
||||||
return fmt.Errorf("function argument should be struct, got %s", t.In(0).Kind())
|
|
||||||
}
|
|
||||||
if t.NumOut() != 2 {
|
|
||||||
return fmt.Errorf("function should return 2 values, got %d", t.NumOut())
|
|
||||||
}
|
|
||||||
if !t.Out(0).AssignableTo(reflect.TypeOf((*Middleware)(nil)).Elem()) {
|
|
||||||
return fmt.Errorf("function first return value should be Middleware got, %s", t.Out(0))
|
|
||||||
}
|
|
||||||
if !t.Out(1).AssignableTo(reflect.TypeOf((*error)(nil)).Elem()) {
|
|
||||||
return fmt.Errorf("function second return value should be error got, %s", t.Out(1))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
205
vendor/github.com/vulcand/vulcand/plugin/rewrite/rewrite.go
generated
vendored
205
vendor/github.com/vulcand/vulcand/plugin/rewrite/rewrite.go
generated
vendored
|
@ -1,205 +0,0 @@
|
||||||
package rewrite
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/vulcand/oxy/utils"
|
|
||||||
"github.com/vulcand/vulcand/plugin"
|
|
||||||
)
|
|
||||||
|
|
||||||
const Type = "rewrite"
|
|
||||||
|
|
||||||
type Rewrite struct {
|
|
||||||
Regexp string
|
|
||||||
Replacement string
|
|
||||||
RewriteBody bool
|
|
||||||
Redirect bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRewrite(regex, replacement string, rewriteBody, redirect bool) (*Rewrite, error) {
|
|
||||||
return &Rewrite{regex, replacement, rewriteBody, redirect}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *Rewrite) NewHandler(next http.Handler) (http.Handler, error) {
|
|
||||||
return newRewriteHandler(next, rw)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *Rewrite) String() string {
|
|
||||||
return fmt.Sprintf("regexp=%v, replacement=%v, rewriteBody=%v, redirect=%v",
|
|
||||||
rw.Regexp, rw.Replacement, rw.RewriteBody, rw.Redirect)
|
|
||||||
}
|
|
||||||
|
|
||||||
type rewriteHandler struct {
|
|
||||||
next http.Handler
|
|
||||||
errHandler utils.ErrorHandler
|
|
||||||
regexp *regexp.Regexp
|
|
||||||
replacement string
|
|
||||||
rewriteBody bool
|
|
||||||
redirect bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRewriteHandler(next http.Handler, spec *Rewrite) (*rewriteHandler, error) {
|
|
||||||
re, err := regexp.Compile(spec.Regexp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &rewriteHandler{
|
|
||||||
regexp: re,
|
|
||||||
replacement: spec.Replacement,
|
|
||||||
rewriteBody: spec.RewriteBody,
|
|
||||||
redirect: spec.Redirect,
|
|
||||||
next: next,
|
|
||||||
errHandler: utils.DefaultHandler,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *rewriteHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
||||||
oldURL := rawURL(req)
|
|
||||||
|
|
||||||
// only continue if the Regexp param matches the URL
|
|
||||||
if !rw.regexp.MatchString(oldURL) {
|
|
||||||
rw.next.ServeHTTP(w, req)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply a rewrite regexp to the URL
|
|
||||||
newURL := rw.regexp.ReplaceAllString(oldURL, rw.replacement)
|
|
||||||
|
|
||||||
// replace any variables that may be in there
|
|
||||||
rewrittenURL := &bytes.Buffer{}
|
|
||||||
if err := ApplyString(newURL, rewrittenURL, req); err != nil {
|
|
||||||
rw.errHandler.ServeHTTP(w, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse the rewritten URL and replace request URL with it
|
|
||||||
parsedURL, err := url.Parse(rewrittenURL.String())
|
|
||||||
if err != nil {
|
|
||||||
rw.errHandler.ServeHTTP(w, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if rw.redirect && newURL != oldURL {
|
|
||||||
(&redirectHandler{u: parsedURL}).ServeHTTP(w, req)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.URL = parsedURL
|
|
||||||
|
|
||||||
// make sure the request URI corresponds the rewritten URL
|
|
||||||
req.RequestURI = req.URL.RequestURI()
|
|
||||||
|
|
||||||
if !rw.rewriteBody {
|
|
||||||
rw.next.ServeHTTP(w, req)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bw := &bufferWriter{header: make(http.Header), buffer: &bytes.Buffer{}}
|
|
||||||
newBody := &bytes.Buffer{}
|
|
||||||
|
|
||||||
rw.next.ServeHTTP(bw, req)
|
|
||||||
|
|
||||||
if err := Apply(bw.buffer, newBody, req); err != nil {
|
|
||||||
log.Errorf("While rewriting response body for '%s': %v", req.RequestURI, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
utils.CopyHeaders(w.Header(), bw.Header())
|
|
||||||
w.Header().Set("Content-Length", strconv.Itoa(newBody.Len()))
|
|
||||||
w.WriteHeader(bw.code)
|
|
||||||
io.Copy(w, newBody)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FromOther(rw Rewrite) (plugin.Middleware, error) {
|
|
||||||
return NewRewrite(rw.Regexp, rw.Replacement, rw.RewriteBody, rw.Redirect)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FromCli(c *cli.Context) (plugin.Middleware, error) {
|
|
||||||
return NewRewrite(c.String("regexp"), c.String("replacement"), c.Bool("rewriteBody"), c.Bool("redirect"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSpec() *plugin.MiddlewareSpec {
|
|
||||||
return &plugin.MiddlewareSpec{
|
|
||||||
Type: Type,
|
|
||||||
FromOther: FromOther,
|
|
||||||
FromCli: FromCli,
|
|
||||||
CliFlags: CliFlags(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func CliFlags() []cli.Flag {
|
|
||||||
return []cli.Flag{
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "regexp",
|
|
||||||
Usage: "regex to match against http request path",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "replacement",
|
|
||||||
Usage: "replacement text into which regex expansions are inserted",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "rewriteBody",
|
|
||||||
Usage: "if provided, response body is treated as as template and all variables in it are replaced",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "redirect",
|
|
||||||
Usage: "if provided, request is redirected to the rewritten URL",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func rawURL(request *http.Request) string {
|
|
||||||
scheme := "http"
|
|
||||||
if request.TLS != nil || isXForwardedHTTPS(request) {
|
|
||||||
scheme = "https"
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join([]string{scheme, "://", request.Host, request.RequestURI}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func isXForwardedHTTPS(request *http.Request) bool {
|
|
||||||
xForwardedProto := request.Header.Get("X-Forwarded-Proto")
|
|
||||||
|
|
||||||
return len(xForwardedProto) > 0 && xForwardedProto == "https"
|
|
||||||
}
|
|
||||||
|
|
||||||
type redirectHandler struct {
|
|
||||||
u *url.URL
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *redirectHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
||||||
w.Header().Set("Location", f.u.String())
|
|
||||||
w.WriteHeader(http.StatusFound)
|
|
||||||
w.Write([]byte(http.StatusText(http.StatusFound)))
|
|
||||||
}
|
|
||||||
|
|
||||||
type bufferWriter struct {
|
|
||||||
header http.Header
|
|
||||||
code int
|
|
||||||
buffer *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bufferWriter) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bufferWriter) Header() http.Header {
|
|
||||||
return b.header
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bufferWriter) Write(buf []byte) (int, error) {
|
|
||||||
return b.buffer.Write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeader sets rw.Code.
|
|
||||||
func (b *bufferWriter) WriteHeader(code int) {
|
|
||||||
b.code = code
|
|
||||||
}
|
|
44
vendor/github.com/vulcand/vulcand/plugin/rewrite/template.go
generated
vendored
44
vendor/github.com/vulcand/vulcand/plugin/rewrite/template.go
generated
vendored
|
@ -1,44 +0,0 @@
|
||||||
package rewrite
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
// data represents template data that is available to use in templates.
|
|
||||||
type data struct {
|
|
||||||
Request *http.Request
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply reads a template string from the provided reader, applies variables
|
|
||||||
// from the provided request object to it and writes the result into
|
|
||||||
// the provided writer.
|
|
||||||
//
|
|
||||||
// Template is standard Go's http://golang.org/pkg/text/template/.
|
|
||||||
func Apply(in io.Reader, out io.Writer, request *http.Request) error {
|
|
||||||
body, err := ioutil.ReadAll(in)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ApplyString(string(body), out, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyString applies variables from the provided request object to the provided
|
|
||||||
// template string and writes the result into the provided writer.
|
|
||||||
//
|
|
||||||
// Template is standard Go's http://golang.org/pkg/text/template/.
|
|
||||||
func ApplyString(in string, out io.Writer, request *http.Request) error {
|
|
||||||
t, err := template.New("t").Parse(in)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = t.Execute(out, data{request}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
26
vendor/github.com/vulcand/vulcand/router/router.go
generated
vendored
26
vendor/github.com/vulcand/vulcand/router/router.go
generated
vendored
|
@ -1,26 +0,0 @@
|
||||||
package router
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
// This interface captures all routing functionality required by vulcan.
|
|
||||||
// The routing functionality mainly comes from "github.com/vulcand/route",
|
|
||||||
type Router interface {
|
|
||||||
|
|
||||||
// Sets the not-found handler (this handler is called when no other handlers/routes in the routing library match
|
|
||||||
SetNotFound(http.Handler) error
|
|
||||||
|
|
||||||
// Gets the not-found handler that is currently in use by this router.
|
|
||||||
GetNotFound() http.Handler
|
|
||||||
|
|
||||||
// Validates whether this is an acceptable route expression
|
|
||||||
IsValid(string) bool
|
|
||||||
|
|
||||||
// Adds a new route->handler combination. The route is a string which provides the routing expression. http.Handler is called when this expression matches a request.
|
|
||||||
Handle(string, http.Handler) error
|
|
||||||
|
|
||||||
// Removes a route. The http.Handler associated with it, will be discarded.
|
|
||||||
Remove(string) error
|
|
||||||
|
|
||||||
// ServiceHTTP is the http.Handler implementation that allows callers to route their calls to sub-http.Handlers based on route matches.
|
|
||||||
ServeHTTP(http.ResponseWriter, *http.Request)
|
|
||||||
}
|
|
1058
vendor/golang.org/x/crypto/acme/acme.go
generated
vendored
1058
vendor/golang.org/x/crypto/acme/acme.go
generated
vendored
File diff suppressed because it is too large
Load diff
973
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
973
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
|
@ -1,973 +0,0 @@
|
||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package autocert provides automatic access to certificates from Let's Encrypt
|
|
||||||
// and any other ACME-based CA.
|
|
||||||
//
|
|
||||||
// This package is a work in progress and makes no API stability promises.
|
|
||||||
package autocert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
mathrand "math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/acme"
|
|
||||||
)
|
|
||||||
|
|
||||||
// createCertRetryAfter is how much time to wait before removing a failed state
|
|
||||||
// entry due to an unsuccessful createCert call.
|
|
||||||
// This is a variable instead of a const for testing.
|
|
||||||
// TODO: Consider making it configurable or an exp backoff?
|
|
||||||
var createCertRetryAfter = time.Minute
|
|
||||||
|
|
||||||
// pseudoRand is safe for concurrent use.
|
|
||||||
var pseudoRand *lockedMathRand
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
src := mathrand.NewSource(timeNow().UnixNano())
|
|
||||||
pseudoRand = &lockedMathRand{rnd: mathrand.New(src)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcceptTOS is a Manager.Prompt function that always returns true to
|
|
||||||
// indicate acceptance of the CA's Terms of Service during account
|
|
||||||
// registration.
|
|
||||||
func AcceptTOS(tosURL string) bool { return true }
|
|
||||||
|
|
||||||
// HostPolicy specifies which host names the Manager is allowed to respond to.
|
|
||||||
// It returns a non-nil error if the host should be rejected.
|
|
||||||
// The returned error is accessible via tls.Conn.Handshake and its callers.
|
|
||||||
// See Manager's HostPolicy field and GetCertificate method docs for more details.
|
|
||||||
type HostPolicy func(ctx context.Context, host string) error
|
|
||||||
|
|
||||||
// HostWhitelist returns a policy where only the specified host names are allowed.
|
|
||||||
// Only exact matches are currently supported. Subdomains, regexp or wildcard
|
|
||||||
// will not match.
|
|
||||||
func HostWhitelist(hosts ...string) HostPolicy {
|
|
||||||
whitelist := make(map[string]bool, len(hosts))
|
|
||||||
for _, h := range hosts {
|
|
||||||
whitelist[h] = true
|
|
||||||
}
|
|
||||||
return func(_ context.Context, host string) error {
|
|
||||||
if !whitelist[host] {
|
|
||||||
return errors.New("acme/autocert: host not configured")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// defaultHostPolicy is used when Manager.HostPolicy is not set.
|
|
||||||
func defaultHostPolicy(context.Context, string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manager is a stateful certificate manager built on top of acme.Client.
|
|
||||||
// It obtains and refreshes certificates automatically using "tls-sni-01",
|
|
||||||
// "tls-sni-02" and "http-01" challenge types, as well as providing them
|
|
||||||
// to a TLS server via tls.Config.
|
|
||||||
//
|
|
||||||
// You must specify a cache implementation, such as DirCache,
|
|
||||||
// to reuse obtained certificates across program restarts.
|
|
||||||
// Otherwise your server is very likely to exceed the certificate
|
|
||||||
// issuer's request rate limits.
|
|
||||||
type Manager struct {
|
|
||||||
// Prompt specifies a callback function to conditionally accept a CA's Terms of Service (TOS).
|
|
||||||
// The registration may require the caller to agree to the CA's TOS.
|
|
||||||
// If so, Manager calls Prompt with a TOS URL provided by the CA. Prompt should report
|
|
||||||
// whether the caller agrees to the terms.
|
|
||||||
//
|
|
||||||
// To always accept the terms, the callers can use AcceptTOS.
|
|
||||||
Prompt func(tosURL string) bool
|
|
||||||
|
|
||||||
// Cache optionally stores and retrieves previously-obtained certificates.
|
|
||||||
// If nil, certs will only be cached for the lifetime of the Manager.
|
|
||||||
//
|
|
||||||
// Manager passes the Cache certificates data encoded in PEM, with private/public
|
|
||||||
// parts combined in a single Cache.Put call, private key first.
|
|
||||||
Cache Cache
|
|
||||||
|
|
||||||
// HostPolicy controls which domains the Manager will attempt
|
|
||||||
// to retrieve new certificates for. It does not affect cached certs.
|
|
||||||
//
|
|
||||||
// If non-nil, HostPolicy is called before requesting a new cert.
|
|
||||||
// If nil, all hosts are currently allowed. This is not recommended,
|
|
||||||
// as it opens a potential attack where clients connect to a server
|
|
||||||
// by IP address and pretend to be asking for an incorrect host name.
|
|
||||||
// Manager will attempt to obtain a certificate for that host, incorrectly,
|
|
||||||
// eventually reaching the CA's rate limit for certificate requests
|
|
||||||
// and making it impossible to obtain actual certificates.
|
|
||||||
//
|
|
||||||
// See GetCertificate for more details.
|
|
||||||
HostPolicy HostPolicy
|
|
||||||
|
|
||||||
// RenewBefore optionally specifies how early certificates should
|
|
||||||
// be renewed before they expire.
|
|
||||||
//
|
|
||||||
// If zero, they're renewed 30 days before expiration.
|
|
||||||
RenewBefore time.Duration
|
|
||||||
|
|
||||||
// Client is used to perform low-level operations, such as account registration
|
|
||||||
// and requesting new certificates.
|
|
||||||
// If Client is nil, a zero-value acme.Client is used with acme.LetsEncryptURL
|
|
||||||
// directory endpoint and a newly-generated ECDSA P-256 key.
|
|
||||||
//
|
|
||||||
// Mutating the field after the first call of GetCertificate method will have no effect.
|
|
||||||
Client *acme.Client
|
|
||||||
|
|
||||||
// Email optionally specifies a contact email address.
|
|
||||||
// This is used by CAs, such as Let's Encrypt, to notify about problems
|
|
||||||
// with issued certificates.
|
|
||||||
//
|
|
||||||
// If the Client's account key is already registered, Email is not used.
|
|
||||||
Email string
|
|
||||||
|
|
||||||
// ForceRSA makes the Manager generate certificates with 2048-bit RSA keys.
|
|
||||||
//
|
|
||||||
// If false, a default is used. Currently the default
|
|
||||||
// is EC-based keys using the P-256 curve.
|
|
||||||
ForceRSA bool
|
|
||||||
|
|
||||||
clientMu sync.Mutex
|
|
||||||
client *acme.Client // initialized by acmeClient method
|
|
||||||
|
|
||||||
stateMu sync.Mutex
|
|
||||||
state map[string]*certState // keyed by domain name
|
|
||||||
|
|
||||||
// renewal tracks the set of domains currently running renewal timers.
|
|
||||||
// It is keyed by domain name.
|
|
||||||
renewalMu sync.Mutex
|
|
||||||
renewal map[string]*domainRenewal
|
|
||||||
|
|
||||||
// tokensMu guards the rest of the fields: tryHTTP01, certTokens and httpTokens.
|
|
||||||
tokensMu sync.RWMutex
|
|
||||||
// tryHTTP01 indicates whether the Manager should try "http-01" challenge type
|
|
||||||
// during the authorization flow.
|
|
||||||
tryHTTP01 bool
|
|
||||||
// httpTokens contains response body values for http-01 challenges
|
|
||||||
// and is keyed by the URL path at which a challenge response is expected
|
|
||||||
// to be provisioned.
|
|
||||||
// The entries are stored for the duration of the authorization flow.
|
|
||||||
httpTokens map[string][]byte
|
|
||||||
// certTokens contains temporary certificates for tls-sni challenges
|
|
||||||
// and is keyed by token domain name, which matches server name of ClientHello.
|
|
||||||
// Keys always have ".acme.invalid" suffix.
|
|
||||||
// The entries are stored for the duration of the authorization flow.
|
|
||||||
certTokens map[string]*tls.Certificate
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCertificate implements the tls.Config.GetCertificate hook.
|
|
||||||
// It provides a TLS certificate for hello.ServerName host, including answering
|
|
||||||
// *.acme.invalid (TLS-SNI) challenges. All other fields of hello are ignored.
|
|
||||||
//
|
|
||||||
// If m.HostPolicy is non-nil, GetCertificate calls the policy before requesting
|
|
||||||
// a new cert. A non-nil error returned from m.HostPolicy halts TLS negotiation.
|
|
||||||
// The error is propagated back to the caller of GetCertificate and is user-visible.
|
|
||||||
// This does not affect cached certs. See HostPolicy field description for more details.
|
|
||||||
func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
||||||
if m.Prompt == nil {
|
|
||||||
return nil, errors.New("acme/autocert: Manager.Prompt not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
name := hello.ServerName
|
|
||||||
if name == "" {
|
|
||||||
return nil, errors.New("acme/autocert: missing server name")
|
|
||||||
}
|
|
||||||
if !strings.Contains(strings.Trim(name, "."), ".") {
|
|
||||||
return nil, errors.New("acme/autocert: server name component count invalid")
|
|
||||||
}
|
|
||||||
if strings.ContainsAny(name, `/\`) {
|
|
||||||
return nil, errors.New("acme/autocert: server name contains invalid character")
|
|
||||||
}
|
|
||||||
|
|
||||||
// In the worst-case scenario, the timeout needs to account for caching, host policy,
|
|
||||||
// domain ownership verification and certificate issuance.
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// check whether this is a token cert requested for TLS-SNI challenge
|
|
||||||
if strings.HasSuffix(name, ".acme.invalid") {
|
|
||||||
m.tokensMu.RLock()
|
|
||||||
defer m.tokensMu.RUnlock()
|
|
||||||
if cert := m.certTokens[name]; cert != nil {
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
if cert, err := m.cacheGet(ctx, name); err == nil {
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
// TODO: cache error results?
|
|
||||||
return nil, fmt.Errorf("acme/autocert: no token cert for %q", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// regular domain
|
|
||||||
name = strings.TrimSuffix(name, ".") // golang.org/issue/18114
|
|
||||||
cert, err := m.cert(ctx, name)
|
|
||||||
if err == nil {
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
if err != ErrCacheMiss {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// first-time
|
|
||||||
if err := m.hostPolicy()(ctx, name); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cert, err = m.createCert(ctx, name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m.cachePut(ctx, name, cert)
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPHandler configures the Manager to provision ACME "http-01" challenge responses.
|
|
||||||
// It returns an http.Handler that responds to the challenges and must be
|
|
||||||
// running on port 80. If it receives a request that is not an ACME challenge,
|
|
||||||
// it delegates the request to the optional fallback handler.
|
|
||||||
//
|
|
||||||
// If fallback is nil, the returned handler redirects all GET and HEAD requests
|
|
||||||
// to the default TLS port 443 with 302 Found status code, preserving the original
|
|
||||||
// request path and query. It responds with 400 Bad Request to all other HTTP methods.
|
|
||||||
// The fallback is not protected by the optional HostPolicy.
|
|
||||||
//
|
|
||||||
// Because the fallback handler is run with unencrypted port 80 requests,
|
|
||||||
// the fallback should not serve TLS-only requests.
|
|
||||||
//
|
|
||||||
// If HTTPHandler is never called, the Manager will only use TLS SNI
|
|
||||||
// challenges for domain verification.
|
|
||||||
func (m *Manager) HTTPHandler(fallback http.Handler) http.Handler {
|
|
||||||
m.tokensMu.Lock()
|
|
||||||
defer m.tokensMu.Unlock()
|
|
||||||
m.tryHTTP01 = true
|
|
||||||
|
|
||||||
if fallback == nil {
|
|
||||||
fallback = http.HandlerFunc(handleHTTPRedirect)
|
|
||||||
}
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if !strings.HasPrefix(r.URL.Path, "/.well-known/acme-challenge/") {
|
|
||||||
fallback.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// A reasonable context timeout for cache and host policy only,
|
|
||||||
// because we don't wait for a new certificate issuance here.
|
|
||||||
ctx, cancel := context.WithTimeout(r.Context(), time.Minute)
|
|
||||||
defer cancel()
|
|
||||||
if err := m.hostPolicy()(ctx, r.Host); err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data, err := m.httpToken(ctx, r.URL.Path)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Write(data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleHTTPRedirect(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != "GET" && r.Method != "HEAD" {
|
|
||||||
http.Error(w, "Use HTTPS", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
target := "https://" + stripPort(r.Host) + r.URL.RequestURI()
|
|
||||||
http.Redirect(w, r, target, http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stripPort(hostport string) string {
|
|
||||||
host, _, err := net.SplitHostPort(hostport)
|
|
||||||
if err != nil {
|
|
||||||
return hostport
|
|
||||||
}
|
|
||||||
return net.JoinHostPort(host, "443")
|
|
||||||
}
|
|
||||||
|
|
||||||
// cert returns an existing certificate either from m.state or cache.
|
|
||||||
// If a certificate is found in cache but not in m.state, the latter will be filled
|
|
||||||
// with the cached value.
|
|
||||||
func (m *Manager) cert(ctx context.Context, name string) (*tls.Certificate, error) {
|
|
||||||
m.stateMu.Lock()
|
|
||||||
if s, ok := m.state[name]; ok {
|
|
||||||
m.stateMu.Unlock()
|
|
||||||
s.RLock()
|
|
||||||
defer s.RUnlock()
|
|
||||||
return s.tlscert()
|
|
||||||
}
|
|
||||||
defer m.stateMu.Unlock()
|
|
||||||
cert, err := m.cacheGet(ctx, name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
signer, ok := cert.PrivateKey.(crypto.Signer)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("acme/autocert: private key cannot sign")
|
|
||||||
}
|
|
||||||
if m.state == nil {
|
|
||||||
m.state = make(map[string]*certState)
|
|
||||||
}
|
|
||||||
s := &certState{
|
|
||||||
key: signer,
|
|
||||||
cert: cert.Certificate,
|
|
||||||
leaf: cert.Leaf,
|
|
||||||
}
|
|
||||||
m.state[name] = s
|
|
||||||
go m.renew(name, s.key, s.leaf.NotAfter)
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cacheGet always returns a valid certificate, or an error otherwise.
|
|
||||||
// If a cached certficate exists but is not valid, ErrCacheMiss is returned.
|
|
||||||
func (m *Manager) cacheGet(ctx context.Context, domain string) (*tls.Certificate, error) {
|
|
||||||
if m.Cache == nil {
|
|
||||||
return nil, ErrCacheMiss
|
|
||||||
}
|
|
||||||
data, err := m.Cache.Get(ctx, domain)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// private
|
|
||||||
priv, pub := pem.Decode(data)
|
|
||||||
if priv == nil || !strings.Contains(priv.Type, "PRIVATE") {
|
|
||||||
return nil, ErrCacheMiss
|
|
||||||
}
|
|
||||||
privKey, err := parsePrivateKey(priv.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// public
|
|
||||||
var pubDER [][]byte
|
|
||||||
for len(pub) > 0 {
|
|
||||||
var b *pem.Block
|
|
||||||
b, pub = pem.Decode(pub)
|
|
||||||
if b == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pubDER = append(pubDER, b.Bytes)
|
|
||||||
}
|
|
||||||
if len(pub) > 0 {
|
|
||||||
// Leftover content not consumed by pem.Decode. Corrupt. Ignore.
|
|
||||||
return nil, ErrCacheMiss
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify and create TLS cert
|
|
||||||
leaf, err := validCert(domain, pubDER, privKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, ErrCacheMiss
|
|
||||||
}
|
|
||||||
tlscert := &tls.Certificate{
|
|
||||||
Certificate: pubDER,
|
|
||||||
PrivateKey: privKey,
|
|
||||||
Leaf: leaf,
|
|
||||||
}
|
|
||||||
return tlscert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) cachePut(ctx context.Context, domain string, tlscert *tls.Certificate) error {
|
|
||||||
if m.Cache == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// contains PEM-encoded data
|
|
||||||
var buf bytes.Buffer
|
|
||||||
|
|
||||||
// private
|
|
||||||
switch key := tlscert.PrivateKey.(type) {
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
if err := encodeECDSAKey(&buf, key); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
b := x509.MarshalPKCS1PrivateKey(key)
|
|
||||||
pb := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: b}
|
|
||||||
if err := pem.Encode(&buf, pb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("acme/autocert: unknown private key type")
|
|
||||||
}
|
|
||||||
|
|
||||||
// public
|
|
||||||
for _, b := range tlscert.Certificate {
|
|
||||||
pb := &pem.Block{Type: "CERTIFICATE", Bytes: b}
|
|
||||||
if err := pem.Encode(&buf, pb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.Cache.Put(ctx, domain, buf.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeECDSAKey(w io.Writer, key *ecdsa.PrivateKey) error {
|
|
||||||
b, err := x509.MarshalECPrivateKey(key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pb := &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
|
|
||||||
return pem.Encode(w, pb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// createCert starts the domain ownership verification and returns a certificate
|
|
||||||
// for that domain upon success.
|
|
||||||
//
|
|
||||||
// If the domain is already being verified, it waits for the existing verification to complete.
|
|
||||||
// Either way, createCert blocks for the duration of the whole process.
|
|
||||||
func (m *Manager) createCert(ctx context.Context, domain string) (*tls.Certificate, error) {
|
|
||||||
// TODO: maybe rewrite this whole piece using sync.Once
|
|
||||||
state, err := m.certState(domain)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// state may exist if another goroutine is already working on it
|
|
||||||
// in which case just wait for it to finish
|
|
||||||
if !state.locked {
|
|
||||||
state.RLock()
|
|
||||||
defer state.RUnlock()
|
|
||||||
return state.tlscert()
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are the first; state is locked.
|
|
||||||
// Unblock the readers when domain ownership is verified
|
|
||||||
// and we got the cert or the process failed.
|
|
||||||
defer state.Unlock()
|
|
||||||
state.locked = false
|
|
||||||
|
|
||||||
der, leaf, err := m.authorizedCert(ctx, state.key, domain)
|
|
||||||
if err != nil {
|
|
||||||
// Remove the failed state after some time,
|
|
||||||
// making the manager call createCert again on the following TLS hello.
|
|
||||||
time.AfterFunc(createCertRetryAfter, func() {
|
|
||||||
defer testDidRemoveState(domain)
|
|
||||||
m.stateMu.Lock()
|
|
||||||
defer m.stateMu.Unlock()
|
|
||||||
// Verify the state hasn't changed and it's still invalid
|
|
||||||
// before deleting.
|
|
||||||
s, ok := m.state[domain]
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err := validCert(domain, s.cert, s.key); err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
delete(m.state, domain)
|
|
||||||
})
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
state.cert = der
|
|
||||||
state.leaf = leaf
|
|
||||||
go m.renew(domain, state.key, state.leaf.NotAfter)
|
|
||||||
return state.tlscert()
|
|
||||||
}
|
|
||||||
|
|
||||||
// certState returns a new or existing certState.
|
|
||||||
// If a new certState is returned, state.exist is false and the state is locked.
|
|
||||||
// The returned error is non-nil only in the case where a new state could not be created.
|
|
||||||
func (m *Manager) certState(domain string) (*certState, error) {
|
|
||||||
m.stateMu.Lock()
|
|
||||||
defer m.stateMu.Unlock()
|
|
||||||
if m.state == nil {
|
|
||||||
m.state = make(map[string]*certState)
|
|
||||||
}
|
|
||||||
// existing state
|
|
||||||
if state, ok := m.state[domain]; ok {
|
|
||||||
return state, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// new locked state
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
key crypto.Signer
|
|
||||||
)
|
|
||||||
if m.ForceRSA {
|
|
||||||
key, err = rsa.GenerateKey(rand.Reader, 2048)
|
|
||||||
} else {
|
|
||||||
key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
state := &certState{
|
|
||||||
key: key,
|
|
||||||
locked: true,
|
|
||||||
}
|
|
||||||
state.Lock() // will be unlocked by m.certState caller
|
|
||||||
m.state[domain] = state
|
|
||||||
return state, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// authorizedCert starts the domain ownership verification process and requests a new cert upon success.
|
|
||||||
// The key argument is the certificate private key.
|
|
||||||
func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, domain string) (der [][]byte, leaf *x509.Certificate, err error) {
|
|
||||||
client, err := m.acmeClient(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.verify(ctx, client, domain); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
csr, err := certRequest(key, domain)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
der, _, err = client.CreateCert(ctx, csr, 0, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
leaf, err = validCert(domain, der, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return der, leaf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify runs the identifier (domain) authorization flow
|
|
||||||
// using each applicable ACME challenge type.
|
|
||||||
func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string) error {
|
|
||||||
// The list of challenge types we'll try to fulfill
|
|
||||||
// in this specific order.
|
|
||||||
challengeTypes := []string{"tls-sni-02", "tls-sni-01"}
|
|
||||||
m.tokensMu.RLock()
|
|
||||||
if m.tryHTTP01 {
|
|
||||||
challengeTypes = append(challengeTypes, "http-01")
|
|
||||||
}
|
|
||||||
m.tokensMu.RUnlock()
|
|
||||||
|
|
||||||
var nextTyp int // challengeType index of the next challenge type to try
|
|
||||||
for {
|
|
||||||
// Start domain authorization and get the challenge.
|
|
||||||
authz, err := client.Authorize(ctx, domain)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// No point in accepting challenges if the authorization status
|
|
||||||
// is in a final state.
|
|
||||||
switch authz.Status {
|
|
||||||
case acme.StatusValid:
|
|
||||||
return nil // already authorized
|
|
||||||
case acme.StatusInvalid:
|
|
||||||
return fmt.Errorf("acme/autocert: invalid authorization %q", authz.URI)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pick the next preferred challenge.
|
|
||||||
var chal *acme.Challenge
|
|
||||||
for chal == nil && nextTyp < len(challengeTypes) {
|
|
||||||
chal = pickChallenge(challengeTypes[nextTyp], authz.Challenges)
|
|
||||||
nextTyp++
|
|
||||||
}
|
|
||||||
if chal == nil {
|
|
||||||
return fmt.Errorf("acme/autocert: unable to authorize %q; tried %q", domain, challengeTypes)
|
|
||||||
}
|
|
||||||
cleanup, err := m.fulfill(ctx, client, chal)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
defer cleanup()
|
|
||||||
if _, err := client.Accept(ctx, chal); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// A challenge is fulfilled and accepted: wait for the CA to validate.
|
|
||||||
if _, err := client.WaitAuthorization(ctx, authz.URI); err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fulfill provisions a response to the challenge chal.
|
|
||||||
// The cleanup is non-nil only if provisioning succeeded.
|
|
||||||
func (m *Manager) fulfill(ctx context.Context, client *acme.Client, chal *acme.Challenge) (cleanup func(), err error) {
|
|
||||||
switch chal.Type {
|
|
||||||
case "tls-sni-01":
|
|
||||||
cert, name, err := client.TLSSNI01ChallengeCert(chal.Token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m.putCertToken(ctx, name, &cert)
|
|
||||||
return func() { go m.deleteCertToken(name) }, nil
|
|
||||||
case "tls-sni-02":
|
|
||||||
cert, name, err := client.TLSSNI02ChallengeCert(chal.Token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m.putCertToken(ctx, name, &cert)
|
|
||||||
return func() { go m.deleteCertToken(name) }, nil
|
|
||||||
case "http-01":
|
|
||||||
resp, err := client.HTTP01ChallengeResponse(chal.Token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p := client.HTTP01ChallengePath(chal.Token)
|
|
||||||
m.putHTTPToken(ctx, p, resp)
|
|
||||||
return func() { go m.deleteHTTPToken(p) }, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("acme/autocert: unknown challenge type %q", chal.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
func pickChallenge(typ string, chal []*acme.Challenge) *acme.Challenge {
|
|
||||||
for _, c := range chal {
|
|
||||||
if c.Type == typ {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// putCertToken stores the cert under the named key in both m.certTokens map
|
|
||||||
// and m.Cache.
|
|
||||||
func (m *Manager) putCertToken(ctx context.Context, name string, cert *tls.Certificate) {
|
|
||||||
m.tokensMu.Lock()
|
|
||||||
defer m.tokensMu.Unlock()
|
|
||||||
if m.certTokens == nil {
|
|
||||||
m.certTokens = make(map[string]*tls.Certificate)
|
|
||||||
}
|
|
||||||
m.certTokens[name] = cert
|
|
||||||
m.cachePut(ctx, name, cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteCertToken removes the token certificate for the specified domain name
|
|
||||||
// from both m.certTokens map and m.Cache.
|
|
||||||
func (m *Manager) deleteCertToken(name string) {
|
|
||||||
m.tokensMu.Lock()
|
|
||||||
defer m.tokensMu.Unlock()
|
|
||||||
delete(m.certTokens, name)
|
|
||||||
if m.Cache != nil {
|
|
||||||
m.Cache.Delete(context.Background(), name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// httpToken retrieves an existing http-01 token value from an in-memory map
|
|
||||||
// or the optional cache.
|
|
||||||
func (m *Manager) httpToken(ctx context.Context, tokenPath string) ([]byte, error) {
|
|
||||||
m.tokensMu.RLock()
|
|
||||||
defer m.tokensMu.RUnlock()
|
|
||||||
if v, ok := m.httpTokens[tokenPath]; ok {
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
if m.Cache == nil {
|
|
||||||
return nil, fmt.Errorf("acme/autocert: no token at %q", tokenPath)
|
|
||||||
}
|
|
||||||
return m.Cache.Get(ctx, httpTokenCacheKey(tokenPath))
|
|
||||||
}
|
|
||||||
|
|
||||||
// putHTTPToken stores an http-01 token value using tokenPath as key
|
|
||||||
// in both in-memory map and the optional Cache.
|
|
||||||
//
|
|
||||||
// It ignores any error returned from Cache.Put.
|
|
||||||
func (m *Manager) putHTTPToken(ctx context.Context, tokenPath, val string) {
|
|
||||||
m.tokensMu.Lock()
|
|
||||||
defer m.tokensMu.Unlock()
|
|
||||||
if m.httpTokens == nil {
|
|
||||||
m.httpTokens = make(map[string][]byte)
|
|
||||||
}
|
|
||||||
b := []byte(val)
|
|
||||||
m.httpTokens[tokenPath] = b
|
|
||||||
if m.Cache != nil {
|
|
||||||
m.Cache.Put(ctx, httpTokenCacheKey(tokenPath), b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteHTTPToken removes an http-01 token value from both in-memory map
|
|
||||||
// and the optional Cache, ignoring any error returned from the latter.
|
|
||||||
//
|
|
||||||
// If m.Cache is non-nil, it blocks until Cache.Delete returns without a timeout.
|
|
||||||
func (m *Manager) deleteHTTPToken(tokenPath string) {
|
|
||||||
m.tokensMu.Lock()
|
|
||||||
defer m.tokensMu.Unlock()
|
|
||||||
delete(m.httpTokens, tokenPath)
|
|
||||||
if m.Cache != nil {
|
|
||||||
m.Cache.Delete(context.Background(), httpTokenCacheKey(tokenPath))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// httpTokenCacheKey returns a key at which an http-01 token value may be stored
|
|
||||||
// in the Manager's optional Cache.
|
|
||||||
func httpTokenCacheKey(tokenPath string) string {
|
|
||||||
return "http-01-" + path.Base(tokenPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// renew starts a cert renewal timer loop, one per domain.
|
|
||||||
//
|
|
||||||
// The loop is scheduled in two cases:
|
|
||||||
// - a cert was fetched from cache for the first time (wasn't in m.state)
|
|
||||||
// - a new cert was created by m.createCert
|
|
||||||
//
|
|
||||||
// The key argument is a certificate private key.
|
|
||||||
// The exp argument is the cert expiration time (NotAfter).
|
|
||||||
func (m *Manager) renew(domain string, key crypto.Signer, exp time.Time) {
|
|
||||||
m.renewalMu.Lock()
|
|
||||||
defer m.renewalMu.Unlock()
|
|
||||||
if m.renewal[domain] != nil {
|
|
||||||
// another goroutine is already on it
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if m.renewal == nil {
|
|
||||||
m.renewal = make(map[string]*domainRenewal)
|
|
||||||
}
|
|
||||||
dr := &domainRenewal{m: m, domain: domain, key: key}
|
|
||||||
m.renewal[domain] = dr
|
|
||||||
dr.start(exp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stopRenew stops all currently running cert renewal timers.
|
|
||||||
// The timers are not restarted during the lifetime of the Manager.
|
|
||||||
func (m *Manager) stopRenew() {
|
|
||||||
m.renewalMu.Lock()
|
|
||||||
defer m.renewalMu.Unlock()
|
|
||||||
for name, dr := range m.renewal {
|
|
||||||
delete(m.renewal, name)
|
|
||||||
dr.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) accountKey(ctx context.Context) (crypto.Signer, error) {
|
|
||||||
const keyName = "acme_account.key"
|
|
||||||
|
|
||||||
genKey := func() (*ecdsa.PrivateKey, error) {
|
|
||||||
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Cache == nil {
|
|
||||||
return genKey()
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := m.Cache.Get(ctx, keyName)
|
|
||||||
if err == ErrCacheMiss {
|
|
||||||
key, err := genKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := encodeECDSAKey(&buf, key); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := m.Cache.Put(ctx, keyName, buf.Bytes()); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
priv, _ := pem.Decode(data)
|
|
||||||
if priv == nil || !strings.Contains(priv.Type, "PRIVATE") {
|
|
||||||
return nil, errors.New("acme/autocert: invalid account key found in cache")
|
|
||||||
}
|
|
||||||
return parsePrivateKey(priv.Bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) acmeClient(ctx context.Context) (*acme.Client, error) {
|
|
||||||
m.clientMu.Lock()
|
|
||||||
defer m.clientMu.Unlock()
|
|
||||||
if m.client != nil {
|
|
||||||
return m.client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
client := m.Client
|
|
||||||
if client == nil {
|
|
||||||
client = &acme.Client{DirectoryURL: acme.LetsEncryptURL}
|
|
||||||
}
|
|
||||||
if client.Key == nil {
|
|
||||||
var err error
|
|
||||||
client.Key, err = m.accountKey(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var contact []string
|
|
||||||
if m.Email != "" {
|
|
||||||
contact = []string{"mailto:" + m.Email}
|
|
||||||
}
|
|
||||||
a := &acme.Account{Contact: contact}
|
|
||||||
_, err := client.Register(ctx, a, m.Prompt)
|
|
||||||
if ae, ok := err.(*acme.Error); err == nil || ok && ae.StatusCode == http.StatusConflict {
|
|
||||||
// conflict indicates the key is already registered
|
|
||||||
m.client = client
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
return m.client, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) hostPolicy() HostPolicy {
|
|
||||||
if m.HostPolicy != nil {
|
|
||||||
return m.HostPolicy
|
|
||||||
}
|
|
||||||
return defaultHostPolicy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) renewBefore() time.Duration {
|
|
||||||
if m.RenewBefore > renewJitter {
|
|
||||||
return m.RenewBefore
|
|
||||||
}
|
|
||||||
return 720 * time.Hour // 30 days
|
|
||||||
}
|
|
||||||
|
|
||||||
// certState is ready when its mutex is unlocked for reading.
|
|
||||||
type certState struct {
|
|
||||||
sync.RWMutex
|
|
||||||
locked bool // locked for read/write
|
|
||||||
key crypto.Signer // private key for cert
|
|
||||||
cert [][]byte // DER encoding
|
|
||||||
leaf *x509.Certificate // parsed cert[0]; always non-nil if cert != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// tlscert creates a tls.Certificate from s.key and s.cert.
|
|
||||||
// Callers should wrap it in s.RLock() and s.RUnlock().
|
|
||||||
func (s *certState) tlscert() (*tls.Certificate, error) {
|
|
||||||
if s.key == nil {
|
|
||||||
return nil, errors.New("acme/autocert: missing signer")
|
|
||||||
}
|
|
||||||
if len(s.cert) == 0 {
|
|
||||||
return nil, errors.New("acme/autocert: missing certificate")
|
|
||||||
}
|
|
||||||
return &tls.Certificate{
|
|
||||||
PrivateKey: s.key,
|
|
||||||
Certificate: s.cert,
|
|
||||||
Leaf: s.leaf,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// certRequest creates a certificate request for the given common name cn
|
|
||||||
// and optional SANs.
|
|
||||||
func certRequest(key crypto.Signer, cn string, san ...string) ([]byte, error) {
|
|
||||||
req := &x509.CertificateRequest{
|
|
||||||
Subject: pkix.Name{CommonName: cn},
|
|
||||||
DNSNames: san,
|
|
||||||
}
|
|
||||||
return x509.CreateCertificateRequest(rand.Reader, req, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates
|
|
||||||
// PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys.
|
|
||||||
// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three.
|
|
||||||
//
|
|
||||||
// Inspired by parsePrivateKey in crypto/tls/tls.go.
|
|
||||||
func parsePrivateKey(der []byte) (crypto.Signer, error) {
|
|
||||||
if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
|
|
||||||
switch key := key.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
return key, nil
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
return key, nil
|
|
||||||
default:
|
|
||||||
return nil, errors.New("acme/autocert: unknown private key type in PKCS#8 wrapping")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if key, err := x509.ParseECPrivateKey(der); err == nil {
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("acme/autocert: failed to parse private key")
|
|
||||||
}
|
|
||||||
|
|
||||||
// validCert parses a cert chain provided as der argument and verifies the leaf, der[0],
|
|
||||||
// corresponds to the private key, as well as the domain match and expiration dates.
|
|
||||||
// It doesn't do any revocation checking.
|
|
||||||
//
|
|
||||||
// The returned value is the verified leaf cert.
|
|
||||||
func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certificate, err error) {
|
|
||||||
// parse public part(s)
|
|
||||||
var n int
|
|
||||||
for _, b := range der {
|
|
||||||
n += len(b)
|
|
||||||
}
|
|
||||||
pub := make([]byte, n)
|
|
||||||
n = 0
|
|
||||||
for _, b := range der {
|
|
||||||
n += copy(pub[n:], b)
|
|
||||||
}
|
|
||||||
x509Cert, err := x509.ParseCertificates(pub)
|
|
||||||
if len(x509Cert) == 0 {
|
|
||||||
return nil, errors.New("acme/autocert: no public key found")
|
|
||||||
}
|
|
||||||
// verify the leaf is not expired and matches the domain name
|
|
||||||
leaf = x509Cert[0]
|
|
||||||
now := timeNow()
|
|
||||||
if now.Before(leaf.NotBefore) {
|
|
||||||
return nil, errors.New("acme/autocert: certificate is not valid yet")
|
|
||||||
}
|
|
||||||
if now.After(leaf.NotAfter) {
|
|
||||||
return nil, errors.New("acme/autocert: expired certificate")
|
|
||||||
}
|
|
||||||
if err := leaf.VerifyHostname(domain); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// ensure the leaf corresponds to the private key
|
|
||||||
switch pub := leaf.PublicKey.(type) {
|
|
||||||
case *rsa.PublicKey:
|
|
||||||
prv, ok := key.(*rsa.PrivateKey)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("acme/autocert: private key type does not match public key type")
|
|
||||||
}
|
|
||||||
if pub.N.Cmp(prv.N) != 0 {
|
|
||||||
return nil, errors.New("acme/autocert: private key does not match public key")
|
|
||||||
}
|
|
||||||
case *ecdsa.PublicKey:
|
|
||||||
prv, ok := key.(*ecdsa.PrivateKey)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("acme/autocert: private key type does not match public key type")
|
|
||||||
}
|
|
||||||
if pub.X.Cmp(prv.X) != 0 || pub.Y.Cmp(prv.Y) != 0 {
|
|
||||||
return nil, errors.New("acme/autocert: private key does not match public key")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, errors.New("acme/autocert: unknown public key algorithm")
|
|
||||||
}
|
|
||||||
return leaf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func retryAfter(v string) time.Duration {
|
|
||||||
if i, err := strconv.Atoi(v); err == nil {
|
|
||||||
return time.Duration(i) * time.Second
|
|
||||||
}
|
|
||||||
if t, err := http.ParseTime(v); err == nil {
|
|
||||||
return t.Sub(timeNow())
|
|
||||||
}
|
|
||||||
return time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
type lockedMathRand struct {
|
|
||||||
sync.Mutex
|
|
||||||
rnd *mathrand.Rand
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *lockedMathRand) int63n(max int64) int64 {
|
|
||||||
r.Lock()
|
|
||||||
n := r.rnd.Int63n(max)
|
|
||||||
r.Unlock()
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// For easier testing.
|
|
||||||
var (
|
|
||||||
timeNow = time.Now
|
|
||||||
|
|
||||||
// Called when a state is removed.
|
|
||||||
testDidRemoveState = func(domain string) {}
|
|
||||||
)
|
|
130
vendor/golang.org/x/crypto/acme/autocert/cache.go
generated
vendored
130
vendor/golang.org/x/crypto/acme/autocert/cache.go
generated
vendored
|
@ -1,130 +0,0 @@
|
||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package autocert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrCacheMiss is returned when a certificate is not found in cache.
|
|
||||||
var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss")
|
|
||||||
|
|
||||||
// Cache is used by Manager to store and retrieve previously obtained certificates
|
|
||||||
// as opaque data.
|
|
||||||
//
|
|
||||||
// The key argument of the methods refers to a domain name but need not be an FQDN.
|
|
||||||
// Cache implementations should not rely on the key naming pattern.
|
|
||||||
type Cache interface {
|
|
||||||
// Get returns a certificate data for the specified key.
|
|
||||||
// If there's no such key, Get returns ErrCacheMiss.
|
|
||||||
Get(ctx context.Context, key string) ([]byte, error)
|
|
||||||
|
|
||||||
// Put stores the data in the cache under the specified key.
|
|
||||||
// Underlying implementations may use any data storage format,
|
|
||||||
// as long as the reverse operation, Get, results in the original data.
|
|
||||||
Put(ctx context.Context, key string, data []byte) error
|
|
||||||
|
|
||||||
// Delete removes a certificate data from the cache under the specified key.
|
|
||||||
// If there's no such key in the cache, Delete returns nil.
|
|
||||||
Delete(ctx context.Context, key string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// DirCache implements Cache using a directory on the local filesystem.
|
|
||||||
// If the directory does not exist, it will be created with 0700 permissions.
|
|
||||||
type DirCache string
|
|
||||||
|
|
||||||
// Get reads a certificate data from the specified file name.
|
|
||||||
func (d DirCache) Get(ctx context.Context, name string) ([]byte, error) {
|
|
||||||
name = filepath.Join(string(d), name)
|
|
||||||
var (
|
|
||||||
data []byte
|
|
||||||
err error
|
|
||||||
done = make(chan struct{})
|
|
||||||
)
|
|
||||||
go func() {
|
|
||||||
data, err = ioutil.ReadFile(name)
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
case <-done:
|
|
||||||
}
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil, ErrCacheMiss
|
|
||||||
}
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put writes the certificate data to the specified file name.
|
|
||||||
// The file will be created with 0600 permissions.
|
|
||||||
func (d DirCache) Put(ctx context.Context, name string, data []byte) error {
|
|
||||||
if err := os.MkdirAll(string(d), 0700); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
var err error
|
|
||||||
go func() {
|
|
||||||
defer close(done)
|
|
||||||
var tmp string
|
|
||||||
if tmp, err = d.writeTempFile(name, data); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
// Don't overwrite the file if the context was canceled.
|
|
||||||
default:
|
|
||||||
newName := filepath.Join(string(d), name)
|
|
||||||
err = os.Rename(tmp, newName)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
case <-done:
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes the specified file name.
|
|
||||||
func (d DirCache) Delete(ctx context.Context, name string) error {
|
|
||||||
name = filepath.Join(string(d), name)
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
done = make(chan struct{})
|
|
||||||
)
|
|
||||||
go func() {
|
|
||||||
err = os.Remove(name)
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
case <-done:
|
|
||||||
}
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeTempFile writes b to a temporary file, closes the file and returns its path.
|
|
||||||
func (d DirCache) writeTempFile(prefix string, b []byte) (string, error) {
|
|
||||||
// TempFile uses 0600 permissions
|
|
||||||
f, err := ioutil.TempFile(string(d), prefix)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if _, err := f.Write(b); err != nil {
|
|
||||||
f.Close()
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return f.Name(), f.Close()
|
|
||||||
}
|
|
160
vendor/golang.org/x/crypto/acme/autocert/listener.go
generated
vendored
160
vendor/golang.org/x/crypto/acme/autocert/listener.go
generated
vendored
|
@ -1,160 +0,0 @@
|
||||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package autocert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewListener returns a net.Listener that listens on the standard TLS
|
|
||||||
// port (443) on all interfaces and returns *tls.Conn connections with
|
|
||||||
// LetsEncrypt certificates for the provided domain or domains.
|
|
||||||
//
|
|
||||||
// It enables one-line HTTPS servers:
|
|
||||||
//
|
|
||||||
// log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
|
|
||||||
//
|
|
||||||
// NewListener is a convenience function for a common configuration.
|
|
||||||
// More complex or custom configurations can use the autocert.Manager
|
|
||||||
// type instead.
|
|
||||||
//
|
|
||||||
// Use of this function implies acceptance of the LetsEncrypt Terms of
|
|
||||||
// Service. If domains is not empty, the provided domains are passed
|
|
||||||
// to HostWhitelist. If domains is empty, the listener will do
|
|
||||||
// LetsEncrypt challenges for any requested domain, which is not
|
|
||||||
// recommended.
|
|
||||||
//
|
|
||||||
// Certificates are cached in a "golang-autocert" directory under an
|
|
||||||
// operating system-specific cache or temp directory. This may not
|
|
||||||
// be suitable for servers spanning multiple machines.
|
|
||||||
//
|
|
||||||
// The returned listener uses a *tls.Config that enables HTTP/2, and
|
|
||||||
// should only be used with servers that support HTTP/2.
|
|
||||||
//
|
|
||||||
// The returned Listener also enables TCP keep-alives on the accepted
|
|
||||||
// connections. The returned *tls.Conn are returned before their TLS
|
|
||||||
// handshake has completed.
|
|
||||||
func NewListener(domains ...string) net.Listener {
|
|
||||||
m := &Manager{
|
|
||||||
Prompt: AcceptTOS,
|
|
||||||
}
|
|
||||||
if len(domains) > 0 {
|
|
||||||
m.HostPolicy = HostWhitelist(domains...)
|
|
||||||
}
|
|
||||||
dir := cacheDir()
|
|
||||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
|
||||||
log.Printf("warning: autocert.NewListener not using a cache: %v", err)
|
|
||||||
} else {
|
|
||||||
m.Cache = DirCache(dir)
|
|
||||||
}
|
|
||||||
return m.Listener()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listener listens on the standard TLS port (443) on all interfaces
|
|
||||||
// and returns a net.Listener returning *tls.Conn connections.
|
|
||||||
//
|
|
||||||
// The returned listener uses a *tls.Config that enables HTTP/2, and
|
|
||||||
// should only be used with servers that support HTTP/2.
|
|
||||||
//
|
|
||||||
// The returned Listener also enables TCP keep-alives on the accepted
|
|
||||||
// connections. The returned *tls.Conn are returned before their TLS
|
|
||||||
// handshake has completed.
|
|
||||||
//
|
|
||||||
// Unlike NewListener, it is the caller's responsibility to initialize
|
|
||||||
// the Manager m's Prompt, Cache, HostPolicy, and other desired options.
|
|
||||||
func (m *Manager) Listener() net.Listener {
|
|
||||||
ln := &listener{
|
|
||||||
m: m,
|
|
||||||
conf: &tls.Config{
|
|
||||||
GetCertificate: m.GetCertificate, // bonus: panic on nil m
|
|
||||||
NextProtos: []string{"h2", "http/1.1"}, // Enable HTTP/2
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443")
|
|
||||||
return ln
|
|
||||||
}
|
|
||||||
|
|
||||||
type listener struct {
|
|
||||||
m *Manager
|
|
||||||
conf *tls.Config
|
|
||||||
|
|
||||||
tcpListener net.Listener
|
|
||||||
tcpListenErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ln *listener) Accept() (net.Conn, error) {
|
|
||||||
if ln.tcpListenErr != nil {
|
|
||||||
return nil, ln.tcpListenErr
|
|
||||||
}
|
|
||||||
conn, err := ln.tcpListener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tcpConn := conn.(*net.TCPConn)
|
|
||||||
|
|
||||||
// Because Listener is a convenience function, help out with
|
|
||||||
// this too. This is not possible for the caller to set once
|
|
||||||
// we return a *tcp.Conn wrapping an inaccessible net.Conn.
|
|
||||||
// If callers don't want this, they can do things the manual
|
|
||||||
// way and tweak as needed. But this is what net/http does
|
|
||||||
// itself, so copy that. If net/http changes, we can change
|
|
||||||
// here too.
|
|
||||||
tcpConn.SetKeepAlive(true)
|
|
||||||
tcpConn.SetKeepAlivePeriod(3 * time.Minute)
|
|
||||||
|
|
||||||
return tls.Server(tcpConn, ln.conf), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ln *listener) Addr() net.Addr {
|
|
||||||
if ln.tcpListener != nil {
|
|
||||||
return ln.tcpListener.Addr()
|
|
||||||
}
|
|
||||||
// net.Listen failed. Return something non-nil in case callers
|
|
||||||
// call Addr before Accept:
|
|
||||||
return &net.TCPAddr{IP: net.IP{0, 0, 0, 0}, Port: 443}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ln *listener) Close() error {
|
|
||||||
if ln.tcpListenErr != nil {
|
|
||||||
return ln.tcpListenErr
|
|
||||||
}
|
|
||||||
return ln.tcpListener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func homeDir() string {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
|
|
||||||
}
|
|
||||||
if h := os.Getenv("HOME"); h != "" {
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
return "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
func cacheDir() string {
|
|
||||||
const base = "golang-autocert"
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "darwin":
|
|
||||||
return filepath.Join(homeDir(), "Library", "Caches", base)
|
|
||||||
case "windows":
|
|
||||||
for _, ev := range []string{"APPDATA", "CSIDL_APPDATA", "TEMP", "TMP"} {
|
|
||||||
if v := os.Getenv(ev); v != "" {
|
|
||||||
return filepath.Join(v, base)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Worst case:
|
|
||||||
return filepath.Join(homeDir(), base)
|
|
||||||
}
|
|
||||||
if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" {
|
|
||||||
return filepath.Join(xdg, base)
|
|
||||||
}
|
|
||||||
return filepath.Join(homeDir(), ".cache", base)
|
|
||||||
}
|
|
124
vendor/golang.org/x/crypto/acme/autocert/renewal.go
generated
vendored
124
vendor/golang.org/x/crypto/acme/autocert/renewal.go
generated
vendored
|
@ -1,124 +0,0 @@
|
||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package autocert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// renewJitter is the maximum deviation from Manager.RenewBefore.
|
|
||||||
const renewJitter = time.Hour
|
|
||||||
|
|
||||||
// domainRenewal tracks the state used by the periodic timers
|
|
||||||
// renewing a single domain's cert.
|
|
||||||
type domainRenewal struct {
|
|
||||||
m *Manager
|
|
||||||
domain string
|
|
||||||
key crypto.Signer
|
|
||||||
|
|
||||||
timerMu sync.Mutex
|
|
||||||
timer *time.Timer
|
|
||||||
}
|
|
||||||
|
|
||||||
// start starts a cert renewal timer at the time
|
|
||||||
// defined by the certificate expiration time exp.
|
|
||||||
//
|
|
||||||
// If the timer is already started, calling start is a noop.
|
|
||||||
func (dr *domainRenewal) start(exp time.Time) {
|
|
||||||
dr.timerMu.Lock()
|
|
||||||
defer dr.timerMu.Unlock()
|
|
||||||
if dr.timer != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dr.timer = time.AfterFunc(dr.next(exp), dr.renew)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stop stops the cert renewal timer.
|
|
||||||
// If the timer is already stopped, calling stop is a noop.
|
|
||||||
func (dr *domainRenewal) stop() {
|
|
||||||
dr.timerMu.Lock()
|
|
||||||
defer dr.timerMu.Unlock()
|
|
||||||
if dr.timer == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dr.timer.Stop()
|
|
||||||
dr.timer = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// renew is called periodically by a timer.
|
|
||||||
// The first renew call is kicked off by dr.start.
|
|
||||||
func (dr *domainRenewal) renew() {
|
|
||||||
dr.timerMu.Lock()
|
|
||||||
defer dr.timerMu.Unlock()
|
|
||||||
if dr.timer == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
|
||||||
defer cancel()
|
|
||||||
// TODO: rotate dr.key at some point?
|
|
||||||
next, err := dr.do(ctx)
|
|
||||||
if err != nil {
|
|
||||||
next = renewJitter / 2
|
|
||||||
next += time.Duration(pseudoRand.int63n(int64(next)))
|
|
||||||
}
|
|
||||||
dr.timer = time.AfterFunc(next, dr.renew)
|
|
||||||
testDidRenewLoop(next, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// do is similar to Manager.createCert but it doesn't lock a Manager.state item.
|
|
||||||
// Instead, it requests a new certificate independently and, upon success,
|
|
||||||
// replaces dr.m.state item with a new one and updates cache for the given domain.
|
|
||||||
//
|
|
||||||
// It may return immediately if the expiration date of the currently cached cert
|
|
||||||
// is far enough in the future.
|
|
||||||
//
|
|
||||||
// The returned value is a time interval after which the renewal should occur again.
|
|
||||||
func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
|
|
||||||
// a race is likely unavoidable in a distributed environment
|
|
||||||
// but we try nonetheless
|
|
||||||
if tlscert, err := dr.m.cacheGet(ctx, dr.domain); err == nil {
|
|
||||||
next := dr.next(tlscert.Leaf.NotAfter)
|
|
||||||
if next > dr.m.renewBefore()+renewJitter {
|
|
||||||
return next, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.domain)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
state := &certState{
|
|
||||||
key: dr.key,
|
|
||||||
cert: der,
|
|
||||||
leaf: leaf,
|
|
||||||
}
|
|
||||||
tlscert, err := state.tlscert()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
dr.m.cachePut(ctx, dr.domain, tlscert)
|
|
||||||
dr.m.stateMu.Lock()
|
|
||||||
defer dr.m.stateMu.Unlock()
|
|
||||||
// m.state is guaranteed to be non-nil at this point
|
|
||||||
dr.m.state[dr.domain] = state
|
|
||||||
return dr.next(leaf.NotAfter), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dr *domainRenewal) next(expiry time.Time) time.Duration {
|
|
||||||
d := expiry.Sub(timeNow()) - dr.m.renewBefore()
|
|
||||||
// add a bit of randomness to renew deadline
|
|
||||||
n := pseudoRand.int63n(int64(renewJitter))
|
|
||||||
d -= time.Duration(n)
|
|
||||||
if d < 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
var testDidRenewLoop = func(next time.Duration, err error) {}
|
|
153
vendor/golang.org/x/crypto/acme/jws.go
generated
vendored
153
vendor/golang.org/x/crypto/acme/jws.go
generated
vendored
|
@ -1,153 +0,0 @@
|
||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package acme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha256"
|
|
||||||
_ "crypto/sha512" // need for EC keys
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
// jwsEncodeJSON signs claimset using provided key and a nonce.
|
|
||||||
// The result is serialized in JSON format.
|
|
||||||
// See https://tools.ietf.org/html/rfc7515#section-7.
|
|
||||||
func jwsEncodeJSON(claimset interface{}, key crypto.Signer, nonce string) ([]byte, error) {
|
|
||||||
jwk, err := jwkEncode(key.Public())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
alg, sha := jwsHasher(key)
|
|
||||||
if alg == "" || !sha.Available() {
|
|
||||||
return nil, ErrUnsupportedKey
|
|
||||||
}
|
|
||||||
phead := fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q}`, alg, jwk, nonce)
|
|
||||||
phead = base64.RawURLEncoding.EncodeToString([]byte(phead))
|
|
||||||
cs, err := json.Marshal(claimset)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
payload := base64.RawURLEncoding.EncodeToString(cs)
|
|
||||||
hash := sha.New()
|
|
||||||
hash.Write([]byte(phead + "." + payload))
|
|
||||||
sig, err := jwsSign(key, sha, hash.Sum(nil))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
enc := struct {
|
|
||||||
Protected string `json:"protected"`
|
|
||||||
Payload string `json:"payload"`
|
|
||||||
Sig string `json:"signature"`
|
|
||||||
}{
|
|
||||||
Protected: phead,
|
|
||||||
Payload: payload,
|
|
||||||
Sig: base64.RawURLEncoding.EncodeToString(sig),
|
|
||||||
}
|
|
||||||
return json.Marshal(&enc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
|
|
||||||
// The result is also suitable for creating a JWK thumbprint.
|
|
||||||
// https://tools.ietf.org/html/rfc7517
|
|
||||||
func jwkEncode(pub crypto.PublicKey) (string, error) {
|
|
||||||
switch pub := pub.(type) {
|
|
||||||
case *rsa.PublicKey:
|
|
||||||
// https://tools.ietf.org/html/rfc7518#section-6.3.1
|
|
||||||
n := pub.N
|
|
||||||
e := big.NewInt(int64(pub.E))
|
|
||||||
// Field order is important.
|
|
||||||
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
|
||||||
return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
|
|
||||||
base64.RawURLEncoding.EncodeToString(e.Bytes()),
|
|
||||||
base64.RawURLEncoding.EncodeToString(n.Bytes()),
|
|
||||||
), nil
|
|
||||||
case *ecdsa.PublicKey:
|
|
||||||
// https://tools.ietf.org/html/rfc7518#section-6.2.1
|
|
||||||
p := pub.Curve.Params()
|
|
||||||
n := p.BitSize / 8
|
|
||||||
if p.BitSize%8 != 0 {
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
x := pub.X.Bytes()
|
|
||||||
if n > len(x) {
|
|
||||||
x = append(make([]byte, n-len(x)), x...)
|
|
||||||
}
|
|
||||||
y := pub.Y.Bytes()
|
|
||||||
if n > len(y) {
|
|
||||||
y = append(make([]byte, n-len(y)), y...)
|
|
||||||
}
|
|
||||||
// Field order is important.
|
|
||||||
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
|
||||||
return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`,
|
|
||||||
p.Name,
|
|
||||||
base64.RawURLEncoding.EncodeToString(x),
|
|
||||||
base64.RawURLEncoding.EncodeToString(y),
|
|
||||||
), nil
|
|
||||||
}
|
|
||||||
return "", ErrUnsupportedKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// jwsSign signs the digest using the given key.
|
|
||||||
// It returns ErrUnsupportedKey if the key type is unknown.
|
|
||||||
// The hash is used only for RSA keys.
|
|
||||||
func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
|
|
||||||
switch key := key.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
return key.Sign(rand.Reader, digest, hash)
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
r, s, err := ecdsa.Sign(rand.Reader, key, digest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rb, sb := r.Bytes(), s.Bytes()
|
|
||||||
size := key.Params().BitSize / 8
|
|
||||||
if size%8 > 0 {
|
|
||||||
size++
|
|
||||||
}
|
|
||||||
sig := make([]byte, size*2)
|
|
||||||
copy(sig[size-len(rb):], rb)
|
|
||||||
copy(sig[size*2-len(sb):], sb)
|
|
||||||
return sig, nil
|
|
||||||
}
|
|
||||||
return nil, ErrUnsupportedKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// jwsHasher indicates suitable JWS algorithm name and a hash function
|
|
||||||
// to use for signing a digest with the provided key.
|
|
||||||
// It returns ("", 0) if the key is not supported.
|
|
||||||
func jwsHasher(key crypto.Signer) (string, crypto.Hash) {
|
|
||||||
switch key := key.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
return "RS256", crypto.SHA256
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
switch key.Params().Name {
|
|
||||||
case "P-256":
|
|
||||||
return "ES256", crypto.SHA256
|
|
||||||
case "P-384":
|
|
||||||
return "ES384", crypto.SHA384
|
|
||||||
case "P-521":
|
|
||||||
return "ES512", crypto.SHA512
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// JWKThumbprint creates a JWK thumbprint out of pub
|
|
||||||
// as specified in https://tools.ietf.org/html/rfc7638.
|
|
||||||
func JWKThumbprint(pub crypto.PublicKey) (string, error) {
|
|
||||||
jwk, err := jwkEncode(pub)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
b := sha256.Sum256([]byte(jwk))
|
|
||||||
return base64.RawURLEncoding.EncodeToString(b[:]), nil
|
|
||||||
}
|
|
329
vendor/golang.org/x/crypto/acme/types.go
generated
vendored
329
vendor/golang.org/x/crypto/acme/types.go
generated
vendored
|
@ -1,329 +0,0 @@
|
||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package acme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/x509"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ACME server response statuses used to describe Authorization and Challenge states.
|
|
||||||
const (
|
|
||||||
StatusUnknown = "unknown"
|
|
||||||
StatusPending = "pending"
|
|
||||||
StatusProcessing = "processing"
|
|
||||||
StatusValid = "valid"
|
|
||||||
StatusInvalid = "invalid"
|
|
||||||
StatusRevoked = "revoked"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CRLReasonCode identifies the reason for a certificate revocation.
|
|
||||||
type CRLReasonCode int
|
|
||||||
|
|
||||||
// CRL reason codes as defined in RFC 5280.
|
|
||||||
const (
|
|
||||||
CRLReasonUnspecified CRLReasonCode = 0
|
|
||||||
CRLReasonKeyCompromise CRLReasonCode = 1
|
|
||||||
CRLReasonCACompromise CRLReasonCode = 2
|
|
||||||
CRLReasonAffiliationChanged CRLReasonCode = 3
|
|
||||||
CRLReasonSuperseded CRLReasonCode = 4
|
|
||||||
CRLReasonCessationOfOperation CRLReasonCode = 5
|
|
||||||
CRLReasonCertificateHold CRLReasonCode = 6
|
|
||||||
CRLReasonRemoveFromCRL CRLReasonCode = 8
|
|
||||||
CRLReasonPrivilegeWithdrawn CRLReasonCode = 9
|
|
||||||
CRLReasonAACompromise CRLReasonCode = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrUnsupportedKey is returned when an unsupported key type is encountered.
|
|
||||||
var ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported")
|
|
||||||
|
|
||||||
// Error is an ACME error, defined in Problem Details for HTTP APIs doc
|
|
||||||
// http://tools.ietf.org/html/draft-ietf-appsawg-http-problem.
|
|
||||||
type Error struct {
|
|
||||||
// StatusCode is The HTTP status code generated by the origin server.
|
|
||||||
StatusCode int
|
|
||||||
// ProblemType is a URI reference that identifies the problem type,
|
|
||||||
// typically in a "urn:acme:error:xxx" form.
|
|
||||||
ProblemType string
|
|
||||||
// Detail is a human-readable explanation specific to this occurrence of the problem.
|
|
||||||
Detail string
|
|
||||||
// Header is the original server error response headers.
|
|
||||||
// It may be nil.
|
|
||||||
Header http.Header
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
|
||||||
return fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthorizationError indicates that an authorization for an identifier
|
|
||||||
// did not succeed.
|
|
||||||
// It contains all errors from Challenge items of the failed Authorization.
|
|
||||||
type AuthorizationError struct {
|
|
||||||
// URI uniquely identifies the failed Authorization.
|
|
||||||
URI string
|
|
||||||
|
|
||||||
// Identifier is an AuthzID.Value of the failed Authorization.
|
|
||||||
Identifier string
|
|
||||||
|
|
||||||
// Errors is a collection of non-nil error values of Challenge items
|
|
||||||
// of the failed Authorization.
|
|
||||||
Errors []error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AuthorizationError) Error() string {
|
|
||||||
e := make([]string, len(a.Errors))
|
|
||||||
for i, err := range a.Errors {
|
|
||||||
e[i] = err.Error()
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; "))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RateLimit reports whether err represents a rate limit error and
|
|
||||||
// any Retry-After duration returned by the server.
|
|
||||||
//
|
|
||||||
// See the following for more details on rate limiting:
|
|
||||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-5.6
|
|
||||||
func RateLimit(err error) (time.Duration, bool) {
|
|
||||||
e, ok := err.(*Error)
|
|
||||||
if !ok {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
// Some CA implementations may return incorrect values.
|
|
||||||
// Use case-insensitive comparison.
|
|
||||||
if !strings.HasSuffix(strings.ToLower(e.ProblemType), ":ratelimited") {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
if e.Header == nil {
|
|
||||||
return 0, true
|
|
||||||
}
|
|
||||||
return retryAfter(e.Header.Get("Retry-After"), 0), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Account is a user account. It is associated with a private key.
|
|
||||||
type Account struct {
|
|
||||||
// URI is the account unique ID, which is also a URL used to retrieve
|
|
||||||
// account data from the CA.
|
|
||||||
URI string
|
|
||||||
|
|
||||||
// Contact is a slice of contact info used during registration.
|
|
||||||
Contact []string
|
|
||||||
|
|
||||||
// The terms user has agreed to.
|
|
||||||
// A value not matching CurrentTerms indicates that the user hasn't agreed
|
|
||||||
// to the actual Terms of Service of the CA.
|
|
||||||
AgreedTerms string
|
|
||||||
|
|
||||||
// Actual terms of a CA.
|
|
||||||
CurrentTerms string
|
|
||||||
|
|
||||||
// Authz is the authorization URL used to initiate a new authz flow.
|
|
||||||
Authz string
|
|
||||||
|
|
||||||
// Authorizations is a URI from which a list of authorizations
|
|
||||||
// granted to this account can be fetched via a GET request.
|
|
||||||
Authorizations string
|
|
||||||
|
|
||||||
// Certificates is a URI from which a list of certificates
|
|
||||||
// issued for this account can be fetched via a GET request.
|
|
||||||
Certificates string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Directory is ACME server discovery data.
|
|
||||||
type Directory struct {
|
|
||||||
// RegURL is an account endpoint URL, allowing for creating new
|
|
||||||
// and modifying existing accounts.
|
|
||||||
RegURL string
|
|
||||||
|
|
||||||
// AuthzURL is used to initiate Identifier Authorization flow.
|
|
||||||
AuthzURL string
|
|
||||||
|
|
||||||
// CertURL is a new certificate issuance endpoint URL.
|
|
||||||
CertURL string
|
|
||||||
|
|
||||||
// RevokeURL is used to initiate a certificate revocation flow.
|
|
||||||
RevokeURL string
|
|
||||||
|
|
||||||
// Term is a URI identifying the current terms of service.
|
|
||||||
Terms string
|
|
||||||
|
|
||||||
// Website is an HTTP or HTTPS URL locating a website
|
|
||||||
// providing more information about the ACME server.
|
|
||||||
Website string
|
|
||||||
|
|
||||||
// CAA consists of lowercase hostname elements, which the ACME server
|
|
||||||
// recognises as referring to itself for the purposes of CAA record validation
|
|
||||||
// as defined in RFC6844.
|
|
||||||
CAA []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Challenge encodes a returned CA challenge.
|
|
||||||
// Its Error field may be non-nil if the challenge is part of an Authorization
|
|
||||||
// with StatusInvalid.
|
|
||||||
type Challenge struct {
|
|
||||||
// Type is the challenge type, e.g. "http-01", "tls-sni-02", "dns-01".
|
|
||||||
Type string
|
|
||||||
|
|
||||||
// URI is where a challenge response can be posted to.
|
|
||||||
URI string
|
|
||||||
|
|
||||||
// Token is a random value that uniquely identifies the challenge.
|
|
||||||
Token string
|
|
||||||
|
|
||||||
// Status identifies the status of this challenge.
|
|
||||||
Status string
|
|
||||||
|
|
||||||
// Error indicates the reason for an authorization failure
|
|
||||||
// when this challenge was used.
|
|
||||||
// The type of a non-nil value is *Error.
|
|
||||||
Error error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authorization encodes an authorization response.
|
|
||||||
type Authorization struct {
|
|
||||||
// URI uniquely identifies a authorization.
|
|
||||||
URI string
|
|
||||||
|
|
||||||
// Status identifies the status of an authorization.
|
|
||||||
Status string
|
|
||||||
|
|
||||||
// Identifier is what the account is authorized to represent.
|
|
||||||
Identifier AuthzID
|
|
||||||
|
|
||||||
// Challenges that the client needs to fulfill in order to prove possession
|
|
||||||
// of the identifier (for pending authorizations).
|
|
||||||
// For final authorizations, the challenges that were used.
|
|
||||||
Challenges []*Challenge
|
|
||||||
|
|
||||||
// A collection of sets of challenges, each of which would be sufficient
|
|
||||||
// to prove possession of the identifier.
|
|
||||||
// Clients must complete a set of challenges that covers at least one set.
|
|
||||||
// Challenges are identified by their indices in the challenges array.
|
|
||||||
// If this field is empty, the client needs to complete all challenges.
|
|
||||||
Combinations [][]int
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthzID is an identifier that an account is authorized to represent.
|
|
||||||
type AuthzID struct {
|
|
||||||
Type string // The type of identifier, e.g. "dns".
|
|
||||||
Value string // The identifier itself, e.g. "example.org".
|
|
||||||
}
|
|
||||||
|
|
||||||
// wireAuthz is ACME JSON representation of Authorization objects.
|
|
||||||
type wireAuthz struct {
|
|
||||||
Status string
|
|
||||||
Challenges []wireChallenge
|
|
||||||
Combinations [][]int
|
|
||||||
Identifier struct {
|
|
||||||
Type string
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (z *wireAuthz) authorization(uri string) *Authorization {
|
|
||||||
a := &Authorization{
|
|
||||||
URI: uri,
|
|
||||||
Status: z.Status,
|
|
||||||
Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value},
|
|
||||||
Combinations: z.Combinations, // shallow copy
|
|
||||||
Challenges: make([]*Challenge, len(z.Challenges)),
|
|
||||||
}
|
|
||||||
for i, v := range z.Challenges {
|
|
||||||
a.Challenges[i] = v.challenge()
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func (z *wireAuthz) error(uri string) *AuthorizationError {
|
|
||||||
err := &AuthorizationError{
|
|
||||||
URI: uri,
|
|
||||||
Identifier: z.Identifier.Value,
|
|
||||||
}
|
|
||||||
for _, raw := range z.Challenges {
|
|
||||||
if raw.Error != nil {
|
|
||||||
err.Errors = append(err.Errors, raw.Error.error(nil))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// wireChallenge is ACME JSON challenge representation.
|
|
||||||
type wireChallenge struct {
|
|
||||||
URI string `json:"uri"`
|
|
||||||
Type string
|
|
||||||
Token string
|
|
||||||
Status string
|
|
||||||
Error *wireError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *wireChallenge) challenge() *Challenge {
|
|
||||||
v := &Challenge{
|
|
||||||
URI: c.URI,
|
|
||||||
Type: c.Type,
|
|
||||||
Token: c.Token,
|
|
||||||
Status: c.Status,
|
|
||||||
}
|
|
||||||
if v.Status == "" {
|
|
||||||
v.Status = StatusPending
|
|
||||||
}
|
|
||||||
if c.Error != nil {
|
|
||||||
v.Error = c.Error.error(nil)
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// wireError is a subset of fields of the Problem Details object
|
|
||||||
// as described in https://tools.ietf.org/html/rfc7807#section-3.1.
|
|
||||||
type wireError struct {
|
|
||||||
Status int
|
|
||||||
Type string
|
|
||||||
Detail string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *wireError) error(h http.Header) *Error {
|
|
||||||
return &Error{
|
|
||||||
StatusCode: e.Status,
|
|
||||||
ProblemType: e.Type,
|
|
||||||
Detail: e.Detail,
|
|
||||||
Header: h,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CertOption is an optional argument type for the TLSSNIxChallengeCert methods for
|
|
||||||
// customizing a temporary certificate for TLS-SNI challenges.
|
|
||||||
type CertOption interface {
|
|
||||||
privateCertOpt()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithKey creates an option holding a private/public key pair.
|
|
||||||
// The private part signs a certificate, and the public part represents the signee.
|
|
||||||
func WithKey(key crypto.Signer) CertOption {
|
|
||||||
return &certOptKey{key}
|
|
||||||
}
|
|
||||||
|
|
||||||
type certOptKey struct {
|
|
||||||
key crypto.Signer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*certOptKey) privateCertOpt() {}
|
|
||||||
|
|
||||||
// WithTemplate creates an option for specifying a certificate template.
|
|
||||||
// See x509.CreateCertificate for template usage details.
|
|
||||||
//
|
|
||||||
// In TLSSNIxChallengeCert methods, the template is also used as parent,
|
|
||||||
// resulting in a self-signed certificate.
|
|
||||||
// The DNSNames field of t is always overwritten for tls-sni challenge certs.
|
|
||||||
func WithTemplate(t *x509.Certificate) CertOption {
|
|
||||||
return (*certOptTemplate)(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
type certOptTemplate x509.Certificate
|
|
||||||
|
|
||||||
func (*certOptTemplate) privateCertOpt() {}
|
|
Loading…
Reference in a new issue