diff --git a/CHANGELOG.md b/CHANGELOG.md index b03fc05fd..db7258925 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Change Log +## [v1.3.0-rc3](https://github.com/containous/traefik/tree/v1.3.0-rc3) (2017-05-24) +[All Commits](https://github.com/containous/traefik/compare/v1.3.0-rc2...v1.3.0-rc3) + +**Enhancements:** +- [#1658](https://api.github.com/repos/containous/traefik/issues/1658) Get testify/require dependency. ([timoreimann](https://api.github.com/users/timoreimann)) + +**Bug fixes:** +- [#1507](https://api.github.com/repos/containous/traefik/issues/1507) Create log folder if not present ([tanyadegurechaff](https://api.github.com/users/tanyadegurechaff)) +- [#1604](https://api.github.com/repos/containous/traefik/issues/1604) [k8s] Ignore Ingresses with empty Endpoint subsets. ([timoreimann](https://api.github.com/users/timoreimann)) +- [#1630](https://api.github.com/repos/containous/traefik/issues/1630) [k8s] Remove rule type path list. ([timoreimann](https://api.github.com/users/timoreimann)) +- [#1635](https://api.github.com/repos/containous/traefik/issues/1635) Upgrade go-marathon to 15ea23e. ([timoreimann](https://api.github.com/users/timoreimann)) +- [#1638](https://api.github.com/repos/containous/traefik/issues/1638) Fix behavior for PathPrefixStrip ([seryl](https://api.github.com/users/seryl)) +- [#1654](https://api.github.com/repos/containous/traefik/issues/1654) fix: Empty Rancher Service Labels. ([ldez](https://api.github.com/users/ldez)) + +**Documentation:** +- [#1578](https://api.github.com/repos/containous/traefik/issues/1578) Add Marathon guide. ([Stibbons](https://api.github.com/users/Stibbons)) +- [#1602](https://api.github.com/repos/containous/traefik/issues/1602) Re Orginise k8s docs to make 1.6 usage easier ([errm](https://api.github.com/users/errm)) +- [#1642](https://api.github.com/repos/containous/traefik/issues/1642) Update changelog ([ldez](https://api.github.com/users/ldez)) + ## [v1.3.0-rc2](https://github.com/containous/traefik/tree/v1.3.0-rc2) (2017-05-16) [All Commits](https://github.com/containous/traefik/compare/v1.3.0-rc1...v1.3.0-rc2) diff --git a/glide.lock b/glide.lock index 91a98fc5a..de833ca2e 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ hash: e59e8244152a823cd3633fb09cdd583c4e5be78d7b50fb7047ba6b6a9ed5e8ec -updated: 2017-05-02T11:46:23.91434995-04:00 +updated: 2017-05-19T23:30:19.890844996+02:00 imports: - name: cloud.google.com/go version: 2e6a95edb1071d750f6d7db777bf66cd2997af6c @@ -201,7 +201,7 @@ imports: - name: github.com/fatih/color version: 9131ab34cf20d2f6d83fdc67168a5430d1c7dc23 - name: github.com/gambol99/go-marathon - version: d672c6fbb499596869d95146a26e7d0746c06c54 + version: 15ea23e360abb8b25071e677aed344f31838e403 - name: github.com/ghodss/yaml version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee - name: github.com/go-ini/ini diff --git a/middlewares/stripPrefix.go b/middlewares/stripPrefix.go index 585e60305..291dda40d 100644 --- a/middlewares/stripPrefix.go +++ b/middlewares/stripPrefix.go @@ -17,17 +17,29 @@ type StripPrefix struct { func (s *StripPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) { for _, prefix := range s.Prefixes { - if p := strings.TrimPrefix(r.URL.Path, strings.TrimSpace(prefix)); len(p) < len(r.URL.Path) { - r.URL.Path = p - r.Header[forwardedPrefixHeader] = []string{prefix} - r.RequestURI = r.URL.RequestURI() - s.Handler.ServeHTTP(w, r) + origPrefix := strings.TrimSpace(prefix) + if origPrefix == r.URL.Path { + r.URL.Path = "/" + s.serveRequest(w, r, origPrefix) + return + } + + prefix = strings.TrimSuffix(origPrefix, "/") + "/" + if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) { + r.URL.Path = "/" + strings.TrimPrefix(p, "/") + s.serveRequest(w, r, origPrefix) return } } http.NotFound(w, r) } +func (s *StripPrefix) serveRequest(w http.ResponseWriter, r *http.Request, prefix string) { + r.Header[forwardedPrefixHeader] = []string{prefix} + r.RequestURI = r.URL.RequestURI() + s.Handler.ServeHTTP(w, r) +} + // SetHandler sets handler func (s *StripPrefix) SetHandler(Handler http.Handler) { s.Handler = Handler diff --git a/middlewares/stripPrefix_test.go b/middlewares/stripPrefix_test.go new file mode 100644 index 000000000..d7fe348f5 --- /dev/null +++ b/middlewares/stripPrefix_test.go @@ -0,0 +1,103 @@ +package middlewares + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStripPrefix(t *testing.T) { + tests := []struct { + desc string + prefixes []string + path string + expectedStatusCode int + expectedPath string + }{ + { + desc: "no prefixes configured", + prefixes: []string{}, + path: "/noprefixes", + expectedStatusCode: http.StatusNotFound, + }, + { + desc: "wildcard (.*) requests", + prefixes: []string{"/"}, + path: "/", + expectedStatusCode: http.StatusOK, + expectedPath: "/", + }, + { + desc: "prefix and path matching", + prefixes: []string{"/stat"}, + path: "/stat", + expectedStatusCode: http.StatusOK, + expectedPath: "/", + }, + { + desc: "path prefix on exactly matching path", + prefixes: []string{"/stat/"}, + path: "/stat/", + expectedStatusCode: http.StatusOK, + expectedPath: "/", + }, + { + desc: "path prefix on matching longer path", + prefixes: []string{"/stat/"}, + path: "/stat/us", + expectedStatusCode: http.StatusOK, + expectedPath: "/us", + }, + { + desc: "path prefix on mismatching path", + prefixes: []string{"/stat/"}, + path: "/status", + expectedStatusCode: http.StatusNotFound, + }, + { + desc: "general prefix on matching path", + prefixes: []string{"/stat"}, + path: "/stat/", + expectedStatusCode: http.StatusOK, + expectedPath: "/", + }, + { + desc: "earlier prefix matching", + prefixes: []string{"/stat", "/stat/us"}, + path: "/stat/us", + expectedStatusCode: http.StatusOK, + expectedPath: "/us", + }, + { + desc: "later prefix matching", + prefixes: []string{"/mismatch", "/stat"}, + path: "/stat", + expectedStatusCode: http.StatusOK, + expectedPath: "/", + }, + } + + for _, test := range tests { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + var gotPath string + server := httptest.NewServer(&StripPrefix{ + Prefixes: test.prefixes, + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotPath = r.URL.Path + }), + }) + defer server.Close() + + resp, err := http.Get(server.URL + test.path) + require.NoError(t, err, "Failed to send GET request") + assert.Equal(t, test.expectedStatusCode, resp.StatusCode, "Unexpected status code") + + assert.Equal(t, test.expectedPath, gotPath, "Unexpected path") + }) + } +} diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go index 8087d5af7..7a5e17473 100644 --- a/provider/kubernetes/kubernetes.go +++ b/provider/kubernetes/kubernetes.go @@ -27,9 +27,6 @@ var _ provider.Provider = (*Provider)(nil) const ( annotationFrontendRuleType = "traefik.frontend.rule.type" - ruleTypePathPrefixStrip = "PathPrefixStrip" - ruleTypePathStrip = "PathStrip" - ruleTypePath = "Path" ruleTypePathPrefix = "PathPrefix" annotationKubernetesWhitelistSourceRange = "ingress.kubernetes.io/whitelist-source-range" @@ -205,12 +202,8 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error) } if len(pa.Path) > 0 { - ruleType, unknown := getRuleTypeFromAnnotation(i.Annotations) - switch { - case unknown: - log.Warnf("Unknown RuleType '%s' for Ingress %s/%s, falling back to PathPrefix", ruleType, i.ObjectMeta.Namespace, i.ObjectMeta.Name) - fallthrough - case ruleType == "": + ruleType := i.Annotations[annotationFrontendRuleType] + if ruleType == "" { ruleType = ruleTypePathPrefix } @@ -265,28 +258,25 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error) } if !exists { - log.Errorf("Endpoints not found for %s/%s", service.ObjectMeta.Namespace, service.ObjectMeta.Name) - continue + log.Warnf("Endpoints not found for %s/%s", service.ObjectMeta.Namespace, service.ObjectMeta.Name) + break } if len(endpoints.Subsets) == 0 { - log.Warnf("Service endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name) - templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{ - URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(int(port.Port)), - Weight: 1, - } - } else { - for _, subset := range endpoints.Subsets { - for _, address := range subset.Addresses { - url := protocol + "://" + address.IP + ":" + strconv.Itoa(endpointPortNumber(port, subset.Ports)) - name := url - if address.TargetRef != nil && address.TargetRef.Name != "" { - name = address.TargetRef.Name - } - templateObjects.Backends[r.Host+pa.Path].Servers[name] = types.Server{ - URL: url, - Weight: 1, - } + log.Warnf("Endpoints not available for %s/%s", service.ObjectMeta.Namespace, service.ObjectMeta.Name) + break + } + + for _, subset := range endpoints.Subsets { + for _, address := range subset.Addresses { + url := protocol + "://" + address.IP + ":" + strconv.Itoa(endpointPortNumber(port, subset.Ports)) + name := url + if address.TargetRef != nil && address.TargetRef.Name != "" { + name = address.TargetRef.Name + } + templateObjects.Backends[r.Host+pa.Path].Servers[name] = types.Server{ + URL: url, + Weight: 1, } } } @@ -398,24 +388,3 @@ func (p *Provider) loadConfig(templateObjects types.Configuration) *types.Config } return configuration } - -func getRuleTypeFromAnnotation(annotations map[string]string) (ruleType string, unknown bool) { - ruleType = annotations[annotationFrontendRuleType] - for _, knownRuleType := range []string{ - ruleTypePathPrefixStrip, - ruleTypePathStrip, - ruleTypePath, - ruleTypePathPrefix, - } { - if strings.ToLower(ruleType) == strings.ToLower(knownRuleType) { - return knownRuleType, false - } - } - - if ruleType != "" { - // Annotation is set but does not match anything we know. - unknown = true - } - - return ruleType, unknown -} diff --git a/provider/kubernetes/kubernetes_test.go b/provider/kubernetes/kubernetes_test.go index 07e843e89..73f7dfe1c 100644 --- a/provider/kubernetes/kubernetes_test.go +++ b/provider/kubernetes/kubernetes_test.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "reflect" - "strings" "testing" "github.com/containous/traefik/types" @@ -333,19 +332,24 @@ func TestRuleType(t *testing.T) { frontendRuleType string }{ { - desc: "implicit default", + desc: "rule type annotation missing", ingressRuleType: "", frontendRuleType: ruleTypePathPrefix, }, { - desc: "unknown ingress / explicit default", - ingressRuleType: "unknown", - frontendRuleType: ruleTypePathPrefix, + desc: "Path rule type annotation set", + ingressRuleType: "Path", + frontendRuleType: "Path", }, { - desc: "explicit ingress", - ingressRuleType: ruleTypePath, - frontendRuleType: ruleTypePath, + desc: "PathStrip rule type annotation set", + ingressRuleType: "PathStrip", + frontendRuleType: "PathStrip", + }, + { + desc: "PathStripPrefix rule type annotation set", + ingressRuleType: "PathStripPrefix", + frontendRuleType: "PathStripPrefix", }, } @@ -1832,65 +1836,6 @@ func TestInvalidPassHostHeaderValue(t *testing.T) { } } -func TestGetRuleTypeFromAnnotation(t *testing.T) { - tests := []struct { - in string - wantedUnknown bool - }{ - { - in: ruleTypePathPrefixStrip, - wantedUnknown: false, - }, - { - in: ruleTypePathStrip, - wantedUnknown: false, - }, - { - in: ruleTypePath, - wantedUnknown: false, - }, - { - in: ruleTypePathPrefix, - wantedUnknown: false, - }, - { - wantedUnknown: false, - }, - { - in: "Unknown", - wantedUnknown: true, - }, - } - - for _, test := range tests { - test := test - inputs := []string{test.in, strings.ToLower(test.in)} - if inputs[0] == inputs[1] { - // Lower-casing makes no difference -- truncating to single case. - inputs = inputs[:1] - } - for _, input := range inputs { - t.Run(fmt.Sprintf("in='%s'", input), func(t *testing.T) { - t.Parallel() - annotations := map[string]string{} - if test.in != "" { - annotations[annotationFrontendRuleType] = test.in - } - - gotRuleType, gotUnknown := getRuleTypeFromAnnotation(annotations) - - if gotUnknown != test.wantedUnknown { - t.Errorf("got unknown '%t', wanted '%t'", gotUnknown, test.wantedUnknown) - } - - if gotRuleType != test.in { - t.Errorf("got rule type '%s', wanted '%s'", gotRuleType, test.in) - } - }) - } - } -} - func TestKubeAPIErrors(t *testing.T) { ingresses := []*v1beta1.Ingress{{ ObjectMeta: v1.ObjectMeta{ @@ -2027,6 +1972,21 @@ func TestMissingResources(t *testing.T) { }, }, }, + { + Host: "missing_endpoint_subsets", + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ + { + Backend: v1beta1.IngressBackend{ + ServiceName: "missing_endpoint_subsets_service", + ServicePort: intstr.FromInt(80), + }, + }, + }, + }, + }, + }, }, }, }} @@ -2061,6 +2021,21 @@ func TestMissingResources(t *testing.T) { }, }, }, + { + ObjectMeta: v1.ObjectMeta{ + Name: "missing_endpoint_subsets_service", + UID: "4", + Namespace: "testing", + }, + Spec: v1.ServiceSpec{ + ClusterIP: "10.0.0.4", + Ports: []v1.ServicePort{ + { + Port: 80, + }, + }, + }, + }, } endpoints := []*v1.Endpoints{ { @@ -2084,6 +2059,14 @@ func TestMissingResources(t *testing.T) { }, }, }, + { + ObjectMeta: v1.ObjectMeta{ + Name: "missing_endpoint_subsets_service", + UID: "4", + Namespace: "testing", + }, + Subsets: []v1.EndpointSubset{}, + }, } watchChan := make(chan interface{}) @@ -2130,6 +2113,14 @@ func TestMissingResources(t *testing.T) { Sticky: false, }, }, + "missing_endpoint_subsets": { + Servers: map[string]types.Server{}, + CircuitBreaker: nil, + LoadBalancer: &types.LoadBalancer{ + Method: "wrr", + Sticky: false, + }, + }, }, Frontends: map[string]*types.Frontend{ "fully_working": { @@ -2150,6 +2141,15 @@ func TestMissingResources(t *testing.T) { }, }, }, + "missing_endpoint_subsets": { + Backend: "missing_endpoint_subsets", + PassHostHeader: true, + Routes: map[string]types.Route{ + "missing_endpoint_subsets": { + Rule: "Host:missing_endpoint_subsets", + }, + }, + }, }, } diff --git a/provider/rancher/rancher.go b/provider/rancher/rancher.go index 88474ee9f..22233d9f1 100644 --- a/provider/rancher/rancher.go +++ b/provider/rancher/rancher.go @@ -388,8 +388,12 @@ func parseRancherData(environments []*rancher.Environment, services []*rancher.S Containers: []string{}, } - for key, value := range service.LaunchConfig.Labels { - rancherData.Labels[key] = value.(string) + if service.LaunchConfig == nil || service.LaunchConfig.Labels == nil { + log.Warnf("Rancher Service Labels are missing. Environment: %s, service: %s", environment.Name, service.Name) + } else { + for key, value := range service.LaunchConfig.Labels { + rancherData.Labels[key] = value.(string) + } } for _, container := range containers { diff --git a/templates/kubernetes.tmpl b/templates/kubernetes.tmpl index 99b2a29c5..be70e9a80 100644 --- a/templates/kubernetes.tmpl +++ b/templates/kubernetes.tmpl @@ -1,4 +1,5 @@ [backends]{{range $backendName, $backend := .Backends}} + [backends."{{$backendName}}"] {{if $backend.CircuitBreaker}} [backends."{{$backendName}}".circuitbreaker] expression = "{{$backend.CircuitBreaker.Expression}}" diff --git a/vendor/github.com/gambol99/go-marathon/application.go b/vendor/github.com/gambol99/go-marathon/application.go index 62eb24705..aba8dc77c 100644 --- a/vendor/github.com/gambol99/go-marathon/application.go +++ b/vendor/github.com/gambol99/go-marathon/application.go @@ -89,6 +89,8 @@ type Application struct { TaskStats map[string]TaskStats `json:"taskStats,omitempty"` User string `json:"user,omitempty"` UpgradeStrategy *UpgradeStrategy `json:"upgradeStrategy,omitempty"` + UnreachableStrategy *UnreachableStrategy `json:"unreachableStrategy,omitempty"` + KillSelection string `json:"killSelection,omitempty"` Uris *[]string `json:"uris,omitempty"` Version string `json:"version,omitempty"` VersionInfo *VersionInfo `json:"versionInfo,omitempty"` @@ -453,7 +455,7 @@ func (r *Application) DeploymentIDs() []*DeploymentID { // CheckHTTP adds a HTTP check to an application // port: the port the check should be checking // interval: the interval in seconds the check should be performed -func (r *Application) CheckHTTP(uri string, port, interval int) (*Application, error) { +func (r *Application) CheckHTTP(path string, port, interval int) (*Application, error) { if r.Container == nil || r.Container.Docker == nil { return nil, ErrNoApplicationContainer } @@ -464,7 +466,7 @@ func (r *Application) CheckHTTP(uri string, port, interval int) (*Application, e } health := NewDefaultHealthCheck() health.IntervalSeconds = interval - *health.Path = uri + *health.Path = path *health.PortIndex = portIndex // step: add to the checks r.AddHealthCheck(*health) @@ -555,6 +557,20 @@ func (r *Application) EmptyUpgradeStrategy() *Application { return r } +// SetUnreachableStrategy sets the unreachable strategy. +func (r *Application) SetUnreachableStrategy(us UnreachableStrategy) *Application { + r.UnreachableStrategy = &us + return r +} + +// EmptyUnreachableStrategy explicitly empties the unreachable strategy -- use this if +// you need to empty the unreachable strategy of an application that already has +// the unreachable strategy set (setting it to nil will keep the current value). +func (r *Application) EmptyUnreachableStrategy() *Application { + r.UnreachableStrategy = &UnreachableStrategy{} + return r +} + // String returns the json representation of this application func (r *Application) String() string { s, err := json.MarshalIndent(r, "", " ") @@ -611,9 +627,9 @@ func (r *marathonClient) HasApplicationVersion(name, version string) (bool, erro // ApplicationVersions is a list of versions which has been deployed with marathon for a specific application // name: the id used to identify the application func (r *marathonClient) ApplicationVersions(name string) (*ApplicationVersions, error) { - uri := fmt.Sprintf("%s/versions", buildURI(name)) + path := fmt.Sprintf("%s/versions", buildPath(name)) versions := new(ApplicationVersions) - if err := r.apiGet(uri, nil, versions); err != nil { + if err := r.apiGet(path, nil, versions); err != nil { return nil, err } return versions, nil @@ -623,9 +639,9 @@ 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) { - uri := fmt.Sprintf(buildURI(name)) + path := fmt.Sprintf(buildPath(name)) deploymentID := new(DeploymentID) - if err := r.apiPut(uri, version, deploymentID); err != nil { + if err := r.apiPut(path, version, deploymentID); err != nil { return nil, err } @@ -639,7 +655,7 @@ func (r *marathonClient) Application(name string) (*Application, error) { Application *Application `json:"app"` } - if err := r.apiGet(buildURI(name), nil, &wrapper); err != nil { + if err := r.apiGet(buildPath(name), nil, &wrapper); err != nil { return nil, err } @@ -650,7 +666,7 @@ func (r *marathonClient) Application(name string) (*Application, error) { // name: the id used to identify the application // opts: GetAppOpts request payload func (r *marathonClient) ApplicationBy(name string, opts *GetAppOpts) (*Application, error) { - u, err := addOptions(buildURI(name), opts) + path, err := addOptions(buildPath(name), opts) if err != nil { return nil, err } @@ -658,7 +674,7 @@ func (r *marathonClient) ApplicationBy(name string, opts *GetAppOpts) (*Applicat Application *Application `json:"app"` } - if err := r.apiGet(u, nil, &wrapper); err != nil { + if err := r.apiGet(path, nil, &wrapper); err != nil { return nil, err } @@ -671,8 +687,8 @@ func (r *marathonClient) ApplicationBy(name string, opts *GetAppOpts) (*Applicat func (r *marathonClient) ApplicationByVersion(name, version string) (*Application, error) { app := new(Application) - uri := fmt.Sprintf("%s/versions/%s", buildURI(name), version) - if err := r.apiGet(uri, nil, app); err != nil { + path := fmt.Sprintf("%s/versions/%s", buildPath(name), version) + if err := r.apiGet(path, nil, app); err != nil { return nil, err } @@ -779,10 +795,10 @@ func (r *marathonClient) appExistAndRunning(name string) bool { // name: the id used to identify the application // force: used to force the delete operation in case of blocked deployment func (r *marathonClient) DeleteApplication(name string, force bool) (*DeploymentID, error) { - uri := buildURIWithForceParam(name, force) + path := buildPathWithForceParam(name, force) // step: check of the application already exists deployID := new(DeploymentID) - if err := r.apiDelete(uri, nil, deployID); err != nil { + if err := r.apiDelete(path, nil, deployID); err != nil { return nil, err } @@ -794,8 +810,8 @@ func (r *marathonClient) DeleteApplication(name string, force bool) (*Deployment func (r *marathonClient) RestartApplication(name string, force bool) (*DeploymentID, error) { deployment := new(DeploymentID) var options struct{} - uri := buildURIWithForceParam(fmt.Sprintf("%s/restart", name), force) - if err := r.apiPost(uri, &options, deployment); err != nil { + path := buildPathWithForceParam(fmt.Sprintf("%s/restart", name), force) + if err := r.apiPost(path, &options, deployment); err != nil { return nil, err } @@ -810,9 +826,9 @@ func (r *marathonClient) ScaleApplicationInstances(name string, instances int, f changes := new(Application) changes.ID = validateID(name) changes.Instances = &instances - uri := buildURIWithForceParam(name, force) + path := buildPathWithForceParam(name, force) deployID := new(DeploymentID) - if err := r.apiPut(uri, changes, deployID); err != nil { + if err := r.apiPut(path, changes, deployID); err != nil { return nil, err } @@ -823,22 +839,22 @@ func (r *marathonClient) ScaleApplicationInstances(name string, instances int, f // application: the structure holding the application configuration func (r *marathonClient) UpdateApplication(application *Application, force bool) (*DeploymentID, error) { result := new(DeploymentID) - uri := buildURIWithForceParam(application.ID, force) - if err := r.apiPut(uri, application, result); err != nil { + path := buildPathWithForceParam(application.ID, force) + if err := r.apiPut(path, application, result); err != nil { return nil, err } return result, nil } -func buildURIWithForceParam(path string, force bool) string { - uri := buildURI(path) +func buildPathWithForceParam(rootPath string, force bool) string { + path := buildPath(rootPath) if force { - uri += "?force=true" + path += "?force=true" } - return uri + return path } -func buildURI(path string) string { +func buildPath(path string) string { return fmt.Sprintf("%s/%s", marathonAPIApps, trimRootPath(path)) } diff --git a/vendor/github.com/gambol99/go-marathon/client.go b/vendor/github.com/gambol99/go-marathon/client.go index b83d66711..d3ca78f8c 100644 --- a/vendor/github.com/gambol99/go-marathon/client.go +++ b/vendor/github.com/gambol99/go-marathon/client.go @@ -27,6 +27,7 @@ import ( "net/http" "net/url" "regexp" + "strings" "sync" "time" ) @@ -238,23 +239,23 @@ func (r *marathonClient) Ping() (bool, error) { return true, nil } -func (r *marathonClient) apiGet(uri string, post, result interface{}) error { - return r.apiCall("GET", uri, post, result) +func (r *marathonClient) apiGet(path string, post, result interface{}) error { + return r.apiCall("GET", path, post, result) } -func (r *marathonClient) apiPut(uri string, post, result interface{}) error { - return r.apiCall("PUT", uri, post, result) +func (r *marathonClient) apiPut(path string, post, result interface{}) error { + return r.apiCall("PUT", path, post, result) } -func (r *marathonClient) apiPost(uri string, post, result interface{}) error { - return r.apiCall("POST", uri, post, result) +func (r *marathonClient) apiPost(path string, post, result interface{}) error { + return r.apiCall("POST", path, post, result) } -func (r *marathonClient) apiDelete(uri string, post, result interface{}) error { - return r.apiCall("DELETE", uri, post, result) +func (r *marathonClient) apiDelete(path string, post, result interface{}) error { + return r.apiCall("DELETE", path, post, result) } -func (r *marathonClient) apiCall(method, url string, body, result interface{}) error { +func (r *marathonClient) apiCall(method, path string, body, result interface{}) error { for { // step: marshall the request to json var requestBody []byte @@ -266,7 +267,7 @@ func (r *marathonClient) apiCall(method, url string, body, result interface{}) e } // step: create the API request - request, member, err := r.buildAPIRequest(method, url, bytes.NewReader(requestBody)) + request, member, err := r.buildAPIRequest(method, path, bytes.NewReader(requestBody)) if err != nil { return err } @@ -317,7 +318,7 @@ func (r *marathonClient) apiCall(method, url string, body, result interface{}) e } // buildAPIRequest creates a default API request -func (r *marathonClient) buildAPIRequest(method, uri string, reader io.Reader) (request *http.Request, member string, err error) { +func (r *marathonClient) buildAPIRequest(method, path string, reader io.Reader) (request *http.Request, member string, err error) { // Grab a member from the cluster member, err = r.hosts.getMember() if err != nil { @@ -325,16 +326,22 @@ func (r *marathonClient) buildAPIRequest(method, uri string, reader io.Reader) ( } // Build the HTTP request to Marathon - request, err = r.client.buildMarathonRequest(method, member, uri, reader) + request, err = r.client.buildMarathonRequest(method, member, path, reader) if err != nil { return nil, member, err } return request, member, nil } -func (rc *httpClient) buildMarathonRequest(method string, member string, uri string, reader io.Reader) (request *http.Request, err error) { +// 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) { + if strings.HasPrefix(path, "/") { + panic(fmt.Sprintf("Path '%s' must not start with a leading slash", path)) + } + // Create the endpoint URL - url := fmt.Sprintf("%s/%s", member, uri) + url := fmt.Sprintf("%s/%s", member, path) // Instantiate an HTTP request request, err = http.NewRequest(method, url, reader) diff --git a/vendor/github.com/gambol99/go-marathon/cluster.go b/vendor/github.com/gambol99/go-marathon/cluster.go index c7ea0a429..a97a22c53 100644 --- a/vendor/github.com/gambol99/go-marathon/cluster.go +++ b/vendor/github.com/gambol99/go-marathon/cluster.go @@ -131,7 +131,7 @@ func (c *cluster) markDown(endpoint string) { func (c *cluster) healthCheckNode(node *member) { // step: wait for the node to become active ... we are assuming a /ping is enough here for { - req, err := c.client.buildMarathonRequest("GET", node.endpoint, "/ping", nil) + 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 { diff --git a/vendor/github.com/gambol99/go-marathon/group.go b/vendor/github.com/gambol99/go-marathon/group.go index 2cdf2acac..b95e40328 100644 --- a/vendor/github.com/gambol99/go-marathon/group.go +++ b/vendor/github.com/gambol99/go-marathon/group.go @@ -106,12 +106,12 @@ func (r *marathonClient) Group(name string) (*Group, error) { // GroupsBy retrieves a list of all the groups from marathon by embed options // opts: GetGroupOpts request payload func (r *marathonClient) GroupsBy(opts *GetGroupOpts) (*Groups, error) { - u, err := addOptions(marathonAPIGroups, opts) + path, err := addOptions(marathonAPIGroups, opts) if err != nil { return nil, err } groups := new(Groups) - if err := r.apiGet(u, "", groups); err != nil { + if err := r.apiGet(path, "", groups); err != nil { return nil, err } return groups, nil @@ -121,12 +121,12 @@ func (r *marathonClient) GroupsBy(opts *GetGroupOpts) (*Groups, error) { // name: the identifier for the group // opts: GetGroupOpts request payload func (r *marathonClient) GroupBy(name string, opts *GetGroupOpts) (*Group, error) { - u, err := addOptions(fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name)), opts) + path, err := addOptions(fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name)), opts) if err != nil { return nil, err } group := new(Group) - if err := r.apiGet(u, nil, group); err != nil { + if err := r.apiGet(path, nil, group); err != nil { return nil, err } return group, nil @@ -135,8 +135,8 @@ func (r *marathonClient) GroupBy(name string, opts *GetGroupOpts) (*Group, error // HasGroup checks if the group exists in marathon // name: the identifier for the group func (r *marathonClient) HasGroup(name string) (bool, error) { - uri := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name)) - err := r.apiCall("GET", uri, "", nil) + path := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name)) + err := r.apiCall("GET", path, "", nil) if err != nil { if apiErr, ok := err.(*APIError); ok && apiErr.ErrCode == ErrCodeNotFound { return false, nil @@ -208,11 +208,9 @@ func (r *marathonClient) WaitOnGroup(name string, timeout time.Duration) error { // force: used to force the delete operation in case of blocked deployment func (r *marathonClient) DeleteGroup(name string, force bool) (*DeploymentID, error) { version := new(DeploymentID) - uri := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name)) - if force { - uri = uri + "?force=true" - } - if err := r.apiDelete(uri, nil, version); err != nil { + path := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name)) + path = buildPathWithForceParam(path, force) + if err := r.apiDelete(path, nil, version); err != nil { return nil, err } @@ -225,11 +223,9 @@ func (r *marathonClient) DeleteGroup(name string, force bool) (*DeploymentID, er // force: used to force the update operation in case of blocked deployment func (r *marathonClient) UpdateGroup(name string, group *Group, force bool) (*DeploymentID, error) { deploymentID := new(DeploymentID) - uri := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name)) - if force { - uri = uri + "?force=true" - } - if err := r.apiPut(uri, group, deploymentID); err != nil { + path := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name)) + path = buildPathWithForceParam(path, force) + if err := r.apiPut(path, group, deploymentID); err != nil { return nil, err } diff --git a/vendor/github.com/gambol99/go-marathon/queue.go b/vendor/github.com/gambol99/go-marathon/queue.go index dfd8c7e92..436489377 100644 --- a/vendor/github.com/gambol99/go-marathon/queue.go +++ b/vendor/github.com/gambol99/go-marathon/queue.go @@ -51,8 +51,8 @@ func (r *marathonClient) Queue() (*Queue, error) { // DeleteQueueDelay resets task launch delay of the specific application // appID: the ID of the application func (r *marathonClient) DeleteQueueDelay(appID string) error { - uri := fmt.Sprintf("%s/%s/delay", marathonAPIQueue, trimRootPath(appID)) - err := r.apiDelete(uri, nil, nil) + path := fmt.Sprintf("%s/%s/delay", marathonAPIQueue, trimRootPath(appID)) + err := r.apiDelete(path, nil, nil) if err != nil { return err } diff --git a/vendor/github.com/gambol99/go-marathon/subscription.go b/vendor/github.com/gambol99/go-marathon/subscription.go index f1c7e25d6..7a625a973 100644 --- a/vendor/github.com/gambol99/go-marathon/subscription.go +++ b/vendor/github.com/gambol99/go-marathon/subscription.go @@ -201,8 +201,8 @@ func (r *marathonClient) registerSSESubscription() error { // Subscribe adds a URL to Marathon's callback facility // callback : the URL you wish to subscribe func (r *marathonClient) Subscribe(callback string) error { - uri := fmt.Sprintf("%s?callbackUrl=%s", marathonAPISubscription, callback) - return r.apiPost(uri, "", nil) + path := fmt.Sprintf("%s?callbackUrl=%s", marathonAPISubscription, callback) + return r.apiPost(path, "", nil) } diff --git a/vendor/github.com/gambol99/go-marathon/task.go b/vendor/github.com/gambol99/go-marathon/task.go index 336ed37f2..cb7afed35 100644 --- a/vendor/github.com/gambol99/go-marathon/task.go +++ b/vendor/github.com/gambol99/go-marathon/task.go @@ -79,13 +79,13 @@ func (r *Task) HasHealthCheckResults() bool { // AllTasks lists tasks of all applications. // opts: AllTasksOpts request payload func (r *marathonClient) AllTasks(opts *AllTasksOpts) (*Tasks, error) { - u, err := addOptions(marathonAPITasks, opts) + path, err := addOptions(marathonAPITasks, opts) if err != nil { return nil, err } tasks := new(Tasks) - if err := r.apiGet(u, nil, tasks); err != nil { + if err := r.apiGet(path, nil, tasks); err != nil { return nil, err } @@ -107,14 +107,14 @@ func (r *marathonClient) Tasks(id string) (*Tasks, error) { // id: the id of the application // opts: KillApplicationTasksOpts request payload func (r *marathonClient) KillApplicationTasks(id string, opts *KillApplicationTasksOpts) (*Tasks, error) { - u := fmt.Sprintf("%s/%s/tasks", marathonAPIApps, trimRootPath(id)) - u, err := addOptions(u, opts) + path := fmt.Sprintf("%s/%s/tasks", marathonAPIApps, trimRootPath(id)) + path, err := addOptions(path, opts) if err != nil { return nil, err } tasks := new(Tasks) - if err := r.apiDelete(u, nil, tasks); err != nil { + if err := r.apiDelete(path, nil, tasks); err != nil { return nil, err } @@ -129,8 +129,8 @@ func (r *marathonClient) KillTask(taskID string, opts *KillTaskOpts) (*Task, err appName = strings.Replace(appName, "_", "/", -1) taskID = strings.Replace(taskID, "/", "_", -1) - u := fmt.Sprintf("%s/%s/tasks/%s", marathonAPIApps, appName, taskID) - u, err := addOptions(u, opts) + path := fmt.Sprintf("%s/%s/tasks/%s", marathonAPIApps, appName, taskID) + path, err := addOptions(path, opts) if err != nil { return nil, err } @@ -139,7 +139,7 @@ func (r *marathonClient) KillTask(taskID string, opts *KillTaskOpts) (*Task, err Task Task `json:"task"` }) - if err := r.apiDelete(u, nil, wrappedTask); err != nil { + if err := r.apiDelete(path, nil, wrappedTask); err != nil { return nil, err } @@ -150,8 +150,8 @@ func (r *marathonClient) KillTask(taskID string, opts *KillTaskOpts) (*Task, err // tasks: the array of task ids // opts: KillTaskOpts request payload func (r *marathonClient) KillTasks(tasks []string, opts *KillTaskOpts) error { - u := fmt.Sprintf("%s/delete", marathonAPITasks) - u, err := addOptions(u, opts) + path := fmt.Sprintf("%s/delete", marathonAPITasks) + path, err := addOptions(path, opts) if err != nil { return nil } @@ -161,7 +161,7 @@ func (r *marathonClient) KillTasks(tasks []string, opts *KillTaskOpts) error { } post.IDs = tasks - return r.apiPost(u, &post, nil) + return r.apiPost(path, &post, nil) } // TaskEndpoints gets the endpoints i.e. HOST_IP:DYNAMIC_PORT for a specific application service diff --git a/vendor/github.com/gambol99/go-marathon/unreachable_strategy.go b/vendor/github.com/gambol99/go-marathon/unreachable_strategy.go new file mode 100644 index 000000000..a15f7afe6 --- /dev/null +++ b/vendor/github.com/gambol99/go-marathon/unreachable_strategy.go @@ -0,0 +1,35 @@ +/* +Copyright 2017 Rohith 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 + +// UnreachableStrategy is the unreachable strategy applied to an application. +type UnreachableStrategy struct { + InactiveAfterSeconds *float64 `json:"inactiveAfterSeconds,omitempty"` + ExpungeAfterSeconds *float64 `json:"expungeAfterSeconds,omitempty"` +} + +// SetInactiveAfterSeconds sets the period after which instance will be marked as inactive. +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 { + us.ExpungeAfterSeconds = &cap + return us +} diff --git a/vendor/github.com/gambol99/go-marathon/update_strategy.go b/vendor/github.com/gambol99/go-marathon/upgrade_strategy.go similarity index 100% rename from vendor/github.com/gambol99/go-marathon/update_strategy.go rename to vendor/github.com/gambol99/go-marathon/upgrade_strategy.go