Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add annotation to allow modifiers to be used properly in kubernetes #3481

Merged
merged 2 commits into from
Jul 6, 2018

Conversation

dtomcej
Copy link
Contributor

@dtomcej dtomcej commented Jun 12, 2018

What does this PR do?

Adds a request-modifier annotation to the kubernetes provider, that will allow the modifiers to be used (AddPrefix, ReplacePath, ReplacePathRegex) properly, and without inferfering with the matcher rules.

Motivation

Fixes #2985, #3230

More

  • Added/updated tests
  • Added/updated documentation

Additional Notes

This PR also introduces a warning when users use ReplacePath as a rule-type, as it is a modifier, not a matcher rule.

@@ -156,7 +156,8 @@ The following general annotations are applicable on the Ingress object:
| `traefik.ingress.kubernetes.io/redirect-regex: ^http://localhost/(.*)` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-replacement`. |
| `traefik.ingress.kubernetes.io/redirect-replacement: http://mydomain/$1` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-regex`. |
| `traefik.ingress.kubernetes.io/rewrite-target: /users` | Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header. |
| `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Override the default frontend rule type. Default: `PathPrefix`. |
| `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Override the default frontend rule type. Only path related matchers should be used (`Path`, `PathPrefix`, `PathStrip`, `PathPrefixStrip`). Default: `PathPrefix`. |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should or rather can?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is crazy legacy code in there that allows for "ReplacePath" to be used as the rule-type. But we want to discourage it (hence the log.warn, and it not being listed in the "should" list), but it can technically be used to maintain backwards compatibility to people already using it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I will update the wording to "can" and anyone that goes "but can you do X" can bite me

rules = append(rules, ruleType+":"+pa.Path)
case ruleTypeReplacePath:
rules = append(rules, ruleType+":"+pa.Path)
log.Warnf("Using %s as rule-type will be deprecated in the future. Please use request-modifier annotation instead", ruleType)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we name the full new annotation name?

@@ -421,6 +443,27 @@ func getRuleForPath(pa extensionsv1beta1.HTTPIngressPath, i *extensionsv1beta1.I
}
rules = append(rules, ruleTypeReplacePath+":"+rootPath)
}

if requestModifier := getStringValue(i.Annotations, annotationKubernetesRequestModifier, ""); requestModifier != "" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if the annotation consists of all whitespaces only?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rude

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, added tests to include a bunch of other oddities

