From 219a6372b04778f361e3646be9e0060bfc30a15e Mon Sep 17 00:00:00 2001 From: Timo Reimann Date: Fri, 19 May 2017 14:24:28 +0200 Subject: [PATCH 1/8] Upgrade go-marathon to 15ea23e. Our vendored copy contains a bug that causes unavailable Marathon nodes to never be marked as available again due to a misconstruction in the URL to the Marathon health check / ping endpoint used by go-marathon internally. A fix[1] has been published. [1]https://github.com/gambol99/go-marathon/pull/283 --- glide.lock | 4 +- .../gambol99/go-marathon/application.go | 64 ++++++++++++------- .../github.com/gambol99/go-marathon/client.go | 35 ++++++---- .../gambol99/go-marathon/cluster.go | 2 +- .../github.com/gambol99/go-marathon/group.go | 28 ++++---- .../github.com/gambol99/go-marathon/queue.go | 4 +- .../gambol99/go-marathon/subscription.go | 4 +- .../github.com/gambol99/go-marathon/task.go | 22 +++---- .../go-marathon/unreachable_strategy.go | 35 ++++++++++ ...update_strategy.go => upgrade_strategy.go} | 0 10 files changed, 126 insertions(+), 72 deletions(-) create mode 100644 vendor/github.com/gambol99/go-marathon/unreachable_strategy.go rename vendor/github.com/gambol99/go-marathon/{update_strategy.go => upgrade_strategy.go} (100%) diff --git a/glide.lock b/glide.lock index a6eb38fcf..710f91ced 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ hash: 1aa32496b865dda72d76c7cba3458f1c2c467acf0b99aab4609323f109aa64f6 -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/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 From f7d9dfafd0c486b31957e0eefa0bf792bfba9b10 Mon Sep 17 00:00:00 2001 From: Timo Reimann Date: Thu, 18 May 2017 23:27:10 +0200 Subject: [PATCH 2/8] [k8s] Remove rule type path list. Instead of doing sanity checks in the Kubernetes provider, we just accept any non-empty value from the annotation and rely on the server part to filter out unknown rules. This allows us to automatically stay in sync with the currently supported Path matchers/modifiers. --- provider/kubernetes/kubernetes.go | 32 +---------- provider/kubernetes/kubernetes_test.go | 73 ++------------------------ 2 files changed, 6 insertions(+), 99 deletions(-) diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go index 61e8bc842..1d81c736a 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" ) @@ -199,12 +196,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 } @@ -392,24 +385,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 5cb0219ae..d3fe45d88 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,14 @@ 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: "explicit ingress", - ingressRuleType: ruleTypePath, - frontendRuleType: ruleTypePath, + desc: "rule type annotation set", + ingressRuleType: "Path", + frontendRuleType: "Path", }, } @@ -1773,65 +1767,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{ From b392023c3781c7bb15ac2ab5fdf0253c202c8331 Mon Sep 17 00:00:00 2001 From: Timo Reimann Date: Fri, 19 May 2017 11:50:36 +0200 Subject: [PATCH 3/8] Add additional tests for PathStrip{Prefix}. --- provider/kubernetes/kubernetes_test.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/provider/kubernetes/kubernetes_test.go b/provider/kubernetes/kubernetes_test.go index d3fe45d88..02ffa5405 100644 --- a/provider/kubernetes/kubernetes_test.go +++ b/provider/kubernetes/kubernetes_test.go @@ -337,10 +337,20 @@ func TestRuleType(t *testing.T) { frontendRuleType: ruleTypePathPrefix, }, { - desc: "rule type annotation set", + desc: "Path rule type annotation set", ingressRuleType: "Path", frontendRuleType: "Path", }, + { + desc: "PathStrip rule type annotation set", + ingressRuleType: "PathStrip", + frontendRuleType: "PathStrip", + }, + { + desc: "PathStripPrefix rule type annotation set", + ingressRuleType: "PathStripPrefix", + frontendRuleType: "PathStripPrefix", + }, } for _, test := range tests { From 9967494996d47b7ab88a2d7783e5c0bb9a5a5261 Mon Sep 17 00:00:00 2001 From: Timo Reimann Date: Mon, 15 May 2017 23:16:35 +0200 Subject: [PATCH 4/8] [k8s] Ignore Ingresses with empty Endpoint subsets. We previously fell back to using ClusterIPs. However, the approach can lead to all kinds of problems since Ingresses rely on being able to talk to Endpoints directly. For instance, it can break stickiness and retries. --- provider/kubernetes/kubernetes.go | 35 ++++++++-------- provider/kubernetes/kubernetes_test.go | 55 ++++++++++++++++++++++++++ templates/kubernetes.tmpl | 1 + 3 files changed, 72 insertions(+), 19 deletions(-) diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go index 1d81c736a..6af976536 100644 --- a/provider/kubernetes/kubernetes.go +++ b/provider/kubernetes/kubernetes.go @@ -252,28 +252,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, } } } diff --git a/provider/kubernetes/kubernetes_test.go b/provider/kubernetes/kubernetes_test.go index 02ffa5405..1fda8af55 100644 --- a/provider/kubernetes/kubernetes_test.go +++ b/provider/kubernetes/kubernetes_test.go @@ -1913,6 +1913,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), + }, + }, + }, + }, + }, + }, }, }, }} @@ -1947,6 +1962,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{ { @@ -1970,6 +2000,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{}) @@ -2016,6 +2054,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": { @@ -2036,6 +2082,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/templates/kubernetes.tmpl b/templates/kubernetes.tmpl index d11a34b61..c0d9a7657 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}}" From 4293446111d5c1dad97fd6533fe30b170e0e5496 Mon Sep 17 00:00:00 2001 From: Timo Reimann Date: Thu, 27 Apr 2017 01:02:00 +0200 Subject: [PATCH 5/8] Install github.com/stretchr/testify/require. --- glide.lock | 3 +- glide.yaml | 2 + .../stretchr/testify/require/doc.go | 28 ++ .../testify/require/forward_requirements.go | 16 + .../stretchr/testify/require/require.go | 429 ++++++++++++++++++ .../testify/require/require_forward.go | 353 ++++++++++++++ .../stretchr/testify/require/requirements.go | 9 + 7 files changed, 839 insertions(+), 1 deletion(-) create mode 100644 vendor/github.com/stretchr/testify/require/doc.go create mode 100644 vendor/github.com/stretchr/testify/require/forward_requirements.go create mode 100644 vendor/github.com/stretchr/testify/require/require.go create mode 100644 vendor/github.com/stretchr/testify/require/require_forward.go create mode 100644 vendor/github.com/stretchr/testify/require/requirements.go diff --git a/glide.lock b/glide.lock index 710f91ced..de833ca2e 100644 --- a/glide.lock +++ b/glide.lock @@ -1,4 +1,4 @@ -hash: 1aa32496b865dda72d76c7cba3458f1c2c467acf0b99aab4609323f109aa64f6 +hash: e59e8244152a823cd3633fb09cdd583c4e5be78d7b50fb7047ba6b6a9ed5e8ec updated: 2017-05-19T23:30:19.890844996+02:00 imports: - name: cloud.google.com/go @@ -391,6 +391,7 @@ imports: subpackages: - assert - mock + - require - name: github.com/thoas/stats version: 152b5d051953fdb6e45f14b6826962aadc032324 - name: github.com/timewasted/linode diff --git a/glide.yaml b/glide.yaml index daa8220de..5bb9e7aff 100644 --- a/glide.yaml +++ b/glide.yaml @@ -49,7 +49,9 @@ import: - package: github.com/streamrail/concurrent-map - package: github.com/stretchr/testify subpackages: + - assert - mock + - require - package: github.com/thoas/stats version: 152b5d051953fdb6e45f14b6826962aadc032324 - package: github.com/unrolled/render diff --git a/vendor/github.com/stretchr/testify/require/doc.go b/vendor/github.com/stretchr/testify/require/doc.go new file mode 100644 index 000000000..169de3922 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/doc.go @@ -0,0 +1,28 @@ +// Package require implements the same assertions as the `assert` package but +// stops test execution when a test fails. +// +// Example Usage +// +// The following is a complete example using require in a standard test function: +// import ( +// "testing" +// "github.com/stretchr/testify/require" +// ) +// +// func TestSomething(t *testing.T) { +// +// var a string = "Hello" +// var b string = "Hello" +// +// require.Equal(t, a, b, "The two words should be the same.") +// +// } +// +// Assertions +// +// The `require` package have same global functions as in the `assert` package, +// but instead of returning a boolean result they call `t.FailNow()`. +// +// Every assertion function also takes an optional string message as the final argument, +// allowing custom error messages to be appended to the message the assertion method outputs. +package require diff --git a/vendor/github.com/stretchr/testify/require/forward_requirements.go b/vendor/github.com/stretchr/testify/require/forward_requirements.go new file mode 100644 index 000000000..d3c2ab9bc --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/forward_requirements.go @@ -0,0 +1,16 @@ +package require + +// Assertions provides assertion methods around the +// TestingT interface. +type Assertions struct { + t TestingT +} + +// New makes a new Assertions object for the specified TestingT. +func New(t TestingT) *Assertions { + return &Assertions{ + t: t, + } +} + +//go:generate go run ../_codegen/main.go -output-package=require -template=require_forward.go.tmpl diff --git a/vendor/github.com/stretchr/testify/require/require.go b/vendor/github.com/stretchr/testify/require/require.go new file mode 100644 index 000000000..fc567f140 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require.go @@ -0,0 +1,429 @@ +/* +* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen +* THIS FILE MUST NOT BE EDITED BY HAND + */ + +package require + +import ( + assert "github.com/stretchr/testify/assert" + http "net/http" + url "net/url" + time "time" +) + +// Condition uses a Comparison to assert a complex condition. +func Condition(t TestingT, comp assert.Comparison, msgAndArgs ...interface{}) { + if !assert.Condition(t, comp, msgAndArgs...) { + t.FailNow() + } +} + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// assert.Contains(t, "Hello World", "World", "But 'Hello World' does contain 'World'") +// assert.Contains(t, ["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") +// assert.Contains(t, {"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") +// +// Returns whether the assertion was successful (true) or not (false). +func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if !assert.Contains(t, s, contains, msgAndArgs...) { + t.FailNow() + } +} + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Empty(t, obj) +// +// Returns whether the assertion was successful (true) or not (false). +func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if !assert.Empty(t, object, msgAndArgs...) { + t.FailNow() + } +} + +// Equal asserts that two objects are equal. +// +// assert.Equal(t, 123, 123, "123 and 123 should be equal") +// +// Returns whether the assertion was successful (true) or not (false). +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func Equal(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if !assert.Equal(t, expected, actual, msgAndArgs...) { + t.FailNow() + } +} + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// assert.EqualError(t, err, expectedErrorString, "An error was expected") +// +// Returns whether the assertion was successful (true) or not (false). +func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) { + if !assert.EqualError(t, theError, errString, msgAndArgs...) { + t.FailNow() + } +} + +// EqualValues asserts that two objects are equal or convertable to the same types +// and equal. +// +// assert.EqualValues(t, uint32(123), int32(123), "123 and 123 should be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if !assert.EqualValues(t, expected, actual, msgAndArgs...) { + t.FailNow() + } +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Error(t, err, "An error was expected") { +// assert.Equal(t, err, expectedError) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func Error(t TestingT, err error, msgAndArgs ...interface{}) { + if !assert.Error(t, err, msgAndArgs...) { + t.FailNow() + } +} + +// Exactly asserts that two objects are equal is value and type. +// +// assert.Exactly(t, int32(123), int64(123), "123 and 123 should NOT be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if !assert.Exactly(t, expected, actual, msgAndArgs...) { + t.FailNow() + } +} + +// Fail reports a failure through +func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) { + if !assert.Fail(t, failureMessage, msgAndArgs...) { + t.FailNow() + } +} + +// FailNow fails test +func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) { + if !assert.FailNow(t, failureMessage, msgAndArgs...) { + t.FailNow() + } +} + +// False asserts that the specified value is false. +// +// assert.False(t, myBool, "myBool should be false") +// +// Returns whether the assertion was successful (true) or not (false). +func False(t TestingT, value bool, msgAndArgs ...interface{}) { + if !assert.False(t, value, msgAndArgs...) { + t.FailNow() + } +} + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) { + if !assert.HTTPBodyContains(t, handler, method, url, values, str) { + t.FailNow() + } +} + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) { + if !assert.HTTPBodyNotContains(t, handler, method, url, values, str) { + t.FailNow() + } +} + +// HTTPError asserts that a specified handler returns an error status code. +// +// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) { + if !assert.HTTPError(t, handler, method, url, values) { + t.FailNow() + } +} + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) { + if !assert.HTTPRedirect(t, handler, method, url, values) { + t.FailNow() + } +} + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccess(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) { + if !assert.HTTPSuccess(t, handler, method, url, values) { + t.FailNow() + } +} + +// Implements asserts that an object is implemented by the specified interface. +// +// assert.Implements(t, (*MyInterface)(nil), new(MyObject), "MyObject") +func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if !assert.Implements(t, interfaceObject, object, msgAndArgs...) { + t.FailNow() + } +} + +// InDelta asserts that the two numerals are within delta of each other. +// +// assert.InDelta(t, math.Pi, (22 / 7.0), 0.01) +// +// Returns whether the assertion was successful (true) or not (false). +func InDelta(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if !assert.InDelta(t, expected, actual, delta, msgAndArgs...) { + t.FailNow() + } +} + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func InDeltaSlice(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if !assert.InDeltaSlice(t, expected, actual, delta, msgAndArgs...) { + t.FailNow() + } +} + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +// +// Returns whether the assertion was successful (true) or not (false). +func InEpsilon(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if !assert.InEpsilon(t, expected, actual, epsilon, msgAndArgs...) { + t.FailNow() + } +} + +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlice(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if !assert.InEpsilonSlice(t, expected, actual, epsilon, msgAndArgs...) { + t.FailNow() + } +} + +// IsType asserts that the specified objects are of the same type. +func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { + if !assert.IsType(t, expectedType, object, msgAndArgs...) { + t.FailNow() + } +} + +// JSONEq asserts that two JSON strings are equivalent. +// +// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +// +// Returns whether the assertion was successful (true) or not (false). +func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { + if !assert.JSONEq(t, expected, actual, msgAndArgs...) { + t.FailNow() + } +} + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// assert.Len(t, mySlice, 3, "The size of slice is not 3") +// +// Returns whether the assertion was successful (true) or not (false). +func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) { + if !assert.Len(t, object, length, msgAndArgs...) { + t.FailNow() + } +} + +// Nil asserts that the specified object is nil. +// +// assert.Nil(t, err, "err should be nothing") +// +// Returns whether the assertion was successful (true) or not (false). +func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if !assert.Nil(t, object, msgAndArgs...) { + t.FailNow() + } +} + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoError(t, err) { +// assert.Equal(t, actualObj, expectedObj) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func NoError(t TestingT, err error, msgAndArgs ...interface{}) { + if !assert.NoError(t, err, msgAndArgs...) { + t.FailNow() + } +} + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// assert.NotContains(t, "Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") +// assert.NotContains(t, ["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") +// assert.NotContains(t, {"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") +// +// Returns whether the assertion was successful (true) or not (false). +func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if !assert.NotContains(t, s, contains, msgAndArgs...) { + t.FailNow() + } +} + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmpty(t, obj) { +// assert.Equal(t, "two", obj[1]) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if !assert.NotEmpty(t, object, msgAndArgs...) { + t.FailNow() + } +} + +// NotEqual asserts that the specified values are NOT equal. +// +// assert.NotEqual(t, obj1, obj2, "two objects shouldn't be equal") +// +// Returns whether the assertion was successful (true) or not (false). +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqual(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if !assert.NotEqual(t, expected, actual, msgAndArgs...) { + t.FailNow() + } +} + +// NotNil asserts that the specified object is not nil. +// +// assert.NotNil(t, err, "err should be something") +// +// Returns whether the assertion was successful (true) or not (false). +func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if !assert.NotNil(t, object, msgAndArgs...) { + t.FailNow() + } +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanics(t, func(){ +// RemainCalm() +// }, "Calling RemainCalm() should NOT panic") +// +// Returns whether the assertion was successful (true) or not (false). +func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if !assert.NotPanics(t, f, msgAndArgs...) { + t.FailNow() + } +} + +// NotRegexp asserts that a specified regexp does not match a string. +// +// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") +// assert.NotRegexp(t, "^start", "it's not starting") +// +// Returns whether the assertion was successful (true) or not (false). +func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if !assert.NotRegexp(t, rx, str, msgAndArgs...) { + t.FailNow() + } +} + +// NotZero asserts that i is not the zero value for its type and returns the truth. +func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) { + if !assert.NotZero(t, i, msgAndArgs...) { + t.FailNow() + } +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panics(t, func(){ +// GoCrazy() +// }, "Calling GoCrazy() should panic") +// +// Returns whether the assertion was successful (true) or not (false). +func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if !assert.Panics(t, f, msgAndArgs...) { + t.FailNow() + } +} + +// Regexp asserts that a specified regexp matches a string. +// +// assert.Regexp(t, regexp.MustCompile("start"), "it's starting") +// assert.Regexp(t, "start...$", "it's not starting") +// +// Returns whether the assertion was successful (true) or not (false). +func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if !assert.Regexp(t, rx, str, msgAndArgs...) { + t.FailNow() + } +} + +// True asserts that the specified value is true. +// +// assert.True(t, myBool, "myBool should be true") +// +// Returns whether the assertion was successful (true) or not (false). +func True(t TestingT, value bool, msgAndArgs ...interface{}) { + if !assert.True(t, value, msgAndArgs...) { + t.FailNow() + } +} + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") +// +// Returns whether the assertion was successful (true) or not (false). +func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { + if !assert.WithinDuration(t, expected, actual, delta, msgAndArgs...) { + t.FailNow() + } +} + +// Zero asserts that i is the zero value for its type and returns the truth. +func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) { + if !assert.Zero(t, i, msgAndArgs...) { + t.FailNow() + } +} diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go b/vendor/github.com/stretchr/testify/require/require_forward.go new file mode 100644 index 000000000..caa18793d --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require_forward.go @@ -0,0 +1,353 @@ +/* +* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen +* THIS FILE MUST NOT BE EDITED BY HAND + */ + +package require + +import ( + assert "github.com/stretchr/testify/assert" + http "net/http" + url "net/url" + time "time" +) + +// Condition uses a Comparison to assert a complex condition. +func (a *Assertions) Condition(comp assert.Comparison, msgAndArgs ...interface{}) { + Condition(a.t, comp, msgAndArgs...) +} + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Contains("Hello World", "World", "But 'Hello World' does contain 'World'") +// a.Contains(["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") +// a.Contains({"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + Contains(a.t, s, contains, msgAndArgs...) +} + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Empty(obj) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) { + Empty(a.t, object, msgAndArgs...) +} + +// Equal asserts that two objects are equal. +// +// a.Equal(123, 123, "123 and 123 should be equal") +// +// Returns whether the assertion was successful (true) or not (false). +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + Equal(a.t, expected, actual, msgAndArgs...) +} + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// a.EqualError(err, expectedErrorString, "An error was expected") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) { + EqualError(a.t, theError, errString, msgAndArgs...) +} + +// EqualValues asserts that two objects are equal or convertable to the same types +// and equal. +// +// a.EqualValues(uint32(123), int32(123), "123 and 123 should be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + EqualValues(a.t, expected, actual, msgAndArgs...) +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Error(err, "An error was expected") { +// assert.Equal(t, err, expectedError) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Error(err error, msgAndArgs ...interface{}) { + Error(a.t, err, msgAndArgs...) +} + +// Exactly asserts that two objects are equal is value and type. +// +// a.Exactly(int32(123), int64(123), "123 and 123 should NOT be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + Exactly(a.t, expected, actual, msgAndArgs...) +} + +// Fail reports a failure through +func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) { + Fail(a.t, failureMessage, msgAndArgs...) +} + +// FailNow fails test +func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) { + FailNow(a.t, failureMessage, msgAndArgs...) +} + +// False asserts that the specified value is false. +// +// a.False(myBool, "myBool should be false") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) False(value bool, msgAndArgs ...interface{}) { + False(a.t, value, msgAndArgs...) +} + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) { + HTTPBodyContains(a.t, handler, method, url, values, str) +} + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) { + HTTPBodyNotContains(a.t, handler, method, url, values, str) +} + +// HTTPError asserts that a specified handler returns an error status code. +// +// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values) { + HTTPError(a.t, handler, method, url, values) +} + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values) { + HTTPRedirect(a.t, handler, method, url, values) +} + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values) { + HTTPSuccess(a.t, handler, method, url, values) +} + +// Implements asserts that an object is implemented by the specified interface. +// +// a.Implements((*MyInterface)(nil), new(MyObject), "MyObject") +func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + Implements(a.t, interfaceObject, object, msgAndArgs...) +} + +// InDelta asserts that the two numerals are within delta of each other. +// +// a.InDelta(math.Pi, (22 / 7.0), 0.01) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + InDelta(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) +} + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) +} + +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...) +} + +// IsType asserts that the specified objects are of the same type. +func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { + IsType(a.t, expectedType, object, msgAndArgs...) +} + +// JSONEq asserts that two JSON strings are equivalent. +// +// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) { + JSONEq(a.t, expected, actual, msgAndArgs...) +} + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// a.Len(mySlice, 3, "The size of slice is not 3") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) { + Len(a.t, object, length, msgAndArgs...) +} + +// Nil asserts that the specified object is nil. +// +// a.Nil(err, "err should be nothing") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) { + Nil(a.t, object, msgAndArgs...) +} + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoError(err) { +// assert.Equal(t, actualObj, expectedObj) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) { + NoError(a.t, err, msgAndArgs...) +} + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContains("Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") +// a.NotContains(["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") +// a.NotContains({"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + NotContains(a.t, s, contains, msgAndArgs...) +} + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmpty(obj) { +// assert.Equal(t, "two", obj[1]) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) { + NotEmpty(a.t, object, msgAndArgs...) +} + +// NotEqual asserts that the specified values are NOT equal. +// +// a.NotEqual(obj1, obj2, "two objects shouldn't be equal") +// +// Returns whether the assertion was successful (true) or not (false). +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + NotEqual(a.t, expected, actual, msgAndArgs...) +} + +// NotNil asserts that the specified object is not nil. +// +// a.NotNil(err, "err should be something") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) { + NotNil(a.t, object, msgAndArgs...) +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanics(func(){ +// RemainCalm() +// }, "Calling RemainCalm() should NOT panic") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotPanics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + NotPanics(a.t, f, msgAndArgs...) +} + +// NotRegexp asserts that a specified regexp does not match a string. +// +// a.NotRegexp(regexp.MustCompile("starts"), "it's starting") +// a.NotRegexp("^start", "it's not starting") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + NotRegexp(a.t, rx, str, msgAndArgs...) +} + +// NotZero asserts that i is not the zero value for its type and returns the truth. +func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) { + NotZero(a.t, i, msgAndArgs...) +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panics(func(){ +// GoCrazy() +// }, "Calling GoCrazy() should panic") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Panics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + Panics(a.t, f, msgAndArgs...) +} + +// Regexp asserts that a specified regexp matches a string. +// +// a.Regexp(regexp.MustCompile("start"), "it's starting") +// a.Regexp("start...$", "it's not starting") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + Regexp(a.t, rx, str, msgAndArgs...) +} + +// True asserts that the specified value is true. +// +// a.True(myBool, "myBool should be true") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) True(value bool, msgAndArgs ...interface{}) { + True(a.t, value, msgAndArgs...) +} + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// a.WithinDuration(time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { + WithinDuration(a.t, expected, actual, delta, msgAndArgs...) +} + +// Zero asserts that i is the zero value for its type and returns the truth. +func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) { + Zero(a.t, i, msgAndArgs...) +} diff --git a/vendor/github.com/stretchr/testify/require/requirements.go b/vendor/github.com/stretchr/testify/require/requirements.go new file mode 100644 index 000000000..41147562d --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/requirements.go @@ -0,0 +1,9 @@ +package require + +// TestingT is an interface wrapper around *testing.T +type TestingT interface { + Errorf(format string, args ...interface{}) + FailNow() +} + +//go:generate go run ../_codegen/main.go -output-package=require -template=require.go.tmpl From bc0121808a9451cfb66ec2f4cbe2166588ee6734 Mon Sep 17 00:00:00 2001 From: Josh Toft Date: Mon, 22 May 2017 15:44:02 -0700 Subject: [PATCH 6/8] Fix behavior for PathPrefixStrip When pushing data to downstream proxies; malformed requests were being sent. The corrected behavior is as follows: | Route Stripped | URL | Passed to Backend | | ----------------- | ---------------------- | ------------------ | | / | / | / | | Route Stripped | URL | Passed to Backend | | ----------------- | ---------------------- | ------------------ | | /stat | /stat | / | | /stat | /stat/ | / | | /stat | /status | /status | | /stat | /stat/us | /us | | Route Stripped | URL | Passed to Backend | | ----------------- | ---------------------- | ------------------ | | /stat/ | /stat | /stat | | /stat/ | /stat/ | / | | /stat/ | /status | /status | | /stat/ | /stat/us | /us | Prior, we could strip the prefixing `/`, and we'd also ignore the case where you want to serve something like `/api` as both the index and as a subpath. Additionally, this should resolve a myriad of issues relating to kubernetes ingress `PathPrefixStrip`. --- middlewares/stripPrefix.go | 22 +++++-- middlewares/stripPrefix_test.go | 103 ++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 middlewares/stripPrefix_test.go 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") + }) + } +} From 5b896bb46c7dbb0d35ad6f4d80dbc462dd514756 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 23 May 2017 15:56:34 +0200 Subject: [PATCH 7/8] fix: Empty Rancher launch config. --- provider/rancher/rancher.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 { From dbfd2663c25ed83cc9ee9353bf0d4b4a34840791 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 24 May 2017 14:37:29 +0200 Subject: [PATCH 8/8] Prepare release v1.3.0-rc3 --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) 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)