diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d438cdb0..5741a5a2a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,32 @@
# Change Log
+## [v1.5.0-rc3](https://github.com/containous/traefik/tree/v1.5.0-rc3) (2017-12-20)
+[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc2...v1.5.0-rc3)
+
+**Enhancements:**
+- **[docker,k8s,rancher]** Support regex redirect by frontend ([#2570](https://github.com/containous/traefik/pull/2570) by [ldez](https://github.com/ldez))
+
+**Bug fixes:**
+- **[acme,docker]** Modify ACME configuration migration into KV store ([#2598](https://github.com/containous/traefik/pull/2598) by [nmengin](https://github.com/nmengin))
+- **[consulcatalog]** Reload configuration when port change for one service ([#2574](https://github.com/containous/traefik/pull/2574) by [mmatur](https://github.com/mmatur))
+- **[consulcatalog]** Fix bad Træfik update on Consul Catalog ([#2573](https://github.com/containous/traefik/pull/2573) by [mmatur](https://github.com/mmatur))
+- **[k8s]** Add missing entrypoints template. ([#2594](https://github.com/containous/traefik/pull/2594) by [ldez](https://github.com/ldez))
+- **[kv]** Fix stickiness bug due to template syntax error ([#2591](https://github.com/containous/traefik/pull/2591) by [dahefanteng](https://github.com/dahefanteng))
+- **[marathon]** Update go-marathon ([#2585](https://github.com/containous/traefik/pull/2585) by [timoreimann](https://github.com/timoreimann))
+- **[mesos]** Mesos: Use slave.PID.Host as task SlaveIP. ([#2590](https://github.com/containous/traefik/pull/2590) by [nemosupremo](https://github.com/nemosupremo))
+- **[middleware]** Fix RawPath handling in addPrefix ([#2560](https://github.com/containous/traefik/pull/2560) by [risdenk](https://github.com/risdenk))
+- **[rules]** Add non regex pathPrefix ([#2592](https://github.com/containous/traefik/pull/2592) by [emilevauge](https://github.com/emilevauge))
+- **[servicefabric]** Fix backend name for Stateful services. (Service Fabric) ([#2559](https://github.com/containous/traefik/pull/2559) by [ldez](https://github.com/ldez))
+- **[servicefabric]** Fix isHealthy logic. ([#2577](https://github.com/containous/traefik/pull/2577) by [ldez](https://github.com/ldez))
+- **[zk]** Change Zookeeper default prefix. ([#2580](https://github.com/containous/traefik/pull/2580) by [ldez](https://github.com/ldez))
+- Fix frontend redirect ([#2544](https://github.com/containous/traefik/pull/2544) by [ldez](https://github.com/ldez))
+
+**Documentation:**
+- **[acme]** Improve documentation for Cloudflare API key ([#2558](https://github.com/containous/traefik/pull/2558) by [mmatur](https://github.com/mmatur))
+- Move rate limit documentation. ([#2588](https://github.com/containous/traefik/pull/2588) by [ldez](https://github.com/ldez))
+- Grammar ([#2562](https://github.com/containous/traefik/pull/2562) by [geraldcroes](https://github.com/geraldcroes))
+- Fix broken links and improve ResponseCodeRatio() description ([#2538](https://github.com/containous/traefik/pull/2538) by [mvasin](https://github.com/mvasin))
+
## [v1.5.0-rc2](https://github.com/containous/traefik/tree/v1.5.0-rc2) (2017-12-06)
[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc1...v1.5.0-rc2)
diff --git a/autogen/gentemplates/gen.go b/autogen/gentemplates/gen.go
index 2f41ada8e..9dd79a101 100644
--- a/autogen/gentemplates/gen.go
+++ b/autogen/gentemplates/gen.go
@@ -516,6 +516,9 @@ var _templatesKubernetesTmpl = []byte(`[backends]{{range $backendName, $backend
backend = "{{$frontend.Backend}}"
priority = {{$frontend.Priority}}
passHostHeader = {{$frontend.PassHostHeader}}
+ entryPoints = [{{range $frontend.EntryPoints}}
+ "{{.}}",
+ {{end}}]
basicAuth = [{{range $frontend.BasicAuth}}
"{{.}}",
{{end}}]
@@ -620,7 +623,7 @@ var _templatesKvTmpl = []byte(`{{$frontends := List .Prefix "/frontends/" }}
sticky = {{ getSticky . }}
{{if hasStickinessLabel $backend}}
[backends."{{$backendName}}".loadBalancer.stickiness]
- cookieName = {{getStickinessCookieName $backend}}
+ cookieName = "{{getStickinessCookieName $backend}}"
{{end}}
{{end}}
diff --git a/cmd/traefik/configuration.go b/cmd/traefik/configuration.go
index 692a956c4..dec67e328 100644
--- a/cmd/traefik/configuration.go
+++ b/cmd/traefik/configuration.go
@@ -112,7 +112,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
var defaultZookeeper zk.Provider
defaultZookeeper.Watch = true
defaultZookeeper.Endpoint = "127.0.0.1:2181"
- defaultZookeeper.Prefix = "/traefik"
+ defaultZookeeper.Prefix = "traefik"
defaultZookeeper.Constraints = types.Constraints{}
//default Boltdb
diff --git a/cmd/traefik/storeconfig.go b/cmd/traefik/storeconfig.go
index 2e1b1c7b2..1e4e40a86 100644
--- a/cmd/traefik/storeconfig.go
+++ b/cmd/traefik/storeconfig.go
@@ -67,12 +67,21 @@ func runStoreConfig(kv *staert.KvSource, traefikConfiguration *TraefikConfigurat
return err
}
}
- if traefikConfiguration.GlobalConfiguration.ACME != nil && len(traefikConfiguration.GlobalConfiguration.ACME.StorageFile) > 0 {
- // convert ACME json file to KV store
- localStore := acme.NewLocalStore(traefikConfiguration.GlobalConfiguration.ACME.StorageFile)
- object, err := localStore.Load()
- if err != nil {
- return err
+ if traefikConfiguration.GlobalConfiguration.ACME != nil {
+ var object cluster.Object
+ if len(traefikConfiguration.GlobalConfiguration.ACME.StorageFile) > 0 {
+ // convert ACME json file to KV store
+ localStore := acme.NewLocalStore(traefikConfiguration.GlobalConfiguration.ACME.StorageFile)
+ object, err = localStore.Load()
+ if err != nil {
+ return err
+ }
+
+ } else {
+ // Create an empty account to create all the keys into the KV store
+ account := &acme.Account{}
+ account.Init()
+ object = account
}
meta := cluster.NewMetadata(object)
@@ -89,6 +98,11 @@ func runStoreConfig(kv *staert.KvSource, traefikConfiguration *TraefikConfigurat
if err != nil {
return err
}
+ // Force to delete storagefile
+ err = kv.Delete(kv.Prefix + "/acme/storagefile")
+ if err != nil {
+ return err
+ }
}
return nil
}
diff --git a/docs/basics.md b/docs/basics.md
index 15d3f977b..9a84281a4 100644
--- a/docs/basics.md
+++ b/docs/basics.md
@@ -321,35 +321,6 @@ In this example, traffic routed through the first frontend will have the `X-Fram
!!! note
The detailed documentation for those security headers can be found in [unrolled/secure](https://github.com/unrolled/secure#available-options).
-#### Rate limiting
-
-Rate limiting can be configured per frontend.
-Multiple sets of rates can be added to each frontend, but the time periods must be unique.
-
-```toml
-[frontends]
- [frontends.frontend1]
- passHostHeader = true
- entrypoints = ["http"]
- backend = "backend1"
- [frontends.frontend1.routes.test_1]
- rule = "Path:/"
- [frontends.frontend1.ratelimit]
- extractorfunc = "client.ip"
- [frontends.frontend1.ratelimit.rateset.rateset1]
- period = "10s"
- average = 100
- burst = 200
- [frontends.frontend1.ratelimit.rateset.rateset2]
- period = "3s"
- average = 5
- burst = 10
-```
-
-In the above example, frontend1 is configured to limit requests by the client's ip address.
-An average of 5 requests every 3 seconds is allowed and an average of 100 requests every 10 seconds.
-These can "burst" up to 10 and 200 in each period respectively.
-
### Backends
A backend is responsible to load-balance the traffic coming from one or more frontends to a set of http servers.
diff --git a/docs/configuration/acme.md b/docs/configuration/acme.md
index 8308e7d41..05a105c1c 100644
--- a/docs/configuration/acme.md
+++ b/docs/configuration/acme.md
@@ -20,6 +20,12 @@ See also [Let's Encrypt examples](/user-guide/examples/#lets-encrypt-support) an
#
email = "test@traefik.io"
+# File used for certificates storage.
+#
+# Optional (Deprecated)
+#
+#storageFile = "acme.json"
+
# File or key used for certificates storage.
#
# Required
@@ -55,7 +61,7 @@ entryPoint = "https"
#
# acmeLogging = true
-# Enable on demand certificate.
+# Enable on demand certificate. (Deprecated)
#
# Optional
#
@@ -89,6 +95,10 @@ entryPoint = "https"
# main = "local4.com"
```
+!!! note
+ ACME entryPoint has to be relied to the port 443, otherwise ACME Challenges can not be done.
+ It's a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
+
### `storage`
```toml
@@ -100,7 +110,7 @@ storage = "acme.json"
File or key used for certificates storage.
-**WARNING** If you use Traefik in Docker, you have 2 options:
+**WARNING** If you use Træfik in Docker, you have 2 options:
- create a file on your host and mount it as a volume:
```toml
@@ -118,6 +128,14 @@ storage = "/etc/traefik/acme/acme.json"
docker run -v "/my/host/acme:/etc/traefik/acme" traefik
```
+!!! note
+ `storage` replaces `storageFile` which is deprecated.
+
+!!! note
+ During Træfik configuration migration from a configuration file to a KV store (thanks to `storeconfig` subcommand as described [here](/user-guide/kv-config/#store-configuration-in-key-value-store)), if ACME certificates have to be migrated too, use both `storageFile` and `storage`.
+ `storageFile` will contain the path to the `acme.json` file to migrate.
+ `storage` will contain the key where the certificates will be stored.
+
### `dnsProvider`
```toml
@@ -146,7 +164,7 @@ Select the provider that matches the DNS domain that will host the challenge TXT
| [GoDaddy](https://godaddy.com/domains) | `godaddy` | `GODADDY_API_KEY`, `GODADDY_API_SECRET` |
| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | `GCE_PROJECT`, `GCE_SERVICE_ACCOUNT_FILE` |
| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` |
-| manual | - | none, but run Traefik interactively & turn on `acmeLogging` to see instructions & press Enter. |
+| manual | - | none, but run Træfik interactively & turn on `acmeLogging` to see instructions & press Enter. |
| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` |
| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` |
| [Open Telekom Cloud](https://cloud.telekom.de/en/) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` |
@@ -171,7 +189,7 @@ If `delayDontCheckDNS` is greater than zero, avoid this & instead just wait so m
Useful if internal networks block external DNS queries.
-### `onDemand`
+### `onDemand` (Deprecated)
```toml
[acme]
@@ -188,7 +206,10 @@ This will request a certificate from Let's Encrypt during the first TLS handshak
TLS handshakes will be slow when requesting a hostname certificate for the first time, this can lead to DoS attacks.
!!! warning
- Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits)
+ Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
+
+!!! warning
+ This option is deprecated.
### `onHostRule`
@@ -238,7 +259,7 @@ main = "local4.com"
```
You can provide SANs (alternative domains) to each main domain.
-All domains must have A/AAAA records pointing to Traefik.
+All domains must have A/AAAA records pointing to Træfik.
!!! warning
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
diff --git a/docs/configuration/backends/zookeeper.md b/docs/configuration/backends/zookeeper.md
index 55b6cb88a..5d31ae3bc 100644
--- a/docs/configuration/backends/zookeeper.md
+++ b/docs/configuration/backends/zookeeper.md
@@ -27,9 +27,9 @@ watch = true
# Prefix used for KV store.
#
# Optional
-# Default: "/traefik"
+# Default: "traefik"
#
-prefix = "/traefik"
+prefix = "traefik"
# Override default configuration template.
# For advanced users :)
diff --git a/docs/configuration/commons.md b/docs/configuration/commons.md
index 69e167e86..0f97cc355 100644
--- a/docs/configuration/commons.md
+++ b/docs/configuration/commons.md
@@ -277,6 +277,36 @@ Custom error pages are easiest to implement using the file provider.
For dynamic providers, the corresponding template file needs to be customized accordingly and referenced in the Traefik configuration.
+## Rate limiting
+
+Rate limiting can be configured per frontend.
+Multiple sets of rates can be added to each frontend, but the time periods must be unique.
+
+```toml
+[frontends]
+ [frontends.frontend1]
+ passHostHeader = true
+ entrypoints = ["http"]
+ backend = "backend1"
+ [frontends.frontend1.routes.test_1]
+ rule = "Path:/"
+ [frontends.frontend1.ratelimit]
+ extractorfunc = "client.ip"
+ [frontends.frontend1.ratelimit.rateset.rateset1]
+ period = "10s"
+ average = 100
+ burst = 200
+ [frontends.frontend1.ratelimit.rateset.rateset2]
+ period = "3s"
+ average = 5
+ burst = 10
+```
+
+In the above example, frontend1 is configured to limit requests by the client's ip address.
+An average of 5 requests every 3 seconds is allowed and an average of 100 requests every 10 seconds.
+These can "burst" up to 10 and 200 in each period respectively.
+
+
## Retry Configuration
```toml
diff --git a/examples/cluster/docker-compose.yml b/examples/cluster/docker-compose.yml
index 2dd1b0b46..c45102874 100644
--- a/examples/cluster/docker-compose.yml
+++ b/examples/cluster/docker-compose.yml
@@ -38,16 +38,7 @@ services:
etcdctl-ping:
image: tenstartups/etcdctl
- command: --endpoints=[10.0.1.12:2379] get "traefik/acme/storagefile"
- environment:
- ETCDCTL_DIAL_: "TIMEOUT 10s"
- ETCDCTL_API : "3"
- networks:
- - net
-
- etcdctl-rm:
- image: tenstartups/etcdctl
- command: --endpoints=[10.0.1.12:2379] del "/traefik/acme/storagefile"
+ command: --endpoints=[10.0.1.12:2379] get "traefik/acme/storage"
environment:
ETCDCTL_DIAL_: "TIMEOUT 10s"
ETCDCTL_API : "3"
@@ -129,7 +120,6 @@ services:
image: containous/traefik
volumes:
- "./traefik.toml:/traefik.toml:ro"
- - "./acme.json:/acme.json:ro"
command: storeconfig --debug
networks:
- net
diff --git a/examples/cluster/manage_cluster_docker_environment.sh b/examples/cluster/manage_cluster_docker_environment.sh
index 9252d2a5e..7f1196157 100755
--- a/examples/cluster/manage_cluster_docker_environment.sh
+++ b/examples/cluster/manage_cluster_docker_environment.sh
@@ -32,15 +32,6 @@ delete_services() {
return 0
}
-# Init the environment : get IP address and create needed files
-init_acme_json() {
- echo "CREATE empty acme.json file"
- rm -f $basedir/acme.json && \
- touch $basedir/acme.json && \
- echo "{}" > $basedir/acme.json && \
- chmod 600 $basedir/acme.json # Needed for ACME
-}
-
start_consul() {
up_environment consul
waiting_counter=12
@@ -76,7 +67,6 @@ start_etcd3() {
}
start_storeconfig_consul() {
- init_acme_json
# Create traefik.toml with consul provider
cp $basedir/traefik.toml.tmpl $basedir/traefik.toml
echo '
@@ -85,29 +75,13 @@ start_storeconfig_consul() {
watch = true
prefix = "traefik"' >> $basedir/traefik.toml
up_environment traefik-storeconfig
- rm -f $basedir/traefik.toml && rm -f $basedir/acme.json
- # Delete acme-storage-file key
+ rm -f $basedir/traefik.toml
waiting_counter=5
- # Not start Traefik store config if consul is not started
- echo "Delete storage file key..."
- while [[ -z $(curl -s http://10.0.1.2:8500/v1/kv/traefik/acme/storagefile) && $waiting_counter -gt 0 ]]; do
- sleep 5
- let waiting_counter-=1
- done
- if [[ $waiting_counter -eq 0 ]]; then
- echo "[WARN] Unable to get storagefile key in consul"
- else
- curl -s --request DELETE http://10.0.1.2:8500/v1/kv/traefik/acme/storagefile
- ret=$1
- if [[ $ret -ne 0 ]]; then
- echo "[ERROR] Unable to delete storagefile key from consul kv."
- fi
- fi
+ delete_services traefik-storeconfig
}
start_storeconfig_etcd3() {
- init_acme_json
# Create traefik.toml with consul provider
cp $basedir/traefik.toml.tmpl $basedir/traefik.toml
echo '
@@ -117,20 +91,15 @@ start_storeconfig_etcd3() {
prefix = "/traefik"
useAPIV3 = true' >> $basedir/traefik.toml
up_environment traefik-storeconfig
- rm -f $basedir/traefik.toml && rm -f $basedir/acme.json
- # Delete acme-storage-file key
+ rm -f $basedir/traefik.toml
waiting_counter=5
- # Not start Traefik store config if consul is not started
+ # Don't start Traefik store config if ETCD3 is not started
echo "Delete storage file key..."
while [[ $(docker-compose -f $doc_file up --exit-code-from etcdctl-ping etcdctl-ping &>/dev/null) -ne 0 && $waiting_counter -gt 0 ]]; do
sleep 5
let waiting_counter-=1
done
- # Not start Traefik store config if consul is not started
- echo "Delete storage file key from ETCD3..."
-
- up_environment etcdctl-rm && \
- delete_services etcdctl-rm traefik-storeconfig etcdctl-ping
+ delete_services traefik-storeconfig etcdctl-ping
}
start_traefik() {
diff --git a/examples/cluster/traefik.toml.tmpl b/examples/cluster/traefik.toml.tmpl
index bfa09538c..2569e66d8 100644
--- a/examples/cluster/traefik.toml.tmpl
+++ b/examples/cluster/traefik.toml.tmpl
@@ -12,7 +12,6 @@ defaultEntryPoints = ["http", "https"]
[acme]
email = "test@traefik.io"
storage = "traefik/acme/account"
-storageFile = "/acme.json"
entryPoint = "https"
OnHostRule = true
caServer = "http://traefik.boulder.com:4000/directory"
diff --git a/glide.lock b/glide.lock
index 9a795d352..68eff15b4 100644
--- a/glide.lock
+++ b/glide.lock
@@ -1,4 +1,4 @@
-hash: f0d5ef854a4c115306c63c15320b595c29f715950eaf5f18418149886ecda400
+hash: d7f811ac4a011308c6e1f73b618215dee90dae6cace9511f66d4b63d916a337a
updated: 2017-12-15T10:34:41.246378337+01:00
imports:
- name: cloud.google.com/go
@@ -94,7 +94,7 @@ imports:
- name: github.com/containous/staert
version: af517d5b70db9c4b0505e0144fcc62b054057d2a
- name: github.com/containous/traefik-extra-service-fabric
- version: c01c1ef60ed612c5e42c1ceae0c6f92e67619cc3
+ version: ca1fb57108293caad285b1c366b763f6c6ab71c9
- name: github.com/coreos/bbolt
version: 3c6cbfb299c11444eb2f8c9d48f0d2ce09157423
- name: github.com/coreos/etcd
@@ -261,7 +261,7 @@ imports:
- name: github.com/fatih/color
version: 62e9147c64a1ed519147b62a56a14e83e2be02c1
- name: github.com/gambol99/go-marathon
- version: dd6cbd4c2d71294a19fb89158f2a00d427f174ab
+ version: 03b46169666c53b9cc953b875ac5714e5103e064
- name: github.com/ghodss/yaml
version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee
- name: github.com/go-ini/ini
diff --git a/glide.yaml b/glide.yaml
index b31ba43e5..e99a24497 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -12,7 +12,7 @@ import:
- package: github.com/cenk/backoff
- package: github.com/containous/flaeg
- package: github.com/containous/traefik-extra-service-fabric
- version: v1.0.4
+ version: v1.0.5
- package: github.com/vulcand/oxy
version: 7b6e758ab449705195df638765c4ca472248908a
repo: https://github.com/containous/oxy.git
diff --git a/provider/mesos/config.go b/provider/mesos/config.go
index eacde5ec6..87a739698 100644
--- a/provider/mesos/config.go
+++ b/provider/mesos/config.go
@@ -81,7 +81,7 @@ func taskRecords(st state.State) []state.Task {
for _, task := range f.Tasks {
for _, slave := range st.Slaves {
if task.SlaveID == slave.ID {
- task.SlaveIP = slave.Hostname
+ task.SlaveIP = slave.PID.Host
}
}
diff --git a/provider/mesos/config_test.go b/provider/mesos/config_test.go
index 47762dfa4..e03a35670 100644
--- a/provider/mesos/config_test.go
+++ b/provider/mesos/config_test.go
@@ -7,6 +7,7 @@ import (
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
+ "github.com/mesos/mesos-go/upid"
"github.com/mesosphere/mesos-dns/records/state"
"github.com/stretchr/testify/assert"
)
@@ -262,6 +263,9 @@ func TestTaskRecords(t *testing.T) {
ID: "s_id",
Hostname: "127.0.0.1",
}
+ slave.PID.UPID = &upid.UPID{}
+ slave.PID.Host = slave.Hostname
+
var taskState = state.State{
Slaves: []state.Slave{slave},
Frameworks: []state.Framework{framework},
diff --git a/server/rules.go b/server/rules.go
index 661b80224..be7f2b033 100644
--- a/server/rules.go
+++ b/server/rules.go
@@ -54,11 +54,32 @@ func (r *Rules) path(paths ...string) *mux.Route {
func (r *Rules) pathPrefix(paths ...string) *mux.Route {
router := r.route.route.Subrouter()
for _, path := range paths {
- router.PathPrefix(strings.TrimSpace(path))
+ buildPath(path, router)
}
return r.route.route
}
+func buildPath(path string, router *mux.Router) {
+ cleanPath := strings.TrimSpace(path)
+ // {} are used to define a regex pattern in http://www.gorillatoolkit.org/pkg/mux.
+ // if we find a { in the path, that means we use regex, then the gorilla/mux implementation is chosen
+ // otherwise, we use a lightweight implementation
+ if strings.Contains(cleanPath, "{") {
+ router.PathPrefix(cleanPath)
+ } else {
+ m := &prefixMatcher{prefix: cleanPath}
+ router.NewRoute().MatcherFunc(m.Match)
+ }
+}
+
+type prefixMatcher struct {
+ prefix string
+}
+
+func (m *prefixMatcher) Match(r *http.Request, _ *mux.RouteMatch) bool {
+ return strings.HasPrefix(r.URL.Path, m.prefix) || strings.HasPrefix(r.URL.Path, m.prefix+"/")
+}
+
type bySize []string
func (a bySize) Len() int { return len(a) }
@@ -111,7 +132,7 @@ func (r *Rules) pathPrefixStrip(paths ...string) *mux.Route {
r.route.stripPrefixes = paths
router := r.route.route.Subrouter()
for _, path := range paths {
- router.PathPrefix(strings.TrimSpace(path))
+ buildPath(path, router)
}
return r.route.route
}
diff --git a/server/rules_test.go b/server/rules_test.go
index f0ad65edd..7d3712289 100644
--- a/server/rules_test.go
+++ b/server/rules_test.go
@@ -192,3 +192,67 @@ type fakeHandler struct {
}
func (h *fakeHandler) ServeHTTP(http.ResponseWriter, *http.Request) {}
+
+func TestPathPrefix(t *testing.T) {
+ testCases := []struct {
+ desc string
+ path string
+ urls map[string]bool
+ }{
+ {
+ desc: "leading slash",
+ path: "/bar",
+ urls: map[string]bool{
+ "http://foo.com/bar": true,
+ "http://foo.com/bar/": true,
+ },
+ },
+ {
+ desc: "leading trailing slash",
+ path: "/bar/",
+ urls: map[string]bool{
+ "http://foo.com/bar": false,
+ "http://foo.com/bar/": true,
+ },
+ },
+ {
+ desc: "no slash",
+ path: "bar",
+ urls: map[string]bool{
+ "http://foo.com/bar": false,
+ "http://foo.com/bar/": false,
+ },
+ },
+ {
+ desc: "trailing slash",
+ path: "bar/",
+ urls: map[string]bool{
+ "http://foo.com/bar": false,
+ "http://foo.com/bar/": false,
+ },
+ },
+ }
+
+ for _, test := range testCases {
+ test := test
+ t.Run(test.desc, func(t *testing.T) {
+ t.Parallel()
+
+ rls := &Rules{
+ route: &serverRoute{
+ route: &mux.Route{},
+ },
+ }
+
+ rt := rls.pathPrefix(test.path)
+
+ for testURL, expectedMatch := range test.urls {
+ req := testhelpers.MustNewRequest(http.MethodGet, testURL, nil)
+ match := rt.Match(req, &mux.RouteMatch{})
+ if match != expectedMatch {
+ t.Errorf("Error matching %s with %s, got %v expected %v", test.path, testURL, match, expectedMatch)
+ }
+ }
+ })
+ }
+}
diff --git a/templates/kubernetes.tmpl b/templates/kubernetes.tmpl
index 305fd4f72..741088953 100644
--- a/templates/kubernetes.tmpl
+++ b/templates/kubernetes.tmpl
@@ -25,6 +25,9 @@
backend = "{{$frontend.Backend}}"
priority = {{$frontend.Priority}}
passHostHeader = {{$frontend.PassHostHeader}}
+ entryPoints = [{{range $frontend.EntryPoints}}
+ "{{.}}",
+ {{end}}]
basicAuth = [{{range $frontend.BasicAuth}}
"{{.}}",
{{end}}]
diff --git a/templates/kv.tmpl b/templates/kv.tmpl
index 09b04b683..08d353016 100644
--- a/templates/kv.tmpl
+++ b/templates/kv.tmpl
@@ -20,7 +20,7 @@
sticky = {{ getSticky . }}
{{if hasStickinessLabel $backend}}
[backends."{{$backendName}}".loadBalancer.stickiness]
- cookieName = {{getStickinessCookieName $backend}}
+ cookieName = "{{getStickinessCookieName $backend}}"
{{end}}
{{end}}
diff --git a/vendor/github.com/containous/traefik-extra-service-fabric/servicefabric.go b/vendor/github.com/containous/traefik-extra-service-fabric/servicefabric.go
index 8183d9480..c23153487 100644
--- a/vendor/github.com/containous/traefik-extra-service-fabric/servicefabric.go
+++ b/vendor/github.com/containous/traefik-extra-service-fabric/servicefabric.go
@@ -303,7 +303,7 @@ func isPrimary(instance replicaInstance) bool {
}
func isHealthy(instanceData *sf.ReplicaItemBase) bool {
- return instanceData != nil && (instanceData.ReplicaStatus == "Ready" || instanceData.HealthState != "Error")
+ return instanceData != nil && (instanceData.ReplicaStatus == "Ready" && instanceData.HealthState != "Error")
}
func hasHTTPEndpoint(instanceData *sf.ReplicaItemBase) bool {
diff --git a/vendor/github.com/gambol99/go-marathon/application.go b/vendor/github.com/gambol99/go-marathon/application.go
index aba8dc77c..fbb6dc1e6 100644
--- a/vendor/github.com/gambol99/go-marathon/application.go
+++ b/vendor/github.com/gambol99/go-marathon/application.go
@@ -1,5 +1,5 @@
/*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -56,15 +56,16 @@ type Port struct {
// Application is the definition for an application in marathon
type Application struct {
- ID string `json:"id,omitempty"`
- Cmd *string `json:"cmd,omitempty"`
- Args *[]string `json:"args,omitempty"`
- Constraints *[][]string `json:"constraints,omitempty"`
- Container *Container `json:"container,omitempty"`
- CPUs float64 `json:"cpus,omitempty"`
- GPUs *float64 `json:"gpus,omitempty"`
- Disk *float64 `json:"disk,omitempty"`
- Env *map[string]string `json:"env,omitempty"`
+ ID string `json:"id,omitempty"`
+ Cmd *string `json:"cmd,omitempty"`
+ Args *[]string `json:"args,omitempty"`
+ Constraints *[][]string `json:"constraints,omitempty"`
+ Container *Container `json:"container,omitempty"`
+ CPUs float64 `json:"cpus,omitempty"`
+ GPUs *float64 `json:"gpus,omitempty"`
+ Disk *float64 `json:"disk,omitempty"`
+ // Contains non-secret environment variables. Secrets environment variables are part of the Secrets map.
+ Env *map[string]string `json:"-"`
Executor *string `json:"executor,omitempty"`
HealthChecks *[]HealthCheck `json:"healthChecks,omitempty"`
ReadinessChecks *[]ReadinessCheck `json:"readinessChecks,omitempty"`
@@ -99,6 +100,8 @@ type Application struct {
LastTaskFailure *LastTaskFailure `json:"lastTaskFailure,omitempty"`
Fetch *[]Fetch `json:"fetch,omitempty"`
IPAddressPerTask *IPAddressPerTask `json:"ipAddress,omitempty"`
+ Residency *Residency `json:"residency,omitempty"`
+ Secrets *map[string]Secret `json:"-"`
}
// ApplicationVersions is a collection of application versions for a specific app in marathon
@@ -149,6 +152,14 @@ type Stats struct {
LifeTime map[string]float64 `json:"lifeTime"`
}
+// Secret is the environment variable and secret store path associated with a secret.
+// The value for EnvVar is populated from the env field, and Source is populated from
+// the secrets field of the application json.
+type Secret struct {
+ EnvVar string
+ Source string
+}
+
// SetIPAddressPerTask defines that the application will have a IP address defines by a external agent.
// This configuration is not allowed to be used with Port or PortDefinitions. Thus, the implementation
// clears both.
@@ -355,8 +366,8 @@ func (r *Application) EmptyLabels() *Application {
}
// AddEnv adds an environment variable to the application
-// name: the name of the variable
-// value: go figure, the value associated to the above
+// name: the name of the variable
+// value: go figure, the value associated to the above
func (r *Application) AddEnv(name, value string) *Application {
if r.Env == nil {
r.EmptyEnvs()
@@ -375,6 +386,28 @@ func (r *Application) EmptyEnvs() *Application {
return r
}
+// AddSecret adds a secret declaration
+// envVar: the name of the environment variable
+// name: the name of the secret
+// source: the source ID of the secret
+func (r *Application) AddSecret(envVar, name, source string) *Application {
+ if r.Secrets == nil {
+ r.EmptySecrets()
+ }
+ (*r.Secrets)[name] = Secret{EnvVar: envVar, Source: source}
+
+ return r
+}
+
+// EmptySecrets explicitly empties the secrets -- use this if you need to empty
+// the secrets of an application that already has secrets set (setting secrets to nil will
+// keep the current value)
+func (r *Application) EmptySecrets() *Application {
+ r.Secrets = &map[string]Secret{}
+
+ return r
+}
+
// SetExecutor sets the executor
func (r *Application) SetExecutor(executor string) *Application {
r.Executor = &executor
@@ -571,6 +604,23 @@ func (r *Application) EmptyUnreachableStrategy() *Application {
return r
}
+// SetResidency sets behavior for resident applications, an application is resident when
+// it has local persistent volumes set
+func (r *Application) SetResidency(whenLost TaskLostBehaviorType) *Application {
+ r.Residency = &Residency{
+ TaskLostBehavior: whenLost,
+ }
+ return r
+}
+
+// EmptyResidency explicitly empties the residency -- use this if
+// you need to empty the residency of an application that already has
+// the residency set (setting it to nil will keep the current value).
+func (r *Application) EmptyResidency() *Application {
+ r.Residency = &Residency{}
+ return r
+}
+
// String returns the json representation of this application
func (r *Application) String() string {
s, err := json.MarshalIndent(r, "", " ")
@@ -639,7 +689,7 @@ func (r *marathonClient) ApplicationVersions(name string) (*ApplicationVersions,
// name: the id used to identify the application
// version: the version (normally a timestamp) you wish to change to
func (r *marathonClient) SetApplicationVersion(name string, version *ApplicationVersion) (*DeploymentID, error) {
- path := fmt.Sprintf(buildPath(name))
+ path := buildPath(name)
deploymentID := new(DeploymentID)
if err := r.apiPut(path, version, deploymentID); err != nil {
return nil, err
diff --git a/vendor/github.com/gambol99/go-marathon/application_marshalling.go b/vendor/github.com/gambol99/go-marathon/application_marshalling.go
new file mode 100644
index 000000000..c92b9ca01
--- /dev/null
+++ b/vendor/github.com/gambol99/go-marathon/application_marshalling.go
@@ -0,0 +1,106 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import (
+ "encoding/json"
+ "fmt"
+)
+
+// Alias aliases the Application struct so that it will be marshaled/unmarshaled automatically
+type Alias Application
+
+// TmpEnvSecret holds the secret values deserialized from the environment variables field
+type TmpEnvSecret struct {
+ Secret string `json:"secret,omitempty"`
+}
+
+// TmpSecret holds the deserialized secrets field in a Marathon application configuration
+type TmpSecret struct {
+ Source string `json:"source,omitempty"`
+}
+
+// UnmarshalJSON unmarshals the given Application JSON as expected except for environment variables and secrets.
+// Environment varialbes are stored in the Env field. Secrets, including the environment variable part,
+// are stored in the Secrets field.
+func (app *Application) UnmarshalJSON(b []byte) error {
+ aux := &struct {
+ *Alias
+ Env map[string]interface{} `json:"env"`
+ Secrets map[string]TmpSecret `json:"secrets"`
+ }{
+ Alias: (*Alias)(app),
+ }
+ if err := json.Unmarshal(b, aux); err != nil {
+ return fmt.Errorf("malformed application definition %v", err)
+ }
+ env := &map[string]string{}
+ secrets := &map[string]Secret{}
+
+ for envName, genericEnvValue := range aux.Env {
+ switch envValOrSecret := genericEnvValue.(type) {
+ case string:
+ (*env)[envName] = envValOrSecret
+ case map[string]interface{}:
+ for secret, secretStore := range envValOrSecret {
+ if secStore, ok := secretStore.(string); ok && secret == "secret" {
+ (*secrets)[secStore] = Secret{EnvVar: envName}
+ break
+ }
+ return fmt.Errorf("unexpected secret field %v or value type %T", secret, envValOrSecret[secret])
+ }
+ default:
+ return fmt.Errorf("unexpected environment variable type %T", envValOrSecret)
+ }
+ }
+ app.Env = env
+ for k, v := range aux.Secrets {
+ tmp := (*secrets)[k]
+ tmp.Source = v.Source
+ (*secrets)[k] = tmp
+ }
+ app.Secrets = secrets
+ return nil
+}
+
+// MarshalJSON marshals the given Application as expected except for environment variables and secrets,
+// which are marshaled from specialized structs. The environment variable piece of the secrets and other
+// normal environment variables are combined and marshaled to the env field. The secrets and the related
+// source are marshaled into the secrets field.
+func (app *Application) MarshalJSON() ([]byte, error) {
+ env := make(map[string]interface{})
+ secrets := make(map[string]TmpSecret)
+
+ if app.Env != nil {
+ for k, v := range *app.Env {
+ env[string(k)] = string(v)
+ }
+ }
+ if app.Secrets != nil {
+ for k, v := range *app.Secrets {
+ env[v.EnvVar] = TmpEnvSecret{Secret: k}
+ secrets[k] = TmpSecret{v.Source}
+ }
+ }
+ aux := &struct {
+ *Alias
+ Env map[string]interface{} `json:"env,omitempty"`
+ Secrets map[string]TmpSecret `json:"secrets,omitempty"`
+ }{Alias: (*Alias)(app), Env: env, Secrets: secrets}
+
+ return json.Marshal(aux)
+}
diff --git a/vendor/github.com/gambol99/go-marathon/client.go b/vendor/github.com/gambol99/go-marathon/client.go
index a042c560f..cc75c3d3e 100644
--- a/vendor/github.com/gambol99/go-marathon/client.go
+++ b/vendor/github.com/gambol99/go-marathon/client.go
@@ -1,5 +1,5 @@
/*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import (
"io"
"io/ioutil"
"log"
+ "net"
"net/http"
"net/url"
"regexp"
@@ -154,6 +155,24 @@ var (
ErrMarathonDown = errors.New("all the Marathon hosts are presently down")
// ErrTimeoutError is thrown when the operation has timed out
ErrTimeoutError = errors.New("the operation has timed out")
+
+ // Default HTTP client used for SSE subscription requests
+ // It is invalid to set client.Timeout because it includes time to read response so
+ // set dial, tls handshake and response header timeouts instead
+ defaultHTTPSSEClient = &http.Client{
+ Transport: &http.Transport{
+ Dial: (&net.Dialer{
+ Timeout: 5 * time.Second,
+ }).Dial,
+ ResponseHeaderTimeout: 10 * time.Second,
+ TLSHandshakeTimeout: 5 * time.Second,
+ },
+ }
+
+ // Default HTTP client used for non SSE requests
+ defaultHTTPClient = &http.Client{
+ Timeout: 10 * time.Second,
+ }
)
// EventsChannelContext holds contextual data for an EventsChannel.
@@ -177,8 +196,8 @@ type marathonClient struct {
hosts *cluster
// a map of service you wish to listen to
listeners map[EventsChannel]EventsChannelContext
- // a custom logger for debug log messages
- debugLog *log.Logger
+ // a custom log function for debug messages
+ debugLog func(format string, v ...interface{})
// the marathon HTTP client to ensure consistency in requests
client *httpClient
}
@@ -196,9 +215,18 @@ type newRequestError struct {
// NewClient creates a new marathon client
// config: the configuration to use
func NewClient(config Config) (Marathon, error) {
- // step: if no http client, set to default
+ // step: if the SSE HTTP client is missing, prefer a configured regular
+ // client, and otherwise use the default SSE HTTP client.
+ if config.HTTPSSEClient == nil {
+ config.HTTPSSEClient = defaultHTTPSSEClient
+ if config.HTTPClient != nil {
+ config.HTTPSSEClient = config.HTTPClient
+ }
+ }
+
+ // step: if a regular HTTP client is missing, use the default one.
if config.HTTPClient == nil {
- config.HTTPClient = http.DefaultClient
+ config.HTTPClient = defaultHTTPClient
}
// step: if no polling wait time is set, default to 500 milliseconds.
@@ -215,16 +243,19 @@ func NewClient(config Config) (Marathon, error) {
return nil, err
}
- debugLogOutput := config.LogOutput
- if debugLogOutput == nil {
- debugLogOutput = ioutil.Discard
+ debugLog := func(string, ...interface{}) {}
+ if config.LogOutput != nil {
+ logger := log.New(config.LogOutput, "", 0)
+ debugLog = func(format string, v ...interface{}) {
+ logger.Printf(format, v...)
+ }
}
return &marathonClient{
config: config,
listeners: make(map[EventsChannel]EventsChannelContext),
hosts: hosts,
- debugLog: log.New(debugLogOutput, "", 0),
+ debugLog: debugLog,
client: client,
}, nil
}
@@ -280,7 +311,7 @@ func (r *marathonClient) apiCall(method, path string, body, result interface{})
if err != nil {
r.hosts.markDown(member)
// step: attempt the request on another member
- r.debugLog.Printf("apiCall(): request failed on host: %s, error: %s, trying another\n", member, err)
+ r.debugLog("apiCall(): request failed on host: %s, error: %s, trying another", member, err)
continue
}
defer response.Body.Close()
@@ -292,9 +323,9 @@ func (r *marathonClient) apiCall(method, path string, body, result interface{})
}
if len(requestBody) > 0 {
- r.debugLog.Printf("apiCall(): %v %v %s returned %v %s\n", request.Method, request.URL.String(), requestBody, response.Status, oneLogLine(respBody))
+ r.debugLog("apiCall(): %v %v %s returned %v %s", request.Method, request.URL.String(), requestBody, response.Status, oneLogLine(respBody))
} else {
- r.debugLog.Printf("apiCall(): %v %v returned %v %s\n", request.Method, request.URL.String(), response.Status, oneLogLine(respBody))
+ r.debugLog("apiCall(): %v %v returned %v %s", request.Method, request.URL.String(), response.Status, oneLogLine(respBody))
}
// step: check for a successfull response
@@ -311,7 +342,7 @@ func (r *marathonClient) apiCall(method, path string, body, result interface{})
if response.StatusCode >= 500 && response.StatusCode <= 599 {
// step: mark the host as down
r.hosts.markDown(member)
- r.debugLog.Printf("apiCall(): request failed, host: %s, status: %d, trying another\n", member, response.StatusCode)
+ r.debugLog("apiCall(): request failed, host: %s, status: %d, trying another", member, response.StatusCode)
continue
}
@@ -329,16 +360,28 @@ func (r *marathonClient) buildAPIRequest(method, path string, reader io.Reader)
}
// Build the HTTP request to Marathon
- request, err = r.client.buildMarathonRequest(method, member, path, reader)
+ request, err = r.client.buildMarathonJSONRequest(method, member, path, reader)
if err != nil {
return nil, member, newRequestError{err}
}
return request, member, nil
}
+// buildMarathonJSONRequest is like buildMarathonRequest but sets the
+// Content-Type and Accept headers to application/json.
+func (rc *httpClient) buildMarathonJSONRequest(method, member, path string, reader io.Reader) (request *http.Request, err error) {
+ req, err := rc.buildMarathonRequest(method, member, path, reader)
+ if err == nil {
+ req.Header.Add("Content-Type", "application/json")
+ req.Header.Add("Accept", "application/json")
+ }
+
+ return req, err
+}
+
// buildMarathonRequest creates a new HTTP request and configures it according to the *httpClient configuration.
// The path must not contain a leading "/", otherwise buildMarathonRequest will panic.
-func (rc *httpClient) buildMarathonRequest(method string, member string, path string, reader io.Reader) (request *http.Request, err error) {
+func (rc *httpClient) buildMarathonRequest(method, member, path string, reader io.Reader) (request *http.Request, err error) {
if strings.HasPrefix(path, "/") {
panic(fmt.Sprintf("Path '%s' must not start with a leading slash", path))
}
@@ -361,9 +404,6 @@ func (rc *httpClient) buildMarathonRequest(method string, member string, path st
request.Header.Add("Authorization", "token="+rc.config.DCOSToken)
}
- request.Header.Add("Content-Type", "application/json")
- request.Header.Add("Accept", "application/json")
-
return request, nil
}
diff --git a/vendor/github.com/gambol99/go-marathon/cluster.go b/vendor/github.com/gambol99/go-marathon/cluster.go
index a97a22c53..3ca99b2c4 100644
--- a/vendor/github.com/gambol99/go-marathon/cluster.go
+++ b/vendor/github.com/gambol99/go-marathon/cluster.go
@@ -1,5 +1,5 @@
/*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -39,6 +39,9 @@ type cluster struct {
members []*member
// the marathon HTTP client to ensure consistency in requests
client *httpClient
+ // healthCheckInterval is the interval by which we probe down nodes for
+ // availability again.
+ healthCheckInterval time.Duration
}
// member represents an individual endpoint
@@ -94,8 +97,9 @@ func newCluster(client *httpClient, marathonURL string, isDCOS bool) (*cluster,
}
return &cluster{
- client: client,
- members: members,
+ client: client,
+ members: members,
+ healthCheckInterval: 5 * time.Second,
}, nil
}
@@ -130,20 +134,21 @@ func (c *cluster) markDown(endpoint string) {
// healthCheckNode performs a health check on the node and when active updates the status
func (c *cluster) healthCheckNode(node *member) {
// step: wait for the node to become active ... we are assuming a /ping is enough here
- for {
+ ticker := time.NewTicker(c.healthCheckInterval)
+ defer ticker.Stop()
+ for range ticker.C {
req, err := c.client.buildMarathonRequest("GET", node.endpoint, "ping", nil)
if err == nil {
res, err := c.client.Do(req)
if err == nil && res.StatusCode == 200 {
+ // step: mark the node as active again
+ c.Lock()
+ node.status = memberStatusUp
+ c.Unlock()
break
}
}
- <-time.After(time.Duration(5 * time.Second))
}
- // step: mark the node as active again
- c.Lock()
- defer c.Unlock()
- node.status = memberStatusUp
}
// activeMembers returns a list of active members
diff --git a/vendor/github.com/gambol99/go-marathon/config.go b/vendor/github.com/gambol99/go-marathon/config.go
index 67bba0982..2e110cc81 100644
--- a/vendor/github.com/gambol99/go-marathon/config.go
+++ b/vendor/github.com/gambol99/go-marathon/config.go
@@ -1,5 +1,5 @@
/*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -50,8 +50,10 @@ type Config struct {
DCOSToken string
// LogOutput the output for debug log messages
LogOutput io.Writer
- // HTTPClient is the http client
+ // HTTPClient is the HTTP client
HTTPClient *http.Client
+ // HTTPSSEClient is the HTTP client used for SSE subscriptions, can't have client.Timeout set
+ HTTPSSEClient *http.Client
// wait time (in milliseconds) between repetitive requests to the API during polling
PollingWaitTime time.Duration
}
diff --git a/vendor/github.com/gambol99/go-marathon/const.go b/vendor/github.com/gambol99/go-marathon/const.go
index 43b1d46a9..8b70c5acb 100644
--- a/vendor/github.com/gambol99/go-marathon/const.go
+++ b/vendor/github.com/gambol99/go-marathon/const.go
@@ -1,5 +1,5 @@
/*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/vendor/github.com/gambol99/go-marathon/deployment.go b/vendor/github.com/gambol99/go-marathon/deployment.go
index 7d57f1758..f83821903 100644
--- a/vendor/github.com/gambol99/go-marathon/deployment.go
+++ b/vendor/github.com/gambol99/go-marathon/deployment.go
@@ -1,5 +1,5 @@
/*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/vendor/github.com/gambol99/go-marathon/docker.go b/vendor/github.com/gambol99/go-marathon/docker.go
index 550409a3c..217d3bbbe 100644
--- a/vendor/github.com/gambol99/go-marathon/docker.go
+++ b/vendor/github.com/gambol99/go-marathon/docker.go
@@ -1,5 +1,5 @@
/*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -46,10 +46,71 @@ type Parameters struct {
// Volume is the docker volume details associated to the container
type Volume struct {
- ContainerPath string `json:"containerPath,omitempty"`
- HostPath string `json:"hostPath,omitempty"`
- External *ExternalVolume `json:"external,omitempty"`
- Mode string `json:"mode,omitempty"`
+ ContainerPath string `json:"containerPath,omitempty"`
+ HostPath string `json:"hostPath,omitempty"`
+ External *ExternalVolume `json:"external,omitempty"`
+ Mode string `json:"mode,omitempty"`
+ Persistent *PersistentVolume `json:"persistent,omitempty"`
+}
+
+type PersistentVolumeType string
+
+const (
+ PersistentVolumeTypeRoot PersistentVolumeType = "root"
+ PersistentVolumeTypePath PersistentVolumeType = "path"
+ PersistentVolumeTypeMount PersistentVolumeType = "mount"
+)
+
+// PersistentVolume declares a Volume to be Persistent, and sets
+// the size (in MiB) and optional type, max size (MiB) and constraints for the Volume.
+type PersistentVolume struct {
+ Type PersistentVolumeType `json:"type,omitempty"`
+ Size int `json:"size"`
+ MaxSize int `json:"maxSize,omitempty"`
+ Constraints *[][]string `json:"constraints,omitempty"`
+}
+
+// SetType sets the type of mesos disk resource to use
+// type: PersistentVolumeType enum
+func (p *PersistentVolume) SetType(tp PersistentVolumeType) *PersistentVolume {
+ p.Type = tp
+ return p
+}
+
+// SetSize sets size of the persistent volume
+// size: size in MiB
+func (p *PersistentVolume) SetSize(size int) *PersistentVolume {
+ p.Size = size
+ return p
+}
+
+// SetMaxSize sets maximum size of an exclusive mount-disk resource to consider;
+// does not apply to root or path disk resource types
+// maxSize: size in MiB
+func (p *PersistentVolume) SetMaxSize(maxSize int) *PersistentVolume {
+ p.MaxSize = maxSize
+ return p
+}
+
+// AddConstraint adds a new constraint
+// constraints: the constraint definition, one constraint per array element
+func (p *PersistentVolume) AddConstraint(constraints ...string) *PersistentVolume {
+ if p.Constraints == nil {
+ p.EmptyConstraints()
+ }
+
+ c := *p.Constraints
+ c = append(c, constraints)
+ p.Constraints = &c
+ return p
+}
+
+// EmptyConstraints explicitly empties constraints -- use this if you need to empty
+// constraints of an application that already has constraints set (setting constraints to nil will
+// keep the current value)
+func (p *PersistentVolume) EmptyConstraints() *PersistentVolume {
+ p.Constraints = &[][]string{}
+ return p
}
// ExternalVolume is an external volume definition
@@ -98,6 +159,19 @@ func (container *Container) EmptyVolumes() *Container {
return container
}
+// SetPersistentVolume defines persistent properties for volume
+func (v *Volume) SetPersistentVolume() *PersistentVolume {
+ ev := &PersistentVolume{}
+ v.Persistent = ev
+ return ev
+}
+
+// EmptyPersistentVolume empties the persistent volume definition
+func (v *Volume) EmptyPersistentVolume() *Volume {
+ v.Persistent = &PersistentVolume{}
+ return v
+}
+
// SetExternalVolume define external elements for a volume
// name: the name of the volume
// provider: the provider of the volume (e.g. dvdi)
diff --git a/vendor/github.com/gambol99/go-marathon/error.go b/vendor/github.com/gambol99/go-marathon/error.go
index 21e731146..09d7dae49 100644
--- a/vendor/github.com/gambol99/go-marathon/error.go
+++ b/vendor/github.com/gambol99/go-marathon/error.go
@@ -1,5 +1,5 @@
/*
-Copyright 2015 Rohith All rights reserved.
+Copyright 2015 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/vendor/github.com/gambol99/go-marathon/events.go b/vendor/github.com/gambol99/go-marathon/events.go
index f97df9084..5814cad29 100644
--- a/vendor/github.com/gambol99/go-marathon/events.go
+++ b/vendor/github.com/gambol99/go-marathon/events.go
@@ -1,5 +1,5 @@
/*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/vendor/github.com/gambol99/go-marathon/group.go b/vendor/github.com/gambol99/go-marathon/group.go
index 401916e3f..63ce310bb 100644
--- a/vendor/github.com/gambol99/go-marathon/group.go
+++ b/vendor/github.com/gambol99/go-marathon/group.go
@@ -1,5 +1,5 @@
/*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -136,7 +136,7 @@ func (r *marathonClient) GroupBy(name string, opts *GetGroupOpts) (*Group, error
// name: the identifier for the group
func (r *marathonClient) HasGroup(name string) (bool, error) {
path := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name))
- err := r.apiCall("GET", path, "", nil)
+ err := r.apiGet(path, "", nil)
if err != nil {
if apiErr, ok := err.(*APIError); ok && apiErr.ErrCode == ErrCodeNotFound {
return false, nil
diff --git a/vendor/github.com/gambol99/go-marathon/health.go b/vendor/github.com/gambol99/go-marathon/health.go
index 11c68e64d..b46d94aad 100644
--- a/vendor/github.com/gambol99/go-marathon/health.go
+++ b/vendor/github.com/gambol99/go-marathon/health.go
@@ -1,5 +1,5 @@
/*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -31,37 +31,37 @@ type HealthCheck struct {
}
// SetCommand sets the given command on the health check.
-func (h HealthCheck) SetCommand(c Command) HealthCheck {
+func (h *HealthCheck) SetCommand(c Command) *HealthCheck {
h.Command = &c
return h
}
// SetPortIndex sets the given port index on the health check.
-func (h HealthCheck) SetPortIndex(i int) HealthCheck {
+func (h *HealthCheck) SetPortIndex(i int) *HealthCheck {
h.PortIndex = &i
return h
}
// SetPort sets the given port on the health check.
-func (h HealthCheck) SetPort(i int) HealthCheck {
+func (h *HealthCheck) SetPort(i int) *HealthCheck {
h.Port = &i
return h
}
// SetPath sets the given path on the health check.
-func (h HealthCheck) SetPath(p string) HealthCheck {
+func (h *HealthCheck) SetPath(p string) *HealthCheck {
h.Path = &p
return h
}
// SetMaxConsecutiveFailures sets the maximum consecutive failures on the health check.
-func (h HealthCheck) SetMaxConsecutiveFailures(i int) HealthCheck {
+func (h *HealthCheck) SetMaxConsecutiveFailures(i int) *HealthCheck {
h.MaxConsecutiveFailures = &i
return h
}
// SetIgnoreHTTP1xx sets ignore http 1xx on the health check.
-func (h HealthCheck) SetIgnoreHTTP1xx(ignore bool) HealthCheck {
+func (h *HealthCheck) SetIgnoreHTTP1xx(ignore bool) *HealthCheck {
h.IgnoreHTTP1xx = &ignore
return h
}
diff --git a/vendor/github.com/gambol99/go-marathon/info.go b/vendor/github.com/gambol99/go-marathon/info.go
index e38cc9ef8..45f5d6807 100644
--- a/vendor/github.com/gambol99/go-marathon/info.go
+++ b/vendor/github.com/gambol99/go-marathon/info.go
@@ -1,5 +1,5 @@
/*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/vendor/github.com/gambol99/go-marathon/last_task_failure.go b/vendor/github.com/gambol99/go-marathon/last_task_failure.go
index 1870f2868..357deee52 100644
--- a/vendor/github.com/gambol99/go-marathon/last_task_failure.go
+++ b/vendor/github.com/gambol99/go-marathon/last_task_failure.go
@@ -1,4 +1,5 @@
/*
+Copyright 2015 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -20,6 +21,7 @@ type LastTaskFailure struct {
AppID string `json:"appId,omitempty"`
Host string `json:"host,omitempty"`
Message string `json:"message,omitempty"`
+ SlaveID string `json:"slaveId,omitempty"`
State string `json:"state,omitempty"`
TaskID string `json:"taskId,omitempty"`
Timestamp string `json:"timestamp,omitempty"`
diff --git a/vendor/github.com/gambol99/go-marathon/port_definition.go b/vendor/github.com/gambol99/go-marathon/port_definition.go
index 4cf3c1fb7..6a5dc6d95 100644
--- a/vendor/github.com/gambol99/go-marathon/port_definition.go
+++ b/vendor/github.com/gambol99/go-marathon/port_definition.go
@@ -1,5 +1,5 @@
/*
-Copyright 2016 Rohith All rights reserved.
+Copyright 2016 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -27,15 +27,39 @@ type PortDefinition struct {
}
// SetPort sets the given port for the PortDefinition
-func (p PortDefinition) SetPort(port int) PortDefinition {
+func (p *PortDefinition) SetPort(port int) *PortDefinition {
+ if p.Port == nil {
+ p.EmptyPort()
+ }
p.Port = &port
return p
}
+// EmptyPort sets the port to 0 for the PortDefinition
+func (p *PortDefinition) EmptyPort() *PortDefinition {
+ port := 0
+ p.Port = &port
+ return p
+}
+
+// SetProtocol sets the protocol for the PortDefinition
+// protocol: the protocol as a string
+func (p *PortDefinition) SetProtocol(protocol string) *PortDefinition {
+ p.Protocol = protocol
+ return p
+}
+
+// SetName sets the name for the PortDefinition
+// name: the name of the PortDefinition
+func (p *PortDefinition) SetName(name string) *PortDefinition {
+ p.Name = name
+ return p
+}
+
// AddLabel adds a label to the PortDefinition
// name: the name of the label
// value: value for this label
-func (p PortDefinition) AddLabel(name, value string) PortDefinition {
+func (p *PortDefinition) AddLabel(name, value string) *PortDefinition {
if p.Labels == nil {
p.EmptyLabels()
}
diff --git a/vendor/github.com/gambol99/go-marathon/queue.go b/vendor/github.com/gambol99/go-marathon/queue.go
index 436489377..2eaede34f 100644
--- a/vendor/github.com/gambol99/go-marathon/queue.go
+++ b/vendor/github.com/gambol99/go-marathon/queue.go
@@ -1,5 +1,5 @@
/*
-Copyright 2016 Rohith All rights reserved.
+Copyright 2016 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -52,9 +52,5 @@ func (r *marathonClient) Queue() (*Queue, error) {
// appID: the ID of the application
func (r *marathonClient) DeleteQueueDelay(appID string) error {
path := fmt.Sprintf("%s/%s/delay", marathonAPIQueue, trimRootPath(appID))
- err := r.apiDelete(path, nil, nil)
- if err != nil {
- return err
- }
- return nil
+ return r.apiDelete(path, nil, nil)
}
diff --git a/vendor/github.com/gambol99/go-marathon/readiness.go b/vendor/github.com/gambol99/go-marathon/readiness.go
index c1887c3c3..ffb0aa149 100644
--- a/vendor/github.com/gambol99/go-marathon/readiness.go
+++ b/vendor/github.com/gambol99/go-marathon/readiness.go
@@ -1,5 +1,5 @@
/*
-Copyright 2017 Rohith All rights reserved.
+Copyright 2017 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/vendor/github.com/gambol99/go-marathon/residency.go b/vendor/github.com/gambol99/go-marathon/residency.go
new file mode 100644
index 000000000..ea9d72d6c
--- /dev/null
+++ b/vendor/github.com/gambol99/go-marathon/residency.go
@@ -0,0 +1,48 @@
+/*
+Copyright 2017 The go-marathon Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package marathon
+
+import "time"
+
+// TaskLostBehaviorType sets action taken when the resident task is lost
+type TaskLostBehaviorType string
+
+const (
+ // TaskLostBehaviorTypeWaitForever indicates to not take any action when the resident task is lost
+ TaskLostBehaviorTypeWaitForever TaskLostBehaviorType = "WAIT_FOREVER"
+ // TaskLostBehaviorTypeWaitForever indicates to try relaunching the lost resident task on
+ // another node after the relaunch escalation timeout has elapsed
+ TaskLostBehaviorTypeRelaunchAfterTimeout TaskLostBehaviorType = "RELAUNCH_AFTER_TIMEOUT"
+)
+
+// Residency defines how terminal states of tasks with local persistent volumes are handled
+type Residency struct {
+ TaskLostBehavior TaskLostBehaviorType `json:"taskLostBehavior,omitempty"`
+ RelaunchEscalationTimeoutSeconds int `json:"relaunchEscalationTimeoutSeconds,omitempty"`
+}
+
+// SetTaskLostBehavior sets the residency behavior
+func (r *Residency) SetTaskLostBehavior(behavior TaskLostBehaviorType) *Residency {
+ r.TaskLostBehavior = behavior
+ return r
+}
+
+// SetRelaunchEscalationTimeout sets the residency relaunch escalation timeout with seconds precision
+func (r *Residency) SetRelaunchEscalationTimeout(timeout time.Duration) *Residency {
+ r.RelaunchEscalationTimeoutSeconds = int(timeout.Seconds())
+ return r
+}
diff --git a/vendor/github.com/gambol99/go-marathon/subscription.go b/vendor/github.com/gambol99/go-marathon/subscription.go
index fa70b1488..a9f75c664 100644
--- a/vendor/github.com/gambol99/go-marathon/subscription.go
+++ b/vendor/github.com/gambol99/go-marathon/subscription.go
@@ -1,5 +1,5 @@
/*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -103,8 +103,7 @@ func (r *marathonClient) registerSubscription() error {
case EventsTransportCallback:
return r.registerCallbackSubscription()
case EventsTransportSSE:
- r.registerSSESubscription()
- return nil
+ return r.registerSSESubscription()
default:
return fmt.Errorf("the events transport: %d is not supported", r.config.EventsTransport)
}
@@ -167,27 +166,34 @@ func (r *marathonClient) registerCallbackSubscription() error {
// connect to the SSE stream and to process the received events. To establish
// the connection it tries the active cluster members until no more member is
// active. When this happens it will retry to get a connection every 5 seconds.
-func (r *marathonClient) registerSSESubscription() {
+func (r *marathonClient) registerSSESubscription() error {
if r.subscribedToSSE {
- return
+ return nil
+ }
+
+ if r.config.HTTPSSEClient.Timeout != 0 {
+ return fmt.Errorf(
+ "global timeout must not be set for SSE connections (found %s) -- remove global timeout from HTTP client or provide separate SSE HTTP client without global timeout",
+ r.config.HTTPSSEClient.Timeout,
+ )
}
go func() {
for {
stream, err := r.connectToSSE()
if err != nil {
- r.debugLog.Printf("Error connecting SSE subscription: %s", err)
+ r.debugLog("Error connecting SSE subscription: %s", err)
<-time.After(5 * time.Second)
continue
}
-
err = r.listenToSSE(stream)
stream.Close()
- r.debugLog.Printf("Error on SSE subscription: %s", err)
+ r.debugLog("Error on SSE subscription: %s", err)
}
}()
r.subscribedToSSE = true
+ return nil
}
// connectToSSE tries to establish an *eventsource.Stream to any of the Marathon cluster members, marking the
@@ -209,15 +215,15 @@ func (r *marathonClient) connectToSSE() (*eventsource.Stream, error) {
// its underlying fields for performance reasons. See note that at least the Transport
// should be reused here: https://golang.org/pkg/net/http/#Client
httpClient := &http.Client{
- Transport: r.config.HTTPClient.Transport,
- CheckRedirect: r.config.HTTPClient.CheckRedirect,
- Jar: r.config.HTTPClient.Jar,
- Timeout: r.config.HTTPClient.Timeout,
+ Transport: r.config.HTTPSSEClient.Transport,
+ CheckRedirect: r.config.HTTPSSEClient.CheckRedirect,
+ Jar: r.config.HTTPSSEClient.Jar,
+ Timeout: r.config.HTTPSSEClient.Timeout,
}
stream, err := eventsource.SubscribeWith("", httpClient, request)
if err != nil {
- r.debugLog.Printf("Error subscribing to Marathon event stream: %s", err)
+ r.debugLog("Error subscribing to Marathon event stream: %s", err)
r.hosts.markDown(member)
continue
}
@@ -231,7 +237,7 @@ func (r *marathonClient) listenToSSE(stream *eventsource.Stream) error {
select {
case ev := <-stream.Events:
if err := r.handleEvent(ev.Data()); err != nil {
- r.debugLog.Printf("listenToSSE(): failed to handle event: %v", err)
+ r.debugLog("listenToSSE(): failed to handle event: %v", err)
}
case err := <-stream.Errors:
return err
@@ -319,12 +325,12 @@ func (r *marathonClient) handleCallbackEvent(writer http.ResponseWriter, request
body, err := ioutil.ReadAll(request.Body)
if err != nil {
// TODO should this return a 500?
- r.debugLog.Printf("handleCallbackEvent(): failed to read request body, error: %s\n", err)
+ r.debugLog("handleCallbackEvent(): failed to read request body, error: %s", err)
return
}
if err := r.handleEvent(string(body[:])); err != nil {
// TODO should this return a 500?
- r.debugLog.Printf("handleCallbackEvent(): failed to handle event: %v\n", err)
+ r.debugLog("handleCallbackEvent(): failed to handle event: %v", err)
}
}
diff --git a/vendor/github.com/gambol99/go-marathon/task.go b/vendor/github.com/gambol99/go-marathon/task.go
index cb7afed35..d923692d7 100644
--- a/vendor/github.com/gambol99/go-marathon/task.go
+++ b/vendor/github.com/gambol99/go-marathon/task.go
@@ -1,5 +1,5 @@
/*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -217,7 +217,7 @@ func (r *Task) allHealthChecksAlive() bool {
}
// step: check the health results then
for _, check := range r.HealthCheckResults {
- if check.Alive == false {
+ if !check.Alive {
return false
}
}
diff --git a/vendor/github.com/gambol99/go-marathon/unreachable_strategy.go b/vendor/github.com/gambol99/go-marathon/unreachable_strategy.go
index a77ff6936..9ed02df9f 100644
--- a/vendor/github.com/gambol99/go-marathon/unreachable_strategy.go
+++ b/vendor/github.com/gambol99/go-marathon/unreachable_strategy.go
@@ -1,5 +1,5 @@
/*
-Copyright 2017 Rohith All rights reserved.
+Copyright 2017 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -65,13 +65,13 @@ func (us *UnreachableStrategy) MarshalJSON() ([]byte, error) {
}
// SetInactiveAfterSeconds sets the period after which instance will be marked as inactive.
-func (us UnreachableStrategy) SetInactiveAfterSeconds(cap float64) UnreachableStrategy {
+func (us *UnreachableStrategy) SetInactiveAfterSeconds(cap float64) *UnreachableStrategy {
us.InactiveAfterSeconds = &cap
return us
}
// SetExpungeAfterSeconds sets the period after which instance will be expunged.
-func (us UnreachableStrategy) SetExpungeAfterSeconds(cap float64) UnreachableStrategy {
+func (us *UnreachableStrategy) SetExpungeAfterSeconds(cap float64) *UnreachableStrategy {
us.ExpungeAfterSeconds = &cap
return us
}
diff --git a/vendor/github.com/gambol99/go-marathon/upgrade_strategy.go b/vendor/github.com/gambol99/go-marathon/upgrade_strategy.go
index f964f08b3..d4d7598a6 100644
--- a/vendor/github.com/gambol99/go-marathon/upgrade_strategy.go
+++ b/vendor/github.com/gambol99/go-marathon/upgrade_strategy.go
@@ -1,5 +1,5 @@
/*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -23,13 +23,13 @@ type UpgradeStrategy struct {
}
// SetMinimumHealthCapacity sets the minimum health capacity.
-func (us UpgradeStrategy) SetMinimumHealthCapacity(cap float64) UpgradeStrategy {
+func (us *UpgradeStrategy) SetMinimumHealthCapacity(cap float64) *UpgradeStrategy {
us.MinimumHealthCapacity = &cap
return us
}
// SetMaximumOverCapacity sets the maximum over capacity.
-func (us UpgradeStrategy) SetMaximumOverCapacity(cap float64) UpgradeStrategy {
+func (us *UpgradeStrategy) SetMaximumOverCapacity(cap float64) *UpgradeStrategy {
us.MaximumOverCapacity = &cap
return us
}
diff --git a/vendor/github.com/gambol99/go-marathon/utils.go b/vendor/github.com/gambol99/go-marathon/utils.go
index 278f49943..718d57bb4 100644
--- a/vendor/github.com/gambol99/go-marathon/utils.go
+++ b/vendor/github.com/gambol99/go-marathon/utils.go
@@ -1,5 +1,5 @@
/*
-Copyright 2014 Rohith All rights reserved.
+Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.