// Split annotation to determine modifier type
modifierParts := strings.Split(strings.Trim(requestModifier, " :"), ":")
if len(modifierParts) < 2 {
return "", fmt.Errorf("could not get rule due to incomplete rule: %q", requestModifier)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this error message different from the one in line 452 to allow distinguishing?

switch modifier {
case ruleTypeAddPrefix, ruleTypeReplacePath, ruleTypeReplacePathRegex:
if ruleType == ruleTypeReplacePath {
return "", fmt.Errorf("cannot use '%s: %s' and '%s: %s'",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not? Can we include a reason?

@@ -31,6 +31,7 @@ const (
annotationKubernetesErrorPages = "ingress.kubernetes.io/error-pages"
annotationKubernetesBuffering = "ingress.kubernetes.io/buffering"
annotationKubernetesAppRoot = "ingress.kubernetes.io/app-root"
annotationKubernetesRequestModifier = "ingress.kubernetes.io/request-modifier"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you possibly check if a similar annotation already exists with Nginx, and if so, whether it makes sense to copy the name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^^ Not really as in: I checked, and nginx does not provide that kind of functionality through an annotation. We are providing "new" functionality with this one.

@@ -156,7 +156,8 @@ The following general annotations are applicable on the Ingress object:
| `traefik.ingress.kubernetes.io/redirect-regex: ^http://localhost/(.*)` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-replacement`. |
| `traefik.ingress.kubernetes.io/redirect-replacement: http://mydomain/$1` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-regex`. |
| `traefik.ingress.kubernetes.io/rewrite-target: /users` | Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header. |
| `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Override the default frontend rule type. Default: `PathPrefix`. |
| `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Override the default frontend rule type. Only path related matchers can be used (`Path`, `PathPrefix`, `PathStrip`, `PathPrefixStrip`). Default: `PathPrefix`. |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about linking to the path matchers documentation here?

@@ -156,7 +156,8 @@ The following general annotations are applicable on the Ingress object:
| `traefik.ingress.kubernetes.io/redirect-regex: ^http://localhost/(.*)` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-replacement`. |
| `traefik.ingress.kubernetes.io/redirect-replacement: http://mydomain/$1` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-regex`. |
| `traefik.ingress.kubernetes.io/rewrite-target: /users` | Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header. |
| `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Override the default frontend rule type. Default: `PathPrefix`. |
| `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Override the default frontend rule type. Only path related matchers can be used (`Path`, `PathPrefix`, `PathStrip`, `PathPrefixStrip`). Default: `PathPrefix`. |
| `traefik.ingress.kubernetes.io/request-modifier: AddPath: /users` | Add a request modifier to the backend request. |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to request modifiers docs from here?

@@ -156,7 +156,8 @@ The following general annotations are applicable on the Ingress object:
| `traefik.ingress.kubernetes.io/redirect-regex: ^http://localhost/(.*)` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-replacement`. |
| `traefik.ingress.kubernetes.io/redirect-replacement: http://mydomain/$1` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-regex`. |
| `traefik.ingress.kubernetes.io/rewrite-target: /users` | Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header. |
| `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Override the default frontend rule type. Default: `PathPrefix`. |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we leave a note that ReplacePath is deprecated?

Copy link
Contributor Author

@dtomcej dtomcej Jun 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess if we are going to leave the message in the log, we might as well note in the docs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could argue that we should phrase the log message even stronger: that the old way will not be deprecated but is from now on effectively. This means that we will still support it but plan to remove support in a future release. At least, that's what I remember we (in particular @ldez) did in the past to migrate options.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have completely remove annotations with the previous "label" style from the documentation.
I think can do the same here.

case ruleTypeReplacePath:
log.Warnf("Using %s as %s will be deprecated in the future. Please use the %s annotation instead", ruleType, annotationKubernetesRuleType, annotationKubernetesRequestModifier)
default:
return "", fmt.Errorf("cannot use nonexistent, or non-matcher rule: %q", ruleType)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding a case "": and log the non-existent case separately? This would be slightly clearer as we don't have to log an empty string.

return strings.Join(rules, ";"), nil
}

func parseRequestModifier(requestModifier, ruleType string) (string, error) {
if strings.Trim(requestModifier, " :") == "" {
return "", fmt.Errorf("could not get rule due to empty rule: %q", requestModifier)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd simplify to "rule %q is empty" as the calling site already provides context that the error is about rule retrieval.

// Split annotation to determine modifier type
modifierParts := strings.Split(strings.Trim(requestModifier, " :"), ":")
if len(modifierParts) < 2 {
return "", fmt.Errorf("could not get rule due to incomplete rule (missing rule type or value): %q", requestModifier)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion to simplify to "rule %q is missing type or value".

return strings.Join(rules, ";"), nil
}

func parseRequestModifier(requestModifier, ruleType string) (string, error) {
if strings.Trim(requestModifier, " :") == "" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TrimRight should suffice, shouldn't it?

switch modifier {
case ruleTypeAddPrefix, ruleTypeReplacePath, ruleTypeReplacePathRegex:
if ruleType == ruleTypeReplacePath {
return "", fmt.Errorf("cannot use '%s: %s' and '%s: %s, as this leads to rule duplication, and unintended behavior'",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing closing '.

annotationKubernetesRuleType, ruleTypeReplacePath, annotationKubernetesRequestModifier, modifier)
}
default:
return "", fmt.Errorf("cannot use nonexistent, or non-modifier rule: %q", modifier)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion for separate empty string case here too.

Copy link
Contributor

@nmengin nmengin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@timoreimann timoreimann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

Copy link
Contributor

@ldez ldez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

dtomcej and others added 2 commits July 6, 2018 07:48
wording
fix review
add tests
update regex to be more friendly
add docs
allll the tests
add space
review: simplify.
review: parseRequestModifier.
review: doc.
fix error wording
fix review
@@ -154,7 +154,8 @@ The following general annotations are applicable on the Ingress object:
| `traefik.ingress.kubernetes.io/redirect-regex: ^http://localhost/(.*)` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-replacement`. |
| `traefik.ingress.kubernetes.io/redirect-replacement: http://mydomain/$1` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-regex`. |
| `traefik.ingress.kubernetes.io/rewrite-target: /users` | Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header. |
| `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Override the default frontend rule type. Default: `PathPrefix`. |
| `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Override the default frontend rule type. Only path related matchers can be used [(`Path`, `PathPrefix`, `PathStrip`, `PathPrefixStrip`)](/basics/#path-matcher-usage-guidelines). Note: ReplacePath is deprecated in this annotation, use the `traefik.ingress.kubernetes.io/request-modifier` annotation instead. Default: `PathPrefix`. |
| `traefik.ingress.kubernetes.io/request-modifier: AddPath: /users` | Add a [request modifier](/basics/#modifiers) to the backend request. |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like there is a type - should'n it be AddPrefix instead of AddPath?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tompson thanks. After changing to AddPrefix my ingress started working :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ferrarimarco can you maybe share your ingress configuration? I still cannot get mine working

Copy link

@ferrarimarco ferrarimarco Jul 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tompson

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: a12
  labels:
    app: a1
    chart: a1
    release: a1
    heritage: Tiller
  annotations:
    kubernetes.io/ingress.class: traefik
    traefik.ingress.kubernetes.io/preserve-host: "true"
    traefik.ingress.kubernetes.io/app-root: "/index.html"
    traefik.ingress.kubernetes.io/request-modifier: "AddPrefix: /a1"
spec:
  rules:
    - host: host1.tld
      http:
        paths:
          - path: /test/aaaa
            backend:
              serviceName: service-name
              servicePort: http

Copy link
Contributor

@tompson tompson Jul 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much!

Unfortunately I am still not able to get my desired configuration to work.

I got a backend that is available under /app and I want a traefik configuration that allows user to call it with /app or with /oldapp - both ways should work exactly the same.

I thought my config should look something like

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: default
  annotations:
    kubernetes.io/ingress.class: traefik
spec:
  rules:
  - host: httpecho.kube.troii.net
    http: 
      paths:
      - path: /app
        backend:
          serviceName: echo-server
          servicePort: 80
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: default
  annotations:
    kubernetes.io/ingress.class: traefik
    traefik.ingress.kubernetes.io/request-modifier: "AddPrefix: /app"
spec:
  rules:
  - host: httpecho.kube.troii.net
    http: 
      paths:
      - path: /oldapp
        backend:
          serviceName: echo-server
          servicePort: 80
---

Copy link
Contributor Author

@dtomcej dtomcej Jul 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: default
  annotations:
    kubernetes.io/ingress.class: traefik
    traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip
    traefik.ingress.kubernetes.io/request-modifier: "AddPrefix: /app"
spec:
  rules:
  - host: httpecho.kube.troii.net
    http: 
      paths:
      - path: /oldapp
        backend:
          serviceName: echo-server
          servicePort: 80
      - path: /app
        backend:
          serviceName: echo-server
          servicePort: 80

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @dtomcej ! should this already work with v1.6.5 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into the commit history and it seems like this will be in included in 1.7.0 but is not part of 1.6.5 - so I will have to wait to try it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Incorrect frontend modifiers in Kubernetes
8 participants