Merge branch 'master' into mrg-current-v2.4

This commit is contained in:
Tom Moulard 2021-04-29 14:24:07 +02:00 committed by GitHub
commit 0624cefc10
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
147 changed files with 6252 additions and 712 deletions

View file

@ -2,6 +2,7 @@ package main
import (
"context"
"crypto/x509"
"encoding/json"
stdlog "log"
"net/http"
@ -14,6 +15,7 @@ import (
"github.com/coreos/go-systemd/daemon"
assetfs "github.com/elazarl/go-bindata-assetfs"
"github.com/go-acme/lego/v4/challenge"
gokitmetrics "github.com/go-kit/kit/metrics"
"github.com/sirupsen/logrus"
"github.com/traefik/paerser/cli"
"github.com/traefik/traefik/v2/autogen/genstatic"
@ -267,6 +269,11 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
watcher.AddListener(func(conf dynamic.Configuration) {
ctx := context.Background()
tlsManager.UpdateConfigs(ctx, conf.TLS.Stores, conf.TLS.Options, conf.TLS.Certificates)
gauge := metricsRegistry.TLSCertsNotAfterTimestampGauge()
for _, certificate := range tlsManager.GetCertificates() {
appendCertMetric(gauge, certificate)
}
})
// Metrics
@ -441,6 +448,20 @@ func registerMetricClients(metricsConfig *types.Metrics) []metrics.Registry {
return registries
}
func appendCertMetric(gauge gokitmetrics.Gauge, certificate *x509.Certificate) {
sort.Strings(certificate.DNSNames)
labels := []string{
"cn", certificate.Subject.CommonName,
"serial", certificate.SerialNumber.String(),
"sans", strings.Join(certificate.DNSNames, ","),
}
notAfter := float64(certificate.NotAfter.Unix())
gauge.With(labels...).Set(notAfter)
}
func setupAccessLog(conf *types.AccessLog) *accesslog.Handler {
if conf == nil {
return nil

116
cmd/traefik/traefik_test.go Normal file
View file

@ -0,0 +1,116 @@
package main
import (
"crypto/x509"
"encoding/pem"
"strings"
"testing"
"github.com/go-kit/kit/metrics"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// FooCert is a PEM-encoded TLS cert.
// generated from src/crypto/tls:
// go run generate_cert.go --rsa-bits 1024 --host foo.org,foo.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
const fooCert = `-----BEGIN CERTIFICATE-----
MIICHzCCAYigAwIBAgIQXQFLeYRwc5X21t457t2xADANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
iQKBgQDCjn67GSs/khuGC4GNN+tVo1S+/eSHwr/hWzhfMqO7nYiXkFzmxi+u14CU
Pda6WOeps7T2/oQEFMxKKg7zYOqkLSbjbE0ZfosopaTvEsZm/AZHAAvoOrAsIJOn
SEiwy8h0tLA4z1SNR6rmIVQWyqBZEPAhBTQM1z7tFp48FakCFwIDAQABo3QwcjAO
BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUDHG3ASzeUezElup9zbPpBn/vjogwGwYDVR0RBBQwEoIH
Zm9vLm9yZ4IHZm9vLmNvbTANBgkqhkiG9w0BAQsFAAOBgQBT+VLMbB9u27tBX8Aw
ZrGY3rbNdBGhXVTksrjiF+6ZtDpD3iI56GH9zLxnqvXkgn3u0+Ard5TqF/xmdwVw
NY0V/aWYfcL2G2auBCQrPvM03ozRnVUwVfP23eUzX2ORNHCYhd2ObQx4krrhs7cJ
SWxtKwFlstoXY3K2g9oRD9UxdQ==
-----END CERTIFICATE-----`
// BarCert is a PEM-encoded TLS cert.
// generated from src/crypto/tls:
// go run generate_cert.go --rsa-bits 1024 --host bar.org,bar.com --ca --start-date "Jan 1 00:00:00 1970" --duration=10000h
const barCert = `-----BEGIN CERTIFICATE-----
MIICHTCCAYagAwIBAgIQcuIcNEXzBHPoxna5S6wG4jANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMB4XDTcwMDEwMTAwMDAwMFoXDTcxMDIyMTE2MDAw
MFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
gYEAqtcrP+KA7D6NjyztGNIPMup9KiBMJ8QL+preog/YHR7SQLO3kGFhpS3WKMab
SzMypC3ZX1PZjBP5ZzwaV3PFbuwlCkPlyxR2lOWmullgI7mjY0TBeYLDIclIzGRp
mpSDDSpkW1ay2iJDSpXjlhmwZr84hrCU7BRTQJo91fdsRTsCAwEAAaN0MHIwDgYD
VR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMB
Af8wHQYDVR0OBBYEFK8jnzFQvBAgWtfzOyXY4VSkwrTXMBsGA1UdEQQUMBKCB2Jh
ci5vcmeCB2Jhci5jb20wDQYJKoZIhvcNAQELBQADgYEAJz0ifAExisC/ZSRhWuHz
7qs1i6Nd4+YgEVR8dR71MChP+AMxucY1/ajVjb9xlLys3GPE90TWSdVppabEVjZY
Oq11nPKc50ItTt8dMku6t0JHBmzoGdkN0V4zJCBqdQJxhop8JpYJ0S9CW0eT93h3
ipYQSsmIINGtMXJ8VkP/MlM=
-----END CERTIFICATE-----`
type gaugeMock struct {
metrics map[string]float64
labels string
}
func (g gaugeMock) With(labelValues ...string) metrics.Gauge {
g.labels = strings.Join(labelValues, ",")
return g
}
func (g gaugeMock) Set(value float64) {
g.metrics[g.labels] = value
}
func (g gaugeMock) Add(delta float64) {
panic("implement me")
}
func TestAppendCertMetric(t *testing.T) {
testCases := []struct {
desc string
certs []string
expected map[string]float64
}{
{
desc: "No certs",
certs: []string{},
expected: map[string]float64{},
},
{
desc: "One cert",
certs: []string{fooCert},
expected: map[string]float64{
"cn,,serial,123624926713171615935660664614975025408,sans,foo.com,foo.org": 3.6e+09,
},
},
{
desc: "Two certs",
certs: []string{fooCert, barCert},
expected: map[string]float64{
"cn,,serial,123624926713171615935660664614975025408,sans,foo.com,foo.org": 3.6e+09,
"cn,,serial,152706022658490889223053211416725817058,sans,bar.com,bar.org": 3.6e+07,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
gauge := &gaugeMock{
metrics: map[string]float64{},
}
for _, cert := range test.certs {
block, _ := pem.Decode([]byte(cert))
parsedCert, err := x509.ParseCertificate(block.Bytes)
require.NoError(t, err)
appendCertMetric(gauge, parsedCert)
}
assert.Equal(t, test.expected, gauge.metrics)
})
}
}

View file

@ -31,20 +31,6 @@ whoami:
```
```yaml tab="Kubernetes IngressRoute"
# As a Kubernetes Traefik IngressRoute
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: middlewares.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: Middleware
plural: middlewares
singular: middleware
scope: Namespaced
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware

View file

@ -364,3 +364,25 @@ For more information, please read the [HTTP routers rule](../routing/routers/ind
### Tracing Span
In `v2.4.9`, we changed span error to log only server errors (>= 500).
## v2.4 to v2.5
### Kubernetes CRD
In `v2.5`, the [Traefik CRDs](../reference/dynamic-configuration/kubernetes-crd.md#definitions) have been updated to support the new API version `apiextensions.k8s.io/v1`.
As required by `apiextensions.k8s.io/v1`, we have included the OpenAPI validation schema.
After deploying the new [Traefik CRDs](../reference/dynamic-configuration/kubernetes-crd.md#definitions), the resources will be validated only on creation or update.
Please note that the unknown fields will not be pruned when migrating from `apiextensions.k8s.io/v1beta1` to `apiextensions.k8s.io/v1` CRDs.
For more details check out the official [documentation](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema).
### Kubernetes Ingress
Traefik v2.5 moves forward for the Ingress provider to support Kubernetes v1.22.
Traefik now supports only v1.14+ Kubernetes clusters, which means the support of `extensions/v1beta1` API Version ingresses has been dropped.
The `extensions/v1beta1` API Version should now be replaced either by `networking.k8s.io/v1beta1` or by `networking.k8s.io/v1` (as of Kubernetes v1.19+).
The support of the `networking.k8s.io/v1beta1` API Version will stop in Kubernetes v1.22.

View file

@ -209,7 +209,7 @@ accessLog:
| `RequestScheme` | The HTTP scheme requested `http` or `https`. |
| `RequestLine` | `RequestMethod` + `RequestPath` + `RequestProtocol` |
| `RequestContentSize` | The number of bytes in the request entity (a.k.a. body) sent by the client. |
| `OriginDuration` | The time taken (in nanoseconds) by the origin server ('upstream') to return its response. |
| `OriginDuration` | The time taken (in nanoseconds) by the origin server ('upstream') to return its response. |
| `OriginContentSize` | The content length specified by the origin server, or 0 if unspecified. |
| `OriginStatus` | The HTTP status code returned by the origin server. If the request was handled by this Traefik instance (e.g. with a redirect), then this value will be absent. |
| `OriginStatusLine` | `OriginStatus` + Status code explanation |
@ -218,8 +218,10 @@ accessLog:
| `DownstreamContentSize` | The number of bytes in the response entity returned to the client. This is in addition to the "Content-Length" header, which may be present in the origin response. |
| `RequestCount` | The number of requests received since the Traefik instance started. |
| `GzipRatio` | The response body compression ratio achieved. |
| `Overhead` | The processing time overhead (in nanoseconds) caused by Traefik. |
| `Overhead` | The processing time overhead (in nanoseconds) caused by Traefik. |
| `RetryAttempts` | The amount of attempts the request was retried. |
| `TLSVersion` | The TLS version used by the connection (e.g. `1.2`) (if connection is TLS). |
| `TLSCipher` | The TLS cipher used by the connection (e.g. `TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`) (if connection is TLS) |
## Log Rotation

View file

@ -20,11 +20,17 @@ the Traefik engineering team developed a [Custom Resource Definition](https://ku
* Apply the needed kubernetesCRD provider [configuration](#provider-configuration)
* Add all necessary Traefik custom [resources](../reference/dynamic-configuration/kubernetes-crd.md#resources)
!!! warning "Deprecated apiextensions.k8s.io/v1beta1 CRD"
The `apiextensions.k8s.io/v1beta1` CustomResourceDefinition is deprecated in Kubernetes `v1.16+` and will be removed in `v1.22+`.
For Kubernetes `v1.16+`, please use the Traefik `apiextensions.k8s.io/v1` CRDs instead.
??? example "Initializing Resource Definition and RBAC"
```yaml tab="Traefik Resource Definition"
# All resources definition must be declared
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition.yml"
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml"
```
```yaml tab="RBAC for Traefik CRD"

View file

@ -6,6 +6,10 @@ The Kubernetes Ingress Controller.
The Traefik Kubernetes Ingress provider is a Kubernetes Ingress controller; that is to say,
it manages access to cluster services by supporting the [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) specification.
## Requirements
Traefik supports `1.14+` Kubernetes clusters.
## Routing Configuration
See the dedicated section in [routing](../routing/providers/kubernetes-ingress.md).
@ -31,9 +35,9 @@ The provider then watches for incoming ingresses events, such as the example bel
and derives the corresponding dynamic configuration from it,
which in turn creates the resulting routers, services, handlers, etc.
```yaml tab="File (YAML)"
```yaml tab="Ingress"
kind: Ingress
apiVersion: extensions/v1beta1
apiVersion: networking.k8s.io/v1beta1
metadata:
name: "foo"
namespace: production
@ -53,6 +57,32 @@ spec:
servicePort: 80
```
```yaml tab="Ingress Kubernetes v1.19+"
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: "foo"
namespace: production
spec:
rules:
- host: example.net
http:
paths:
- path: /bar
backend:
service:
name: service1
port:
number: 80
- path: /foo
backend:
service:
name: service1
port:
number: 80
```
## LetsEncrypt Support with the Ingress Provider
By design, Traefik is a stateless application,
@ -220,12 +250,14 @@ Value of `kubernetes.io/ingress.class` annotation that identifies Ingress object
If the parameter is set, only Ingresses containing an annotation with the same value are processed.
Otherwise, Ingresses missing the annotation, having an empty value, or the value `traefik` are processed.
!!! info "Kubernetes 1.18+"
??? info "Kubernetes 1.18+"
If the Kubernetes cluster version is 1.18+,
the new `IngressClass` resource can be leveraged to identify Ingress objects that should be processed.
In that case, Traefik will look for an `IngressClass` in the cluster with the controller value equal to *traefik.io/ingress-controller*.
In addition to the controller value matching mechanism, the property `ingressClass` (if set) will be used to select IngressClasses by applying a strict matching on their name.
Please see [this article](https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/) for more information or the example below.
```yaml tab="IngressClass"
@ -254,6 +286,39 @@ Otherwise, Ingresses missing the annotation, having an empty value, or the value
servicePort: 80
```
??? info "Kubernetes 1.19+"
If the Kubernetes cluster version is 1.19+,
prefer using the `networking.k8s.io/v1` [apiVersion](https://v1-19.docs.kubernetes.io/docs/setup/release/notes/#api-change) of `Ingress` and `IngressClass`.
```yaml tab="IngressClass"
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: traefik-lb
spec:
controller: traefik.io/ingress-controller
```
```yaml tab="Ingress"
apiVersion: "networking.k8s.io/v1"
kind: "Ingress"
metadata:
name: "example-ingress"
spec:
ingressClassName: "traefik-lb"
rules:
- host: "*.example.com"
http:
paths:
- path: "/example"
backend:
service:
name: "example-service"
port:
number: 80
```
```toml tab="File (TOML)"
[providers.kubernetesIngress]
ingressClass = "traefik-internal"

View file

@ -275,6 +275,7 @@
insecureSkipVerify = true
rootCAs = ["foobar", "foobar"]
maxIdleConnsPerHost = 42
disableHTTP2 = true
[[http.serversTransports.ServersTransport0.certificates]]
certFile = "foobar"
@ -292,6 +293,7 @@
insecureSkipVerify = true
rootCAs = ["foobar", "foobar"]
maxIdleConnsPerHost = 42
disableHTTP2 = true
[[http.serversTransports.ServersTransport1.certificates]]
certFile = "foobar"

View file

@ -327,6 +327,7 @@ http:
dialTimeout: 42s
responseHeaderTimeout: 42s
idleConnTimeout: 42s
disableHTTP2: true
ServersTransport1:
serverName: foobar
insecureSkipVerify: true
@ -343,6 +344,7 @@ http:
dialTimeout: 42s
responseHeaderTimeout: 42s
idleConnTimeout: 42s
disableHTTP2: true
tcp:
routers:
TCPRouter0:

View file

@ -0,0 +1,8 @@
--8<-- "content/reference/dynamic-configuration/traefik.containo.us_ingressroutes.yaml"
--8<-- "content/reference/dynamic-configuration/traefik.containo.us_ingressroutetcps.yaml"
--8<-- "content/reference/dynamic-configuration/traefik.containo.us_ingressrouteudps.yaml"
--8<-- "content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml"
--8<-- "content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml"
--8<-- "content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml"
--8<-- "content/reference/dynamic-configuration/traefik.containo.us_tlsstores.yaml"
--8<-- "content/reference/dynamic-configuration/traefik.containo.us_traefikservices.yaml"

View file

@ -63,10 +63,10 @@ spec:
mirroring:
name: wrr2
kind: TraefikService
# Optional
maxBodySize: 2000000000
mirrors:
- name: s2
# Optional
maxBodySize: 2000000000
# Optional, as it is the default value
kind: Service
percent: 20
@ -77,6 +77,7 @@ apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: ingressroute
spec:
entryPoints:
- web
@ -98,6 +99,7 @@ spec:
port: 433
serversTransport: mytransport
- match: PathPrefix(`/misc`)
kind: Rule
services:
- name: s3
port: 80
@ -105,6 +107,7 @@ spec:
- name: stripprefix
- name: addprefix
- match: PathPrefix(`/misc`)
kind: Rule
services:
- name: s3
# Optional, as it is the default value
@ -113,10 +116,12 @@ spec:
# scheme allow to override the scheme for the service. (ex: https or h2c)
scheme: https
- match: PathPrefix(`/lb`)
kind: Rule
services:
- name: wrr1
kind: TraefikService
- match: PathPrefix(`/mirrored`)
kind: Rule
services:
- name: mirror1
kind: TraefikService
@ -181,10 +186,10 @@ spec:
- foobar
- foobar
clientAuth:
caFiles:
secretNames:
- foobar
- foobar
clientAuthType: foobar
clientAuthType: RequireAndVerifyClientCert
sniStrict: true
preferServerCipherSuites: true
@ -209,3 +214,4 @@ spec:
dialTimeout: 42s
responseHeaderTimeout: 42s
idleConnTimeout: 42s
disableHTTP2: true

View file

@ -5,8 +5,12 @@ Dynamic configuration with Kubernetes Custom Resource
## Definitions
```yaml
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition.yml"
```yaml tab="apiextensions.k8s.io/v1"
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml"
```
```yaml tab="apiextensions.k8s.io/v1beta1"
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition-v1beta1.yml"
```
## Resources

View file

@ -44,3 +44,14 @@ spec:
- serviceName: whoami
port: 80
weight: 1
- matches:
- path:
type: Prefix
value: /foo
forwardTo:
- backendRef:
group: traefik.containo.us
kind: TraefikService
name: myservice@file
weight: 1
port: 80

View file

@ -0,0 +1,179 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: ingressroutes.traefik.containo.us
spec:
group: traefik.containo.us
names:
kind: IngressRoute
listKind: IngressRouteList
plural: ingressroutes
singular: ingressroute
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: IngressRoute is an Ingress CRD specification.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: IngressRouteSpec is a specification for a IngressRouteSpec resource.
properties:
entryPoints:
items:
type: string
type: array
routes:
items:
description: Route contains the set of routes.
properties:
kind:
enum:
- Rule
type: string
match:
type: string
middlewares:
items:
description: MiddlewareRef is a ref to the Middleware resources.
properties:
name:
type: string
namespace:
type: string
required:
- name
type: object
type: array
priority:
type: integer
services:
items:
description: Service defines an upstream to proxy traffic.
properties:
kind:
enum:
- Service
- TraefikService
type: string
name:
description: Name is a reference to a Kubernetes Service object (for a load-balancer of servers), or to a TraefikService object (service load-balancer, mirroring, etc). The differentiation between the two is specified in the Kind field.
type: string
namespace:
type: string
passHostHeader:
type: boolean
port:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
responseForwarding:
description: ResponseForwarding holds configuration for the forward of the response.
properties:
flushInterval:
type: string
type: object
scheme:
type: string
serversTransport:
type: string
sticky:
description: Sticky holds the sticky configuration.
properties:
cookie:
description: Cookie holds the sticky configuration based on cookie.
properties:
httpOnly:
type: boolean
name:
type: string
sameSite:
type: string
secure:
type: boolean
type: object
type: object
strategy:
type: string
weight:
description: Weight should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin).
type: integer
required:
- name
type: object
type: array
required:
- kind
- match
type: object
type: array
tls:
description: "TLS contains the TLS certificates configuration of the routes. To enable Let's Encrypt, use an empty TLS struct, e.g. in YAML: \n \t tls: {} # inline format \n \t tls: \t secretName: # block format"
properties:
certResolver:
type: string
domains:
items:
description: Domain holds a domain name with SANs.
properties:
main:
type: string
sans:
items:
type: string
type: array
type: object
type: array
options:
description: Options is a reference to a TLSOption, that specifies the parameters of the TLS connection.
properties:
name:
type: string
namespace:
type: string
required:
- name
type: object
secretName:
description: SecretName is the name of the referenced Kubernetes Secret to specify the certificate details.
type: string
store:
description: Store is a reference to a TLSStore, that specifies the parameters of the TLS store.
properties:
name:
type: string
namespace:
type: string
required:
- name
type: object
type: object
required:
- routes
type: object
required:
- metadata
- spec
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View file

@ -0,0 +1,134 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: ingressroutetcps.traefik.containo.us
spec:
group: traefik.containo.us
names:
kind: IngressRouteTCP
listKind: IngressRouteTCPList
plural: ingressroutetcps
singular: ingressroutetcp
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: IngressRouteTCP is an Ingress CRD specification.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: IngressRouteTCPSpec is a specification for a IngressRouteTCPSpec resource.
properties:
entryPoints:
items:
type: string
type: array
routes:
items:
description: RouteTCP contains the set of routes.
properties:
match:
type: string
services:
items:
description: ServiceTCP defines an upstream to proxy traffic.
properties:
name:
type: string
namespace:
type: string
port:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
proxyProtocol:
description: ProxyProtocol holds the ProxyProtocol configuration.
properties:
version:
type: integer
type: object
terminationDelay:
type: integer
weight:
type: integer
required:
- name
- port
type: object
type: array
required:
- match
type: object
type: array
tls:
description: "TLSTCP contains the TLS certificates configuration of the routes. To enable Let's Encrypt, use an empty TLS struct, e.g. in YAML: \n \t tls: {} # inline format \n \t tls: \t secretName: # block format"
properties:
certResolver:
type: string
domains:
items:
description: Domain holds a domain name with SANs.
properties:
main:
type: string
sans:
items:
type: string
type: array
type: object
type: array
options:
description: Options is a reference to a TLSOption, that specifies the parameters of the TLS connection.
properties:
name:
type: string
namespace:
type: string
required:
- name
type: object
passthrough:
type: boolean
secretName:
description: SecretName is the name of the referenced Kubernetes Secret to specify the certificate details.
type: string
store:
description: Store is a reference to a TLSStore, that specifies the parameters of the TLS store.
properties:
name:
type: string
namespace:
type: string
required:
- name
type: object
type: object
required:
- routes
type: object
required:
- metadata
- spec
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View file

@ -0,0 +1,79 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: ingressrouteudps.traefik.containo.us
spec:
group: traefik.containo.us
names:
kind: IngressRouteUDP
listKind: IngressRouteUDPList
plural: ingressrouteudps
singular: ingressrouteudp
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: IngressRouteUDP is an Ingress CRD specification.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: IngressRouteUDPSpec is a specification for a IngressRouteUDPSpec resource.
properties:
entryPoints:
items:
type: string
type: array
routes:
items:
description: RouteUDP contains the set of routes.
properties:
services:
items:
description: ServiceUDP defines an upstream to proxy traffic.
properties:
name:
type: string
namespace:
type: string
port:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
weight:
type: integer
required:
- name
- port
type: object
type: array
type: object
type: array
required:
- routes
type: object
required:
- metadata
- spec
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View file

@ -0,0 +1,521 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: middlewares.traefik.containo.us
spec:
group: traefik.containo.us
names:
kind: Middleware
listKind: MiddlewareList
plural: middlewares
singular: middleware
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Middleware is a specification for a Middleware resource.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: MiddlewareSpec holds the Middleware configuration.
properties:
addPrefix:
description: AddPrefix holds the AddPrefix configuration.
properties:
prefix:
type: string
type: object
basicAuth:
description: BasicAuth holds the HTTP basic authentication configuration.
properties:
headerField:
type: string
realm:
type: string
removeHeader:
type: boolean
secret:
type: string
type: object
buffering:
description: Buffering holds the request/response buffering configuration.
properties:
maxRequestBodyBytes:
format: int64
type: integer
maxResponseBodyBytes:
format: int64
type: integer
memRequestBodyBytes:
format: int64
type: integer
memResponseBodyBytes:
format: int64
type: integer
retryExpression:
type: string
type: object
chain:
description: Chain holds a chain of middlewares.
properties:
middlewares:
items:
description: MiddlewareRef is a ref to the Middleware resources.
properties:
name:
type: string
namespace:
type: string
required:
- name
type: object
type: array
type: object
circuitBreaker:
description: CircuitBreaker holds the circuit breaker configuration.
properties:
expression:
type: string
type: object
compress:
description: Compress holds the compress configuration.
properties:
excludedContentTypes:
items:
type: string
type: array
type: object
contentType:
description: ContentType middleware - or rather its unique `autoDetect` option - specifies whether to let the `Content-Type` header, if it has not been set by the backend, be automatically set to a value derived from the contents of the response. As a proxy, the default behavior should be to leave the header alone, regardless of what the backend did with it. However, the historic default was to always auto-detect and set the header if it was nil, and it is going to be kept that way in order to support users currently relying on it. This middleware exists to enable the correct behavior until at least the default one can be changed in a future version.
properties:
autoDetect:
type: boolean
type: object
digestAuth:
description: DigestAuth holds the Digest HTTP authentication configuration.
properties:
headerField:
type: string
realm:
type: string
removeHeader:
type: boolean
secret:
type: string
type: object
errors:
description: ErrorPage holds the custom error page configuration.
properties:
query:
type: string
service:
description: Service defines an upstream to proxy traffic.
properties:
kind:
enum:
- Service
- TraefikService
type: string
name:
description: Name is a reference to a Kubernetes Service object (for a load-balancer of servers), or to a TraefikService object (service load-balancer, mirroring, etc). The differentiation between the two is specified in the Kind field.
type: string
namespace:
type: string
passHostHeader:
type: boolean
port:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
responseForwarding:
description: ResponseForwarding holds configuration for the forward of the response.
properties:
flushInterval:
type: string
type: object
scheme:
type: string
serversTransport:
type: string
sticky:
description: Sticky holds the sticky configuration.
properties:
cookie:
description: Cookie holds the sticky configuration based on cookie.
properties:
httpOnly:
type: boolean
name:
type: string
sameSite:
type: string
secure:
type: boolean
type: object
type: object
strategy:
type: string
weight:
description: Weight should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin).
type: integer
required:
- name
type: object
status:
items:
type: string
type: array
type: object
forwardAuth:
description: ForwardAuth holds the http forward authentication configuration.
properties:
address:
type: string
authRequestHeaders:
items:
type: string
type: array
authResponseHeaders:
items:
type: string
type: array
authResponseHeadersRegex:
type: string
tls:
description: ClientTLS holds TLS specific configurations as client.
properties:
caOptional:
type: boolean
caSecret:
type: string
certSecret:
type: string
insecureSkipVerify:
type: boolean
type: object
trustForwardHeader:
type: boolean
type: object
headers:
description: Headers holds the custom header configuration.
properties:
accessControlAllowCredentials:
description: AccessControlAllowCredentials is only valid if true. false is ignored.
type: boolean
accessControlAllowHeaders:
description: AccessControlAllowHeaders must be used in response to a preflight request with Access-Control-Request-Headers set.
items:
type: string
type: array
accessControlAllowMethods:
description: AccessControlAllowMethods must be used in response to a preflight request with Access-Control-Request-Method set.
items:
type: string
type: array
accessControlAllowOrigin:
description: AccessControlAllowOrigin Can be "origin-list-or-null" or "*". From (https://www.w3.org/TR/cors/#access-control-allow-origin-response-header)
type: string
accessControlAllowOriginList:
description: AccessControlAllowOriginList is a list of allowable origins. Can also be a wildcard origin "*".
items:
type: string
type: array
accessControlAllowOriginListRegex:
description: AccessControlAllowOriginListRegex is a list of allowable origins written following the Regular Expression syntax (https://golang.org/pkg/regexp/).
items:
type: string
type: array
accessControlExposeHeaders:
description: AccessControlExposeHeaders sets valid headers for the response.
items:
type: string
type: array
accessControlMaxAge:
description: AccessControlMaxAge sets the time that a preflight request may be cached.
format: int64
type: integer
addVaryHeader:
description: AddVaryHeader controls if the Vary header is automatically added/updated when the AccessControlAllowOrigin is set.
type: boolean
allowedHosts:
items:
type: string
type: array
browserXssFilter:
type: boolean
contentSecurityPolicy:
type: string
contentTypeNosniff:
type: boolean
customBrowserXSSValue:
type: string
customFrameOptionsValue:
type: string
customRequestHeaders:
additionalProperties:
type: string
type: object
customResponseHeaders:
additionalProperties:
type: string
type: object
featurePolicy:
type: string
forceSTSHeader:
type: boolean
frameDeny:
type: boolean
hostsProxyHeaders:
items:
type: string
type: array
isDevelopment:
type: boolean
publicKey:
type: string
referrerPolicy:
type: string
sslForceHost:
type: boolean
sslHost:
type: string
sslProxyHeaders:
additionalProperties:
type: string
type: object
sslRedirect:
type: boolean
sslTemporaryRedirect:
type: boolean
stsIncludeSubdomains:
type: boolean
stsPreload:
type: boolean
stsSeconds:
format: int64
type: integer
type: object
inFlightReq:
description: InFlightReq limits the number of requests being processed and served concurrently.
properties:
amount:
format: int64
type: integer
sourceCriterion:
description: SourceCriterion defines what criterion is used to group requests as originating from a common source. If none are set, the default is to use the request's remote address field. All fields are mutually exclusive.
properties:
ipStrategy:
description: IPStrategy holds the ip strategy configuration.
properties:
depth:
type: integer
excludedIPs:
items:
type: string
type: array
type: object
requestHeaderName:
type: string
requestHost:
type: boolean
type: object
type: object
ipWhiteList:
description: IPWhiteList holds the ip white list configuration.
properties:
ipStrategy:
description: IPStrategy holds the ip strategy configuration.
properties:
depth:
type: integer
excludedIPs:
items:
type: string
type: array
type: object
sourceRange:
items:
type: string
type: array
type: object
passTLSClientCert:
description: PassTLSClientCert holds the TLS client cert headers configuration.
properties:
info:
description: TLSClientCertificateInfo holds the client TLS certificate info configuration.
properties:
issuer:
description: TLSCLientCertificateDNInfo holds the client TLS certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739
properties:
commonName:
type: boolean
country:
type: boolean
domainComponent:
type: boolean
locality:
type: boolean
organization:
type: boolean
province:
type: boolean
serialNumber:
type: boolean
type: object
notAfter:
type: boolean
notBefore:
type: boolean
sans:
type: boolean
serialNumber:
type: boolean
subject:
description: TLSCLientCertificateDNInfo holds the client TLS certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739
properties:
commonName:
type: boolean
country:
type: boolean
domainComponent:
type: boolean
locality:
type: boolean
organization:
type: boolean
province:
type: boolean
serialNumber:
type: boolean
type: object
type: object
pem:
type: boolean
type: object
plugin:
additionalProperties:
x-kubernetes-preserve-unknown-fields: true
type: object
rateLimit:
description: RateLimit holds the rate limiting configuration for a given router.
properties:
average:
format: int64
type: integer
burst:
format: int64
type: integer
period:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
sourceCriterion:
description: SourceCriterion defines what criterion is used to group requests as originating from a common source. If none are set, the default is to use the request's remote address field. All fields are mutually exclusive.
properties:
ipStrategy:
description: IPStrategy holds the ip strategy configuration.
properties:
depth:
type: integer
excludedIPs:
items:
type: string
type: array
type: object
requestHeaderName:
type: string
requestHost:
type: boolean
type: object
type: object
redirectRegex:
description: RedirectRegex holds the redirection configuration.
properties:
permanent:
type: boolean
regex:
type: string
replacement:
type: string
type: object
redirectScheme:
description: RedirectScheme holds the scheme redirection configuration.
properties:
permanent:
type: boolean
port:
type: string
scheme:
type: string
type: object
replacePath:
description: ReplacePath holds the ReplacePath configuration.
properties:
path:
type: string
type: object
replacePathRegex:
description: ReplacePathRegex holds the ReplacePathRegex configuration.
properties:
regex:
type: string
replacement:
type: string
type: object
retry:
description: Retry holds the retry configuration.
properties:
attempts:
type: integer
initialInterval:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
type: object
stripPrefix:
description: StripPrefix holds the StripPrefix configuration.
properties:
forceSlash:
type: boolean
prefixes:
items:
type: string
type: array
type: object
stripPrefixRegex:
description: StripPrefixRegex holds the StripPrefixRegex configuration.
properties:
regex:
items:
type: string
type: array
type: object
type: object
required:
- metadata
- spec
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View file

@ -0,0 +1,91 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: serverstransports.traefik.containo.us
spec:
group: traefik.containo.us
names:
kind: ServersTransport
listKind: ServersTransportList
plural: serverstransports
singular: serverstransport
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: ServersTransport is a specification for a ServersTransport resource.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: ServersTransportSpec options to configure communication between Traefik and the servers.
properties:
certificatesSecrets:
description: Certificates for mTLS.
items:
type: string
type: array
disableHTTP2:
description: Disable HTTP/2 for connections with backend servers.
type: boolean
forwardingTimeouts:
description: Timeouts for requests forwarded to the backend servers.
properties:
dialTimeout:
anyOf:
- type: integer
- type: string
description: The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists.
x-kubernetes-int-or-string: true
idleConnTimeout:
anyOf:
- type: integer
- type: string
description: The maximum period for which an idle HTTP keep-alive connection will remain open before closing itself.
x-kubernetes-int-or-string: true
responseHeaderTimeout:
anyOf:
- type: integer
- type: string
description: The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists.
x-kubernetes-int-or-string: true
type: object
insecureSkipVerify:
description: Disable SSL certificate verification.
type: boolean
maxIdleConnsPerHost:
description: If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used.
type: integer
rootCAsSecrets:
description: Add cert file for self-signed certificate.
items:
type: string
type: array
serverName:
description: ServerName used to contact the server.
type: string
type: object
required:
- metadata
- spec
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View file

@ -0,0 +1,80 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: tlsoptions.traefik.containo.us
spec:
group: traefik.containo.us
names:
kind: TLSOption
listKind: TLSOptionList
plural: tlsoptions
singular: tlsoption
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: TLSOption is a specification for a TLSOption resource.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: TLSOptionSpec configures TLS for an entry point.
properties:
cipherSuites:
items:
type: string
type: array
clientAuth:
description: ClientAuth defines the parameters of the client authentication part of the TLS connection, if any.
properties:
clientAuthType:
description: ClientAuthType defines the client authentication type to apply.
enum:
- NoClientCert
- RequestClientCert
- VerifyClientCertIfGiven
- RequireAndVerifyClientCert
type: string
secretNames:
description: SecretName is the name of the referenced Kubernetes Secret to specify the certificate details.
items:
type: string
type: array
type: object
curvePreferences:
items:
type: string
type: array
maxVersion:
type: string
minVersion:
type: string
preferServerCipherSuites:
type: boolean
sniStrict:
type: boolean
type: object
required:
- metadata
- spec
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View file

@ -0,0 +1,58 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: tlsstores.traefik.containo.us
spec:
group: traefik.containo.us
names:
kind: TLSStore
listKind: TLSStoreList
plural: tlsstores
singular: tlsstore
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: TLSStore is a specification for a TLSStore resource.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: TLSStoreSpec configures a TLSStore resource.
properties:
defaultCertificate:
description: DefaultCertificate holds a secret name for the TLSOption resource.
properties:
secretName:
description: SecretName is the name of the referenced Kubernetes Secret to specify the certificate details.
type: string
required:
- secretName
type: object
required:
- defaultCertificate
type: object
required:
- metadata
- spec
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View file

@ -0,0 +1,238 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: traefikservices.traefik.containo.us
spec:
group: traefik.containo.us
names:
kind: TraefikService
listKind: TraefikServiceList
plural: traefikservices
singular: traefikservice
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: TraefikService is the specification for a service (that an IngressRoute refers to) that is usually not a terminal service (i.e. not a pod of servers), as opposed to a Kubernetes Service. That is to say, it usually refers to other (children) services, which themselves can be TraefikServices or Services.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: ServiceSpec defines whether a TraefikService is a load-balancer of services or a mirroring service.
properties:
mirroring:
description: Mirroring defines a mirroring service, which is composed of a main load-balancer, and a list of mirrors.
properties:
kind:
enum:
- Service
- TraefikService
type: string
maxBodySize:
format: int64
type: integer
mirrors:
items:
description: MirrorService defines one of the mirrors of a Mirroring service.
properties:
kind:
enum:
- Service
- TraefikService
type: string
name:
description: Name is a reference to a Kubernetes Service object (for a load-balancer of servers), or to a TraefikService object (service load-balancer, mirroring, etc). The differentiation between the two is specified in the Kind field.
type: string
namespace:
type: string
passHostHeader:
type: boolean
percent:
type: integer
port:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
responseForwarding:
description: ResponseForwarding holds configuration for the forward of the response.
properties:
flushInterval:
type: string
type: object
scheme:
type: string
serversTransport:
type: string
sticky:
description: Sticky holds the sticky configuration.
properties:
cookie:
description: Cookie holds the sticky configuration based on cookie.
properties:
httpOnly:
type: boolean
name:
type: string
sameSite:
type: string
secure:
type: boolean
type: object
type: object
strategy:
type: string
weight:
description: Weight should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin).
type: integer
required:
- name
type: object
type: array
name:
description: Name is a reference to a Kubernetes Service object (for a load-balancer of servers), or to a TraefikService object (service load-balancer, mirroring, etc). The differentiation between the two is specified in the Kind field.
type: string
namespace:
type: string
passHostHeader:
type: boolean
port:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
responseForwarding:
description: ResponseForwarding holds configuration for the forward of the response.
properties:
flushInterval:
type: string
type: object
scheme:
type: string
serversTransport:
type: string
sticky:
description: Sticky holds the sticky configuration.
properties:
cookie:
description: Cookie holds the sticky configuration based on cookie.
properties:
httpOnly:
type: boolean
name:
type: string
sameSite:
type: string
secure:
type: boolean
type: object
type: object
strategy:
type: string
weight:
description: Weight should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin).
type: integer
required:
- name
type: object
weighted:
description: WeightedRoundRobin defines a load-balancer of services.
properties:
services:
items:
description: Service defines an upstream to proxy traffic.
properties:
kind:
enum:
- Service
- TraefikService
type: string
name:
description: Name is a reference to a Kubernetes Service object (for a load-balancer of servers), or to a TraefikService object (service load-balancer, mirroring, etc). The differentiation between the two is specified in the Kind field.
type: string
namespace:
type: string
passHostHeader:
type: boolean
port:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
responseForwarding:
description: ResponseForwarding holds configuration for the forward of the response.
properties:
flushInterval:
type: string
type: object
scheme:
type: string
serversTransport:
type: string
sticky:
description: Sticky holds the sticky configuration.
properties:
cookie:
description: Cookie holds the sticky configuration based on cookie.
properties:
httpOnly:
type: boolean
name:
type: string
sameSite:
type: string
secure:
type: boolean
type: object
type: object
strategy:
type: string
weight:
description: Weight should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin).
type: integer
required:
- name
type: object
type: array
sticky:
description: Sticky holds the sticky configuration.
properties:
cookie:
description: Cookie holds the sticky configuration based on cookie.
properties:
httpOnly:
type: boolean
name:
type: string
sameSite:
type: string
secure:
type: boolean
type: object
type: object
type: object
type: object
required:
- metadata
- spec
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View file

@ -102,6 +102,9 @@ Entry points definition. (Default: ```false```)
`--entrypoints.<name>.address`:
Entry point address.
`--entrypoints.<name>.enablehttp3`:
Enable HTTP3. (Default: ```false```)
`--entrypoints.<name>.forwardedheaders.insecure`:
Trust all forwarded headers. (Default: ```false```)
@ -168,12 +171,18 @@ ReadTimeout is the maximum duration for reading the entire request, including th
`--entrypoints.<name>.transport.respondingtimeouts.writetimeout`:
WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```)
`--entrypoints.<name>.udp.timeout`:
Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```)
`--experimental.devplugin.gopath`:
plugin's GOPATH.
`--experimental.devplugin.modulename`:
plugin's module name.
`--experimental.http3`:
Enable HTTP3. (Default: ```false```)
`--experimental.kubernetesgateway`:
Allow the Kubernetes gateway api provider usage. (Default: ```false```)
@ -610,7 +619,7 @@ Kubernetes certificate authority file path (not needed for in-cluster client).
Kubernetes server endpoint (required for external cluster client).
`--providers.kubernetesingress.ingressclass`:
Value of kubernetes.io/ingress.class annotation to watch for.
Value of kubernetes.io/ingress.class annotation or IngressClass name to watch for.
`--providers.kubernetesingress.ingressendpoint.hostname`:
Hostname used for Kubernetes Ingress endpoints.

View file

@ -102,6 +102,9 @@ Entry points definition. (Default: ```false```)
`TRAEFIK_ENTRYPOINTS_<NAME>_ADDRESS`:
Entry point address.
`TRAEFIK_ENTRYPOINTS_<NAME>_ENABLEHTTP3`:
Enable HTTP3. (Default: ```false```)
`TRAEFIK_ENTRYPOINTS_<NAME>_FORWARDEDHEADERS_INSECURE`:
Trust all forwarded headers. (Default: ```false```)
@ -168,12 +171,18 @@ ReadTimeout is the maximum duration for reading the entire request, including th
`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_WRITETIMEOUT`:
WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```)
`TRAEFIK_ENTRYPOINTS_<NAME>_UDP_TIMEOUT`:
Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```)
`TRAEFIK_EXPERIMENTAL_DEVPLUGIN_GOPATH`:
plugin's GOPATH.
`TRAEFIK_EXPERIMENTAL_DEVPLUGIN_MODULENAME`:
plugin's module name.
`TRAEFIK_EXPERIMENTAL_HTTP3`:
Enable HTTP3. (Default: ```false```)
`TRAEFIK_EXPERIMENTAL_KUBERNETESGATEWAY`:
Allow the Kubernetes gateway api provider usage. (Default: ```false```)
@ -610,7 +619,7 @@ Kubernetes certificate authority file path (not needed for in-cluster client).
Kubernetes server endpoint (required for external cluster client).
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_INGRESSCLASS`:
Value of kubernetes.io/ingress.class annotation to watch for.
Value of kubernetes.io/ingress.class annotation or IngressClass name to watch for.
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_INGRESSENDPOINT_HOSTNAME`:
Hostname used for Kubernetes Ingress endpoints.

View file

@ -14,6 +14,7 @@
[entryPoints]
[entryPoints.EntryPoint0]
address = "foobar"
enableHTTP3 = true
[entryPoints.EntryPoint0.transport]
[entryPoints.EntryPoint0.transport.lifeCycle]
requestAcceptGraceTimeout = 42
@ -28,6 +29,8 @@
[entryPoints.EntryPoint0.forwardedHeaders]
insecure = true
trustedIPs = ["foobar", "foobar"]
[entryPoints.EntryPoint0.udp]
timeout = 42
[entryPoints.EntryPoint0.http]
middlewares = ["foobar", "foobar"]
[entryPoints.EntryPoint0.http.redirections]
@ -391,3 +394,5 @@
[experimental.devPlugin]
goPath = "foobar"
moduleName = "foobar"
http3 = true
kubernetesGateway = true

View file

@ -32,6 +32,9 @@ entryPoints:
trustedIPs:
- foobar
- foobar
enableHTTP3: true
udp:
timeout: 42
http:
redirections:
entryPoint:
@ -411,4 +414,6 @@ experimental:
devPlugin:
goPath: foobar
moduleName: foobar
http3: true
kubernetesGateway: true

View file

@ -100,6 +100,7 @@ They can be defined by using a file (TOML or YAML) or CLI arguments.
[entryPoints]
[entryPoints.name]
address = ":8888" # same as ":8888/tcp"
enableHTTP3 = true
[entryPoints.name.transport]
[entryPoints.name.transport.lifeCycle]
requestAcceptGraceTimeout = 42
@ -121,6 +122,7 @@ They can be defined by using a file (TOML or YAML) or CLI arguments.
entryPoints:
name:
address: ":8888" # same as ":8888/tcp"
enableHTTP3: true
transport:
lifeCycle:
requestAcceptGraceTimeout: 42
@ -144,6 +146,7 @@ They can be defined by using a file (TOML or YAML) or CLI arguments.
```bash tab="CLI"
## Static configuration
--entryPoints.name.address=:8888 # same as :8888/tcp
--entryPoints.name.http3=true
--entryPoints.name.transport.lifeCycle.requestAcceptGraceTimeout=42
--entryPoints.name.transport.lifeCycle.graceTimeOut=42
--entryPoints.name.transport.respondingTimeouts.readTimeout=42
@ -218,6 +221,43 @@ If both TCP and UDP are wanted for the same port, two entryPoints definitions ar
Full details for how to specify `address` can be found in [net.Listen](https://golang.org/pkg/net/#Listen) (and [net.Dial](https://golang.org/pkg/net/#Dial)) of the doc for go.
### EnableHTTP3
`enableHTTP3` defines that you want to enable HTTP3 on this `address`.
You can only enable HTTP3 on a TCP entrypoint.
Enabling HTTP3 will automatically add the correct headers for the connection upgrade to HTTP3.
??? info "HTTP3 uses UDP+TLS"
As HTTP3 uses UDP, you can't have a TCP entrypoint with HTTP3 on the same port as a UDP entrypoint.
Since HTTP3 requires the use of TLS, only routers with TLS enabled will be usable with HTTP3.
!!! warning "Enabling Experimental HTTP3"
As the HTTP3 spec is still in draft, HTTP3 support in Traefik is an experimental feature and needs to be activated
in the experimental section of the static configuration.
```toml tab="File (TOML)"
[experimental]
http3 = true
[entryPoints.name]
enableHTTP3 = true
```
```yaml tab="File (YAML)"
experimental:
http3: true
entryPoints:
name:
enableHTTP3: true
```
```bash tab="CLI"
--experimental.http3=true --entrypoints.name.enablehttp3=true
```
### Forwarded Headers
You can configure Traefik to trust the forwarded headers information (`X-Forwarded-*`).
@ -824,3 +864,35 @@ entryPoints:
--entrypoints.websecure.address=:443
--entrypoints.websecure.http.tls.certResolver=leresolver
```
## UDP Options
This whole section is dedicated to options, keyed by entry point, that will apply only to UDP routing.
### Timeout
_Optional, Default=3s_
Timeout defines how long to wait on an idle session before releasing the related resources.
The Timeout value must be greater than zero.
```toml tab="File (TOML)"
[entryPoints.foo]
address = ":8000/udp"
[entryPoints.foo.udp]
timeout = "10s"
```
```yaml tab="File (YAML)"
entryPoints:
foo:
address: ':8000/udp'
udp:
timeout: 10s
```
```bash tab="CLI"
entrypoints.foo.address=:8000/udp
entrypoints.foo.udp.timeout=10s
```

View file

@ -9,7 +9,7 @@ The Kubernetes Ingress Controller, The Custom Resource Way.
```yaml tab="Resource Definition"
# All resources definition must be declared
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition.yml"
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml"
```
```yaml tab="RBAC"
@ -145,7 +145,7 @@ The Kubernetes Ingress Controller, The Custom Resource Way.
spec:
entryPoints:
- fooudp
- udpep
routes:
- kind: Rule
services:
@ -332,7 +332,7 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne
name: foo
namespace: default
passHostHeader: true
port: 80
port: 80 # [9]
responseForwarding:
flushInterval: 1ms
scheme: https
@ -345,38 +345,39 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne
sameSite: none
strategy: RoundRobin
weight: 10
tls: # [9]
secretName: supersecret # [10]
options: # [11]
name: opt # [12]
namespace: default # [13]
certResolver: foo # [14]
domains: # [15]
- main: example.net # [16]
sans: # [17]
tls: # [10]
secretName: supersecret # [11]
options: # [12]
name: opt # [13]
namespace: default # [14]
certResolver: foo # [15]
domains: # [16]
- main: example.net # [17]
sans: # [18]
- a.example.net
- b.example.net
```
| Ref | Attribute | Purpose |
|------|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [1] | `entryPoints` | List of [entry points](../routers/index.md#entrypoints) names |
| [2] | `routes` | List of routes |
| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule) corresponding to an underlying router. |
| [4] | `routes[n].priority` | [Disambiguate](../routers/index.md#priority) rules of the same length, for route matching |
| [5] | `routes[n].middlewares` | List of reference to [Middleware](#kind-middleware) |
| [6] | `middlewares[n].name` | Defines the [Middleware](#kind-middleware) name |
| [7] | `middlewares[n].namespace` | Defines the [Middleware](#kind-middleware) namespace |
| [8] | `routes[n].services` | List of any combination of [TraefikService](#kind-traefikservice) and reference to a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) (See below for `ExternalName Service` setup) |
| [9] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration |
| [10] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) |
| [11] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) |
| [12] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name |
| [13] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace |
| [14] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) |
| [15] | `tls.domains` | List of [domains](../routers/index.md#domains) |
| [16] | `domains[n].main` | Defines the main domain name |
| [17] | `domains[n].sans` | List of SANs (alternative domains) |
| Ref | Attribute | Purpose |
|------|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [1] | `entryPoints` | List of [entry points](../routers/index.md#entrypoints) names |
| [2] | `routes` | List of routes |
| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule) corresponding to an underlying router. |
| [4] | `routes[n].priority` | [Disambiguate](../routers/index.md#priority) rules of the same length, for route matching |
| [5] | `routes[n].middlewares` | List of reference to [Middleware](#kind-middleware) |
| [6] | `middlewares[n].name` | Defines the [Middleware](#kind-middleware) name |
| [7] | `middlewares[n].namespace` | Defines the [Middleware](#kind-middleware) namespace |
| [8] | `routes[n].services` | List of any combination of [TraefikService](#kind-traefikservice) and reference to a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) (See below for `ExternalName Service` setup) |
| [9] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. |
| [10] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration |
| [11] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) |
| [12] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) |
| [13] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name |
| [14] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace |
| [15] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) |
| [16] | `tls.domains` | List of [domains](../routers/index.md#domains) |
| [17] | `domains[n].main` | Defines the main domain name |
| [18] | `domains[n].sans` | List of SANs (alternative domains) |
??? example "Declaring an IngressRoute"
@ -1115,7 +1116,7 @@ Register the `IngressRouteTCP` [kind](../../reference/dynamic-configuration/kube
| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule_1) corresponding to an underlying router |
| [4] | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions (See below for `ExternalName Service` setup) |
| [5] | `services[n].name` | Defines the name of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) |
| [6] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) |
| [6] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. |
| [7] | `services[n].weight` | Defines the weight to apply to the server load balancing |
| [8] | `services[n].terminationDelay` | corresponds to the deadline that the proxy sets, after one of its connected peers indicates it has closed the writing capability of its connection, to close the reading capability as well, hence fully terminating the connection. It is a duration in milliseconds, defaulting to 100. A negative value means an infinite deadline (i.e. the reading capability is never closed). |
| [9] | `proxyProtocol` | Defines the [PROXY protocol](../services/index.md#proxy-protocol) configuration |
@ -1323,9 +1324,9 @@ Register the `IngressRouteUDP` [kind](../../reference/dynamic-configuration/kube
|------|--------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [1] | `entryPoints` | List of [entrypoints](../routers/index.md#entrypoints_1) names |
| [2] | `routes` | List of routes |
| [3] | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions |
| [3] | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions (See below for `ExternalName Service` setup) |
| [4] | `services[n].name` | Defines the name of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) |
| [6] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) |
| [6] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. |
| [7] | `services[n].weight` | Defines the weight to apply to the server load balancing |
??? example "Declaring an IngressRouteUDP"
@ -1349,6 +1350,105 @@ Register the `IngressRouteUDP` [kind](../../reference/dynamic-configuration/kube
weight: 10
```
!!! important "Using Kubernetes ExternalName Service"
Traefik backends creation needs a port to be set, however Kubernetes [ExternalName Service](https://kubernetes.io/fr/docs/concepts/services-networking/service/#externalname) could be defined without any port.
Accordingly, Traefik supports defining a port in two ways:
- only on `IngressRouteUDP` service
- on both sides, you'll be warned if the ports don't match, and the `IngressRouteUDP` service port is used
Thus, in case of two sides port definition, Traefik expects a match between ports.
??? example "Examples"
```yaml tab="IngressRouteUDP"
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteUDP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- services:
- name: external-svc
port: 80
---
apiVersion: v1
kind: Service
metadata:
name: external-svc
namespace: default
spec:
externalName: external.domain
type: ExternalName
```
```yaml tab="ExternalName Service"
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteUDP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- services:
- name: external-svc
---
apiVersion: v1
kind: Service
metadata:
name: external-svc
namespace: default
spec:
externalName: external.domain
type: ExternalName
ports:
- port: 80
```
```yaml tab="Both sides"
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteUDP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- services:
- name: external-svc
port: 80
---
apiVersion: v1
kind: Service
metadata:
name: external-svc
namespace: default
spec:
externalName: external.domain
type: ExternalName
ports:
- port: 80
```
### Kind: `TLSOption`
`TLSOption` is the CRD implementation of a [Traefik "TLS Option"](../../https/tls.md#tls-options).

View file

@ -123,39 +123,49 @@ Kubernetes cluster before creating `HTTPRoute` objects.
metadata:
name: http-app-1
namespace: default
labels: # [1]
labels: # [1]
app: foo
spec:
hostnames: # [2]
hostnames: # [2]
- "whoami"
rules: # [3]
- matches: # [4]
- path: # [5]
type: Exact # [6]
value: /bar # [7]
- headers: # [8]
type: Exact # [9]
values: # [10]
rules: # [3]
- matches: # [4]
- path: # [5]
type: Exact # [6]
value: /bar # [7]
- headers: # [8]
type: Exact # [9]
values: # [10]
- foo: bar
forwardTo: # [11]
- serviceName: whoami # [12]
weight: 1 # [13]
port: 80 # [14]
forwardTo: # [11]
- serviceName: whoami # [12]
weight: 1 # [13]
port: 80 # [14]
- backendRef: # [15]
group: traefik.containo.us # [16]
kind: TraefikService # [17]
name: api@internal # [18]
port: 80
weight: 1
```
| Ref | Attribute | Description |
|------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [1] | `labels` | Labels to match with the `Gateway` labelselector. |
| [2] | `hostnames` | A set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. |
| [3] | `rules` | A list of HTTP matchers, filters and actions. |
| [4] | `matches` | Conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. |
| [5] | `path` | An HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. |
| [6] | `type` | Type of match against the path Value (supported types: `Exact`, `Prefix`). |
| [7] | `value` | The value of the HTTP path to match against. |
| [8] | `headers` | Conditions to select a HTTP route by matching HTTP request headers. |
| [9] | `type` | Type of match for the HTTP request header match against the `values` (supported types: `Exact`). |
| [10] | `values` | A map of HTTP Headers to be matched. It MUST contain at least one entry. |
| [11] | `forwardTo` | The upstream target(s) where the request should be sent. |
| [12] | `serviceName` | The name of the referent service. |
| [13] | `weight` | The proportion of traffic forwarded to a targetRef, computed as weight/(sum of all weights in targetRefs). |
| [14] | `port` | The port of the referent service. |
| Ref | Attribute | Description |
|------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [1] | `labels` | Labels to match with the `Gateway` labelselector. |
| [2] | `hostnames` | A set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. |
| [3] | `rules` | A list of HTTP matchers, filters and actions. |
| [4] | `matches` | Conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. |
| [5] | `path` | An HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. |
| [6] | `type` | Type of match against the path Value (supported types: `Exact`, `Prefix`). |
| [7] | `value` | The value of the HTTP path to match against. |
| [8] | `headers` | Conditions to select a HTTP route by matching HTTP request headers. |
| [9] | `type` | Type of match for the HTTP request header match against the `values` (supported types: `Exact`). |
| [10] | `values` | A map of HTTP Headers to be matched. It MUST contain at least one entry. |
| [11] | `forwardTo` | The upstream target(s) where the request should be sent. |
| [12] | `serviceName` | The name of the referent service. |
| [13] | `weight` | The proportion of traffic forwarded to a targetRef, computed as weight/(sum of all weights in targetRefs). |
| [14] | `port` | The port of the referent service. |
| [15] | `backendRef` | The BackendRef is a reference to a backend (API object within a known namespace) to forward matched requests to. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. Only `TraefikService` is supported. |
| [16] | `group` | Group is the group of the referent. Only `traefik.containo.us` value is supported. |
| [17] | `kind` | Kind is kind of the referent. Only `TraefikService` value is supported. |
| [18] | `name` | Name is the name of the referent. |

View file

@ -85,6 +85,33 @@ which in turn will create the resulting routers, services, handlers, etc.
servicePort: 80
```
```yaml tab="Ingress Kubernetes v1.19+"
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: myingress
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- host: example.com
http:
paths:
- path: /bar
backend:
service:
name: whoami
port:
number: 80
- path: /foo
backend:
service:
name: whoami
port:
number: 80
```
```yaml tab="Traefik"
apiVersion: v1
kind: ServiceAccount
@ -434,6 +461,33 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d
servicePort: 80
```
```yaml tab="Ingress Kubernetes v1.19+"
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: myingress
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
rules:
- host: example.com
http:
paths:
- path: /bar
backend:
service:
name: whoami
port:
number: 80
- path: /foo
backend:
service:
name: whoami
port:
number: 80
```
```yaml tab="Traefik"
apiVersion: v1
kind: ServiceAccount
@ -613,6 +667,34 @@ For more options, please refer to the available [annotations](#on-ingress).
servicePort: 80
```
```yaml tab="Ingress Kubernetes v1.19+"
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: myingress
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: true
spec:
rules:
- host: example.com
http:
paths:
- path: /bar
backend:
service:
name: whoami
port:
number: 80
- path: /foo
backend:
service:
name: whoami
port:
number: 80
```
```yaml tab="Traefik"
apiVersion: v1
kind: ServiceAccount
@ -733,6 +815,31 @@ For more options, please refer to the available [annotations](#on-ingress).
- secretName: supersecret
```
```yaml tab="Ingress Kubernetes v1.19+"
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: foo
namespace: production
spec:
rules:
- host: example.net
http:
paths:
- path: /bar
backend:
service:
name: service1
port:
number: 80
# Only selects which certificate(s) should be loaded from the secret, in order to terminate TLS.
# Doesn't enable TLS for that ingress (hence for the underlying router).
# Please see the TLS annotations on ingress made for that purpose.
tls:
- secretName: supersecret
```
```yaml tab="Secret"
apiVersion: v1
kind: Secret
@ -777,16 +884,30 @@ and will connect via TLS automatically.
Ingresses can be created that look like the following:
```yaml
```yaml tab="Ingress"
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: cheese
spec:
backend:
serviceName: stilton
servicePort: 80
defaultBackend:
serviceName: stilton
serverPort: 80
```
```yaml tab="Ingress Kubernetes v1.19+"
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: cheese
spec:
defaultBackend:
service:
name: stilton
port:
number: 80
```
This ingress follows the Global Default Backend property of ingresses.

View file

@ -993,8 +993,9 @@ So UDP "routers" at this time are pretty much only load-balancers in one form or
It basically means that some state is kept about an ongoing communication between a client and a backend,
notably so that the proxy knows where to forward a response packet from a backend.
As expected, a `timeout` is associated to each of these sessions,
so that they get cleaned out if they go through a period of inactivity longer than a given duration (that is hardcoded to 3 seconds for now).
Making this timeout configurable will be considered later if we get more usage feedback on this matter.
so that they get cleaned out if they go through a period of inactivity longer than a given duration.
Timeout can be configured using the `entryPoints.name.udp.timeout` option as described
under [entry points](../entrypoints/#udp-options).
### EntryPoints

View file

@ -705,6 +705,37 @@ spec:
maxIdleConnsPerHost: 7
```
#### `disableHTTP2`
_Optional, Default=false_
`disableHTTP2` disables HTTP/2 for connections with backend servers.
```toml tab="File (TOML)"
## Dynamic configuration
[http.serversTransports.mytransport]
disableHTTP2 = true
```
```yaml tab="File (YAML)"
## Dynamic configuration
http:
serversTransports:
mytransport:
disableHTTP2: true
```
```yaml tab="Kubernetes"
apiVersion: traefik.containo.us/v1alpha1
kind: ServersTransport
metadata:
name: mytransport
namespace: default
spec:
disableHTTP2: true
```
#### `forwardingTimeouts`
`forwardingTimeouts` is about a number of timeouts relevant to when forwarding requests to the backend servers.

View file

@ -43,7 +43,7 @@ First, the definition of the `IngressRoute` and the `Middleware` kinds.
Also note the RBAC authorization resources; they'll be referenced through the `serviceAccountName` of the deployment, later on.
```yaml
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition.yml"
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml"
---
--8<-- "content/reference/dynamic-configuration/kubernetes-crd-rbac.yml"

7
go.mod
View file

@ -6,8 +6,7 @@ go 1.16
require (
github.com/BurntSushi/toml v0.3.1
github.com/ExpediaDotCom/haystack-client-go v0.0.0-20190315171017-e7edbdf53a61
github.com/Masterminds/semver v1.4.2 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible
github.com/Masterminds/sprig/v3 v3.2.0
github.com/Microsoft/hcsshim v0.8.7 // indirect
github.com/Shopify/sarama v1.23.1 // indirect
github.com/abbot/go-http-auth v0.0.0-00010101000000-000000000000
@ -30,7 +29,6 @@ require (
github.com/eapache/channels v1.1.0
github.com/elazarl/go-bindata-assetfs v1.0.0
github.com/fatih/structs v1.1.0
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2
github.com/go-acme/lego/v4 v4.3.1
github.com/go-check/check v0.0.0-00010101000000-000000000000
@ -47,6 +45,7 @@ require (
github.com/libkermit/compose v0.0.0-20171122111507-c04e39c026ad
github.com/libkermit/docker v0.0.0-20171122101128-e6674d32b807
github.com/libkermit/docker-check v0.0.0-20171122104347-1113af38e591
github.com/lucas-clemente/quic-go v0.20.1
github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f
github.com/miekg/dns v1.1.40
github.com/mitchellh/copystructure v1.0.0
@ -90,10 +89,12 @@ require (
gopkg.in/fsnotify.v1 v1.4.7
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
k8s.io/api v0.20.2
k8s.io/apiextensions-apiserver v0.20.1
k8s.io/apimachinery v0.20.2
k8s.io/client-go v0.20.2
k8s.io/code-generator v0.20.2
mvdan.cc/xurls/v2 v2.1.0
sigs.k8s.io/controller-tools v0.4.1
sigs.k8s.io/gateway-api v0.2.0
)

126
go.sum
View file

@ -1,5 +1,7 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
@ -23,7 +25,12 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/Azure/azure-sdk-for-go v32.4.0+incompatible h1:1JP8SKfroEakYiQU2ZyPDosh8w2Tg9UopKt88VyQPt4=
github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
@ -76,14 +83,12 @@ github.com/ExpediaDotCom/haystack-client-go v0.0.0-20190315171017-e7edbdf53a61/g
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TNDYD9Y=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA=
github.com/Masterminds/sprig/v3 v3.2.0 h1:P1ekkbuU73Ui/wS0nK1HOM37hh4xdfZo485UPf8rc+Y=
github.com/Masterminds/sprig/v3 v3.2.0/go.mod h1:tWhwTbUTndesPNeF0C900vKoq283u6zp4APT9vaF3SI=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/Microsoft/hcsshim v0.8.7 h1:ptnOoufxGSzauVTsdE+wMYnCWA301PdoN4xg5oRdZpg=
@ -119,6 +124,7 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
github.com/aliyun/alibaba-cloud-sdk-go v1.61.976 h1:I9fs4eZbZqimF3TstEqEwK66R2b7QKd6D6OCxibSD60=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.976/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@ -149,6 +155,8 @@ github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
@ -161,6 +169,8 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -210,6 +220,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
@ -293,6 +304,7 @@ github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQo
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/exoscale/egoscale v0.46.0 h1:i1Ut7oqFaOV8opNP9CHcwTnHryIAcTvL3pwZusQEGrA=
github.com/exoscale/egoscale v0.46.0/go.mod h1:mpEXBpROAa/2i5GC0r33rfxG+TxSEka11g1PIXt9+zc=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
@ -302,6 +314,7 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjr
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@ -313,6 +326,7 @@ github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-acme/lego/v4 v4.3.1 h1:rzmg0Gpy25B/exXjl+KgpG5Xt6wN5rFTLjRf/Uf3pfg=
@ -389,6 +403,7 @@ github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobuffalo/flect v0.2.0 h1:EWCvMGGxOjsgwlWaP+f4+Hh6yrrte7JeFL2S6b+0hdM=
github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
@ -408,11 +423,15 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -442,6 +461,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo=
github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
@ -461,6 +482,9 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
@ -491,6 +515,7 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI=
@ -555,8 +580,10 @@ github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.10 h1:6q5mVkdH/vYmqngx7kZQTjJ5HRsx+ImorDIEQ+beJgc=
github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
@ -566,6 +593,7 @@ github.com/jarcoal/httpmock v1.0.6 h1:e81vOSexXU3mJuJ4l//geOmKIt+Vkxerk1feQBC8D0
github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03 h1:FUwcHNlEqkqLjLBdCp5PRlCFijNjvcYANOZXzCfXwCM=
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
@ -603,6 +631,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@ -631,6 +660,9 @@ github.com/liquidweb/liquidweb-go v1.6.3 h1:NVHvcnX3eb3BltiIoA+gLYn15nOpkYkdizOE
github.com/liquidweb/liquidweb-go v1.6.3/go.mod h1:SuXXp+thr28LnjEw18AYtWwIbWMHSUiajPQs8T9c/Rc=
github.com/looplab/fsm v0.1.0 h1:Qte7Zdn/5hBNbXzP7yxVU4OIFHWXBovyTT2LaBTyC20=
github.com/looplab/fsm v0.1.0/go.mod h1:m2VaOfDHxqXBBMgc26m6yUOwkFn8H2AlJDE+jd/uafI=
github.com/lucas-clemente/quic-go v0.20.1 h1:hb5m76V8QS/8Nw/suHvXqo3BMHAozvIkcnzpJdpanSk=
github.com/lucas-clemente/quic-go v0.20.1/go.mod h1:fZq/HUDIM+mW6X6wtzORjC0E/WDBMKe5Hf9bgjISwLk=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@ -647,11 +679,18 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
github.com/marten-seemann/qtls-go1-15 v0.1.4 h1:RehYMOyRW8hPVEja1KBVsFVNSm35Jj9Mvs5yNoZZ28A=
github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
github.com/marten-seemann/qtls-go1-16 v0.1.3 h1:XEZ1xGorVy9u+lJq+WXNE+hiqRYLNvJGYmwfwKQN2gU=
github.com/marten-seemann/qtls-go1-16 v0.1.3/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@ -668,6 +707,7 @@ github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvr
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
@ -713,6 +753,8 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nrdcg/auroradns v1.0.1 h1:m/kBq83Xvy3cU261MOknd8BdnOk12q4lAWM+kOdsC2Y=
github.com/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI=
github.com/nrdcg/desec v0.5.0 h1:foL7hqivYOMlv0qDhHXJtuuEXkqf0wW9EQMqyrt228g=
@ -736,6 +778,7 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4=
github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
@ -767,6 +810,7 @@ github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsq
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI=
@ -807,6 +851,7 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
@ -823,6 +868,7 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
@ -832,6 +878,7 @@ github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
@ -866,7 +913,31 @@ github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHi
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@ -882,6 +953,8 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
@ -892,6 +965,7 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
@ -924,6 +998,7 @@ github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 h1:XGopsea1Dw7
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tinylib/msgp v1.0.2 h1:DfdQrzQa7Yh2es9SuLkixqxuXS2SxsdYn0KbdrOGWD8=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
@ -962,6 +1037,8 @@ github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPU
github.com/vdemeester/shakers v0.1.0 h1:K+n9sSyUCg2ywmZkv+3c7vsYZfivcfKhMh8kRxCrONM=
github.com/vdemeester/shakers v0.1.0/go.mod h1:IZ1HHynUOQt32iQ3rvAeVddXLd19h/6LWiKsh9RZtAQ=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/vulcand/oxy v1.2.0 h1:Y2Wt1EgQddA/qMnp1+YXjehPsyw2Gy8CAfavkFF+fQk=
github.com/vulcand/oxy v1.2.0/go.mod h1:nGeNTWfyYQj3ghi3W8ok7vLSkw7Gkvr0x+G/v8Wk7vM=
github.com/vulcand/predicate v1.1.0 h1:Gq/uWopa4rx/tnZu2opOSBqHK63Yqlou/SzrbwdJiNg=
@ -1001,6 +1078,7 @@ go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSF
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@ -1026,12 +1104,16 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
@ -1044,6 +1126,7 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -1063,6 +1146,7 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -1089,6 +1173,8 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -1096,6 +1182,7 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -1119,16 +1206,20 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d h1:1aflnvSoWWLI2k/dMUAl5lvU1YO4Mb4hz0gh+1rjcxU=
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -1144,6 +1235,7 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -1152,6 +1244,7 @@ golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1196,8 +1289,9 @@ golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014 h1:joucsQqXmyBVxViHCPFjG3hx8JzIFSaym3l3MM/Jsdg=
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -1220,6 +1314,7 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -1273,6 +1368,9 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@ -1287,6 +1385,7 @@ google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
@ -1294,6 +1393,10 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -1315,6 +1418,8 @@ google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a h1:pOwg4OoaRYScjmR4LlLgdtnyoHYTSAVhhqe5uPdpII8=
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@ -1400,6 +1505,7 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@ -1414,6 +1520,7 @@ k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo=
k8s.io/api v0.20.2 h1:y/HR22XDZY3pniu9hIFDLpUCPq2w5eQ6aV/VFQ7uJMw=
k8s.io/api v0.20.2/go.mod h1:d7n6Ehyzx+S+cE3VhTGfVNNqtGc/oL9DCdYYahlurV8=
k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY=
k8s.io/apiextensions-apiserver v0.20.1 h1:ZrXQeslal+6zKM/HjDXLzThlz/vPSxrfK3OqL8txgVQ=
k8s.io/apiextensions-apiserver v0.20.1/go.mod h1:ntnrZV+6a3dB504qwC5PN/Yg9PBiDNt1EVqbW2kORVk=
k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
@ -1463,6 +1570,7 @@ rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
sigs.k8s.io/controller-runtime v0.8.0/go.mod h1:v9Lbj5oX443uR7GXYY46E0EE2o7k2YxQ58GxVNeXSW4=
sigs.k8s.io/controller-tools v0.4.1 h1:VkuV0MxlRPmRu5iTgBZU4UxUX2LiR99n3sdQGRxZF4w=
sigs.k8s.io/controller-tools v0.4.1/go.mod h1:G9rHdZMVlBDocIxGkK3jHLWqcTMNvveypYJwrvYKjWU=
sigs.k8s.io/gateway-api v0.2.0 h1:7cHyUed8LLFXPyzUl/mGylimx3E1CWHJYUK0/AHfEyg=
sigs.k8s.io/gateway-api v0.2.0/go.mod h1:IUbl4vAjUFoa2nt2gER8NsUrAu84x2edpWXbXBvcNis=
@ -1474,3 +1582,5 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

View file

@ -1,119 +0,0 @@
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressroutes.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRoute
plural: ingressroutes
singular: ingressroute
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: middlewares.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: Middleware
plural: middlewares
singular: middleware
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressroutetcps.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRouteTCP
plural: ingressroutetcps
singular: ingressroutetcp
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressrouteudps.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRouteUDP
plural: ingressrouteudps
singular: ingressrouteudp
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: tlsoptions.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TLSOption
plural: tlsoptions
singular: tlsoption
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: serverstransports.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: ServersTransport
plural: serverstransports
singular: serverstransport
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: tlsstores.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TLSStore
plural: tlsstores
singular: tlsstore
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: traefikservices.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TraefikService
plural: traefikservices
singular: traefikservice
scope: Namespaced

File diff suppressed because it is too large Load diff

View file

@ -12,3 +12,5 @@ spec:
- name: whoamiudp
namespace: default
port: 8090
- name: externalname-svc
port: 9090

View file

@ -0,0 +1,70 @@
apiVersion: networking.k8s.io/v1beta1
kind: IngressClass
metadata:
name: traefik-keep
spec:
controller: traefik.io/ingress-controller
---
kind: Ingress
apiVersion: networking.k8s.io/v1beta1
metadata:
name: "whoami-keep-route"
spec:
ingressClassName: "traefik-keep"
rules:
- host: "whoami.test.keep"
http:
paths:
- path: "/keep"
backend:
serviceName: "whoami"
servicePort: 80
---
apiVersion: networking.k8s.io/v1beta1
kind: IngressClass
metadata:
name: traefik-drop
spec:
controller: traefik.io/ingress-controller
---
kind: Ingress
apiVersion: networking.k8s.io/v1beta1
metadata:
name: "whoami-drop-route"
spec:
ingressClassName: "traefik-drop"
rules:
- host: "whoami.test.drop"
http:
paths:
- path: "/drop"
backend:
serviceName: "whoami"
servicePort: 80
---
apiVersion: networking.k8s.io/v1beta1
kind: IngressClass
metadata:
name: traefik-not-ingress-controller
spec:
controller: not.tr43phic.io/ingress-controller
---
kind: Ingress
apiVersion: networking.k8s.io/v1beta1
metadata:
name: "whoami-drop-ingress"
spec:
ingressClassName: "traefik-not-ingress-controller"
rules:
- host: "whoami.test.not.ingress"
http:
paths:
- path: "/ingress"
backend:
serviceName: "whoami"
servicePort: 80

View file

@ -0,0 +1,16 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[api]
insecure = true
[entryPoints]
[entryPoints.web]
address = ":8000"
[providers.kubernetesIngress]
ingressClass = "traefik-keep"

View file

@ -118,6 +118,17 @@ func (s *K8sSuite) TestGatewayConfiguration(c *check.C) {
testConfiguration(c, "testdata/rawdata-gateway.json", "8080")
}
func (s *K8sSuite) TestIngressclass(c *check.C) {
cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_ingressclass.toml"))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
testConfiguration(c, "testdata/rawdata-ingressclass.json", "8080")
}
func testConfiguration(c *check.C, path, apiPort string) {
err := try.GetRequest("http://127.0.0.1:"+apiPort+"/api/entrypoints", 20*time.Second, try.BodyContains(`"name":"web"`))
c.Assert(err, checker.IsNil)

View file

@ -1,5 +1,5 @@
server:
image: rancher/k3s:v1.17.2-k3s1
image: rancher/k3s:v1.18.15-k3s1
command: server --disable-agent --no-deploy coredns --no-deploy servicelb --no-deploy traefik --no-deploy local-storage --no-deploy metrics-server --log /output/k3s.log
environment:
- K3S_CLUSTER_SECRET=somethingtotallyrandom
@ -12,7 +12,7 @@ server:
- 6443:6443
node:
image: rancher/k3s:v1.17.2-k3s1
image: rancher/k3s:v1.18.15-k3s1
privileged: true
links:
- server

View file

@ -317,7 +317,17 @@
}
},
"udpServices": {
"default-test3.route-0@kubernetescrd": {
"default-test3.route-0-externalname-svc-9090@kubernetescrd": {
"loadBalancer": {
"servers": [
{
"address": "domain.com:9090"
}
]
},
"status": "enabled"
},
"default-test3.route-0-whoamiudp-8090@kubernetescrd": {
"loadBalancer": {
"servers": [
{
@ -328,6 +338,21 @@
}
]
},
"status": "enabled"
},
"default-test3.route-0@kubernetescrd": {
"weighted": {
"services": [
{
"name": "default-test3.route-0-whoamiudp-8090",
"weight": 1
},
{
"name": "default-test3.route-0-externalname-svc-9090",
"weight": 1
}
]
},
"status": "enabled",
"usedBy": [
"default-test3.route-0@kubernetescrd"

View file

@ -49,6 +49,28 @@
"using": [
"web"
]
},
"whoami-drop-route-default-whoami-test-drop-drop@kubernetes": {
"entryPoints": [
"web"
],
"service": "default-whoami-80",
"rule": "Host(`whoami.test.drop`) \u0026\u0026 PathPrefix(`/drop`)",
"status": "enabled",
"using": [
"web"
]
},
"whoami-keep-route-default-whoami-test-keep-keep@kubernetes": {
"entryPoints": [
"web"
],
"service": "default-whoami-80",
"rule": "Host(`whoami.test.keep`) \u0026\u0026 PathPrefix(`/keep`)",
"status": "enabled",
"using": [
"web"
]
}
},
"middlewares": {
@ -89,6 +111,28 @@
"dashboard@internal"
]
},
"default-whoami-80@kubernetes": {
"loadBalancer": {
"servers": [
{
"url": "XXXX"
},
{
"url": "XXXX"
}
],
"passHostHeader": true
},
"status": "enabled",
"usedBy": [
"whoami-drop-route-default-whoami-test-drop-drop@kubernetes",
"whoami-keep-route-default-whoami-test-keep-keep@kubernetes"
],
"serverStatus": {
"http://XXXX": "UP",
"http://XXXX": "UP"
}
},
"default-whoami-http@kubernetes": {
"loadBalancer": {
"servers": [

View file

@ -0,0 +1,106 @@
{
"routers": {
"api@internal": {
"entryPoints": [
"traefik"
],
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"priority": 2147483646,
"status": "enabled",
"using": [
"traefik"
]
},
"dashboard@internal": {
"entryPoints": [
"traefik"
],
"middlewares": [
"dashboard_redirect@internal",
"dashboard_stripprefix@internal"
],
"service": "dashboard@internal",
"rule": "PathPrefix(`/`)",
"priority": 2147483645,
"status": "enabled",
"using": [
"traefik"
]
},
"whoami-keep-route-default-whoami-test-keep-keep@kubernetes": {
"entryPoints": [
"web"
],
"service": "default-whoami-80",
"rule": "Host(`whoami.test.keep`) \u0026\u0026 PathPrefix(`/keep`)",
"status": "enabled",
"using": [
"web"
]
}
},
"middlewares": {
"dashboard_redirect@internal": {
"redirectRegex": {
"regex": "^(http:\\/\\/(\\[[\\w:.]+\\]|[\\w\\._-]+)(:\\d+)?)\\/$",
"replacement": "${1}/dashboard/",
"permanent": true
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"dashboard_stripprefix@internal": {
"stripPrefix": {
"prefixes": [
"/dashboard/",
"/dashboard"
]
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
}
},
"services": {
"api@internal": {
"status": "enabled",
"usedBy": [
"api@internal"
]
},
"dashboard@internal": {
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"default-whoami-80@kubernetes": {
"loadBalancer": {
"servers": [
{
"url": "http://10.42.0.4:80"
},
{
"url": "http://10.42.0.5:80"
}
],
"passHostHeader": true
},
"status": "enabled",
"usedBy": [
"whoami-keep-route-default-whoami-test-keep-keep@kubernetes"
],
"serverStatus": {
"http://10.42.0.4:80": "UP",
"http://10.42.0.5:80": "UP"
}
},
"noop@internal": {
"status": "enabled"
}
}
}

View file

@ -208,6 +208,7 @@ type ServersTransport struct {
Certificates tls.Certificates `description:"Certificates for mTLS." json:"certificates,omitempty" toml:"certificates,omitempty" yaml:"certificates,omitempty" export:"true"`
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" json:"maxIdleConnsPerHost,omitempty" toml:"maxIdleConnsPerHost,omitempty" yaml:"maxIdleConnsPerHost,omitempty" export:"true"`
ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers." json:"forwardingTimeouts,omitempty" toml:"forwardingTimeouts,omitempty" yaml:"forwardingTimeouts,omitempty" export:"true"`
DisableHTTP2 bool `description:"Disable HTTP/2 for connections with backend servers." json:"disableHTTP2,omitempty" toml:"disableHTTP2,omitempty" yaml:"disableHTTP2,omitempty" export:"true"`
}
// +k8s:deepcopy-gen=true

View file

@ -5,6 +5,7 @@ import (
"math"
"strings"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v2/pkg/types"
)
@ -15,6 +16,8 @@ type EntryPoint struct {
ProxyProtocol *ProxyProtocol `description:"Proxy-Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
ForwardedHeaders *ForwardedHeaders `description:"Trust client forwarding headers." json:"forwardedHeaders,omitempty" toml:"forwardedHeaders,omitempty" yaml:"forwardedHeaders,omitempty" export:"true"`
HTTP HTTPConfig `description:"HTTP configuration." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"`
EnableHTTP3 bool `description:"Enable HTTP3." json:"enableHTTP3,omitempty" toml:"enableHTTP3,omitempty" yaml:"enableHTTP3,omitempty" export:"true"`
UDP *UDPConfig `description:"UDP configuration." json:"udp,omitempty" toml:"udp,omitempty" yaml:"udp,omitempty"`
}
// GetAddress strips any potential protocol part of the address field of the
@ -45,6 +48,8 @@ func (ep *EntryPoint) SetDefaults() {
ep.Transport = &EntryPointsTransport{}
ep.Transport.SetDefaults()
ep.ForwardedHeaders = &ForwardedHeaders{}
ep.UDP = &UDPConfig{}
ep.UDP.SetDefaults()
}
// HTTPConfig is the HTTP configuration of an entry point.
@ -109,3 +114,13 @@ func (t *EntryPointsTransport) SetDefaults() {
t.RespondingTimeouts = &RespondingTimeouts{}
t.RespondingTimeouts.SetDefaults()
}
// UDPConfig is the UDP configuration of an entry point.
type UDPConfig struct {
Timeout ptypes.Duration `description:"Timeout defines how long to wait on an idle session before releasing the related resources." json:"timeout,omitempty" toml:"timeout,omitempty" yaml:"timeout,omitempty"`
}
// SetDefaults sets the default values.
func (u *UDPConfig) SetDefaults() {
u.Timeout = ptypes.Duration(DefaultUDPTimeout)
}

View file

@ -7,4 +7,5 @@ type Experimental struct {
Plugins map[string]plugins.Descriptor `description:"Plugins configuration." json:"plugins,omitempty" toml:"plugins,omitempty" yaml:"plugins,omitempty" export:"true"`
DevPlugin *plugins.DevPlugin `description:"Dev plugin configuration." json:"devPlugin,omitempty" toml:"devPlugin,omitempty" yaml:"devPlugin,omitempty" export:"true"`
KubernetesGateway bool `description:"Allow the Kubernetes gateway api provider usage." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true"`
HTTP3 bool `description:"Enable HTTP3." json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty" export:"true"`
}

View file

@ -51,6 +51,10 @@ const (
// DefaultAcmeCAServer is the default ACME API endpoint.
DefaultAcmeCAServer = "https://acme-v02.api.letsencrypt.org/directory"
// DefaultUDPTimeout defines how long to wait by default on an idle session,
// before releasing all resources related to that session.
DefaultUDPTimeout = 3 * time.Second
)
// Configuration is the static configuration.
@ -242,6 +246,12 @@ func (c *Configuration) SetEffectiveConfiguration() {
c.Providers.KubernetesGateway = nil
}
if c.Experimental == nil || !c.Experimental.HTTP3 {
for _, ep := range c.EntryPoints {
ep.EnableHTTP3 = false
}
}
// Configure Gateway API provider
if c.Providers.KubernetesGateway != nil {
log.WithoutContext().Debugf("Experimental Kubernetes Gateway provider has been activated")

View file

@ -20,18 +20,19 @@ var datadogTicker *time.Ticker
// Metric names consistent with https://github.com/DataDog/integrations-extras/pull/64
const (
ddMetricsServiceReqsName = "service.request.total"
ddMetricsServiceLatencyName = "service.request.duration"
ddRetriesTotalName = "service.retries.total"
ddConfigReloadsName = "config.reload.total"
ddConfigReloadsFailureTagName = "failure"
ddLastConfigReloadSuccessName = "config.reload.lastSuccessTimestamp"
ddLastConfigReloadFailureName = "config.reload.lastFailureTimestamp"
ddEntryPointReqsName = "entrypoint.request.total"
ddEntryPointReqDurationName = "entrypoint.request.duration"
ddEntryPointOpenConnsName = "entrypoint.connections.open"
ddOpenConnsName = "service.connections.open"
ddServerUpName = "service.server.up"
ddMetricsServiceReqsName = "service.request.total"
ddMetricsServiceLatencyName = "service.request.duration"
ddRetriesTotalName = "service.retries.total"
ddConfigReloadsName = "config.reload.total"
ddConfigReloadsFailureTagName = "failure"
ddLastConfigReloadSuccessName = "config.reload.lastSuccessTimestamp"
ddLastConfigReloadFailureName = "config.reload.lastFailureTimestamp"
ddEntryPointReqsName = "entrypoint.request.total"
ddEntryPointReqDurationName = "entrypoint.request.duration"
ddEntryPointOpenConnsName = "entrypoint.connections.open"
ddOpenConnsName = "service.connections.open"
ddServerUpName = "service.server.up"
ddTLSCertsNotAfterTimestampName = "tls.certs.notAfterTimestamp"
)
// RegisterDatadog registers the metrics pusher if this didn't happen yet and creates a datadog Registry instance.
@ -41,10 +42,11 @@ func RegisterDatadog(ctx context.Context, config *types.Datadog) Registry {
}
registry := &standardRegistry{
configReloadsCounter: datadogClient.NewCounter(ddConfigReloadsName, 1.0),
configReloadsFailureCounter: datadogClient.NewCounter(ddConfigReloadsName, 1.0).With(ddConfigReloadsFailureTagName, "true"),
lastConfigReloadSuccessGauge: datadogClient.NewGauge(ddLastConfigReloadSuccessName),
lastConfigReloadFailureGauge: datadogClient.NewGauge(ddLastConfigReloadFailureName),
configReloadsCounter: datadogClient.NewCounter(ddConfigReloadsName, 1.0),
configReloadsFailureCounter: datadogClient.NewCounter(ddConfigReloadsName, 1.0).With(ddConfigReloadsFailureTagName, "true"),
lastConfigReloadSuccessGauge: datadogClient.NewGauge(ddLastConfigReloadSuccessName),
lastConfigReloadFailureGauge: datadogClient.NewGauge(ddLastConfigReloadFailureName),
tlsCertsNotAfterTimestampGauge: datadogClient.NewGauge(ddTLSCertsNotAfterTimestampName),
}
if config.AddEntryPointsLabels {

View file

@ -36,6 +36,7 @@ func TestDatadog(t *testing.T) {
"traefik.entrypoint.request.duration:10000.000000|h|#entrypoint:test\n",
"traefik.entrypoint.connections.open:1.000000|g|#entrypoint:test\n",
"traefik.service.server.up:1.000000|g|#service:test,url:http://127.0.0.1,one:two\n",
"traefik.tls.certs.notAfterTimestamp:1.000000|g|#key:value\n",
}
udp.ShouldReceiveAll(t, expected, func() {
@ -50,5 +51,6 @@ func TestDatadog(t *testing.T) {
datadogRegistry.EntryPointReqDurationHistogram().With("entrypoint", "test").Observe(10000)
datadogRegistry.EntryPointOpenConnsGauge().With("entrypoint", "test").Set(1)
datadogRegistry.ServiceServerUpGauge().With("service", "test", "url", "http://127.0.0.1", "one", "two").Set(1)
datadogRegistry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1)
})
}

View file

@ -26,18 +26,19 @@ type influxDBWriter struct {
var influxDBTicker *time.Ticker
const (
influxDBMetricsServiceReqsName = "traefik.service.requests.total"
influxDBMetricsServiceLatencyName = "traefik.service.request.duration"
influxDBRetriesTotalName = "traefik.service.retries.total"
influxDBConfigReloadsName = "traefik.config.reload.total"
influxDBConfigReloadsFailureName = influxDBConfigReloadsName + ".failure"
influxDBLastConfigReloadSuccessName = "traefik.config.reload.lastSuccessTimestamp"
influxDBLastConfigReloadFailureName = "traefik.config.reload.lastFailureTimestamp"
influxDBEntryPointReqsName = "traefik.entrypoint.requests.total"
influxDBEntryPointReqDurationName = "traefik.entrypoint.request.duration"
influxDBEntryPointOpenConnsName = "traefik.entrypoint.connections.open"
influxDBOpenConnsName = "traefik.service.connections.open"
influxDBServerUpName = "traefik.service.server.up"
influxDBMetricsServiceReqsName = "traefik.service.requests.total"
influxDBMetricsServiceLatencyName = "traefik.service.request.duration"
influxDBRetriesTotalName = "traefik.service.retries.total"
influxDBConfigReloadsName = "traefik.config.reload.total"
influxDBConfigReloadsFailureName = influxDBConfigReloadsName + ".failure"
influxDBLastConfigReloadSuccessName = "traefik.config.reload.lastSuccessTimestamp"
influxDBLastConfigReloadFailureName = "traefik.config.reload.lastFailureTimestamp"
influxDBEntryPointReqsName = "traefik.entrypoint.requests.total"
influxDBEntryPointReqDurationName = "traefik.entrypoint.request.duration"
influxDBEntryPointOpenConnsName = "traefik.entrypoint.connections.open"
influxDBOpenConnsName = "traefik.service.connections.open"
influxDBServerUpName = "traefik.service.server.up"
influxDBTLSCertsNotAfterTimestampName = "traefik.tls.certs.notAfterTimestamp"
)
const (
@ -55,10 +56,11 @@ func RegisterInfluxDB(ctx context.Context, config *types.InfluxDB) Registry {
}
registry := &standardRegistry{
configReloadsCounter: influxDBClient.NewCounter(influxDBConfigReloadsName),
configReloadsFailureCounter: influxDBClient.NewCounter(influxDBConfigReloadsFailureName),
lastConfigReloadSuccessGauge: influxDBClient.NewGauge(influxDBLastConfigReloadSuccessName),
lastConfigReloadFailureGauge: influxDBClient.NewGauge(influxDBLastConfigReloadFailureName),
configReloadsCounter: influxDBClient.NewCounter(influxDBConfigReloadsName),
configReloadsFailureCounter: influxDBClient.NewCounter(influxDBConfigReloadsFailureName),
lastConfigReloadSuccessGauge: influxDBClient.NewGauge(influxDBLastConfigReloadSuccessName),
lastConfigReloadFailureGauge: influxDBClient.NewGauge(influxDBLastConfigReloadFailureName),
tlsCertsNotAfterTimestampGauge: influxDBClient.NewGauge(influxDBTLSCertsNotAfterTimestampName),
}
if config.AddEntryPointsLabels {

View file

@ -64,6 +64,16 @@ func TestInfluxDB(t *testing.T) {
})
assertMessage(t, msgEntrypoint, expectedEntrypoint)
expectedTLS := []string{
`(traefik\.tls\.certs\.notAfterTimestamp,key=value value=1) [\d]{19}`,
}
msgTLS := udp.ReceiveString(t, func() {
influxDBRegistry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1)
})
assertMessage(t, msgTLS, expectedTLS)
}
func TestInfluxDBHTTP(t *testing.T) {
@ -121,6 +131,15 @@ func TestInfluxDBHTTP(t *testing.T) {
msgEntrypoint := <-c
assertMessage(t, *msgEntrypoint, expectedEntrypoint)
expectedTLS := []string{
`(traefik\.tls\.certs\.notAfterTimestamp,key=value value=1) [\d]{19}`,
}
influxDBRegistry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1)
msgTLS := <-c
assertMessage(t, *msgTLS, expectedTLS)
}
func assertMessage(t *testing.T, msg string, patterns []string) {

View file

@ -21,6 +21,9 @@ type Registry interface {
LastConfigReloadSuccessGauge() metrics.Gauge
LastConfigReloadFailureGauge() metrics.Gauge
// TLS
TLSCertsNotAfterTimestampGauge() metrics.Gauge
// entry point metrics
EntryPointReqsCounter() metrics.Counter
EntryPointReqsTLSCounter() metrics.Counter
@ -50,6 +53,7 @@ func NewMultiRegistry(registries []Registry) Registry {
var configReloadsFailureCounter []metrics.Counter
var lastConfigReloadSuccessGauge []metrics.Gauge
var lastConfigReloadFailureGauge []metrics.Gauge
var tlsCertsNotAfterTimestampGauge []metrics.Gauge
var entryPointReqsCounter []metrics.Counter
var entryPointReqsTLSCounter []metrics.Counter
var entryPointReqDurationHistogram []ScalableHistogram
@ -74,6 +78,9 @@ func NewMultiRegistry(registries []Registry) Registry {
if r.LastConfigReloadFailureGauge() != nil {
lastConfigReloadFailureGauge = append(lastConfigReloadFailureGauge, r.LastConfigReloadFailureGauge())
}
if r.TLSCertsNotAfterTimestampGauge() != nil {
tlsCertsNotAfterTimestampGauge = append(tlsCertsNotAfterTimestampGauge, r.TLSCertsNotAfterTimestampGauge())
}
if r.EntryPointReqsCounter() != nil {
entryPointReqsCounter = append(entryPointReqsCounter, r.EntryPointReqsCounter())
}
@ -113,6 +120,7 @@ func NewMultiRegistry(registries []Registry) Registry {
configReloadsFailureCounter: multi.NewCounter(configReloadsFailureCounter...),
lastConfigReloadSuccessGauge: multi.NewGauge(lastConfigReloadSuccessGauge...),
lastConfigReloadFailureGauge: multi.NewGauge(lastConfigReloadFailureGauge...),
tlsCertsNotAfterTimestampGauge: multi.NewGauge(tlsCertsNotAfterTimestampGauge...),
entryPointReqsCounter: multi.NewCounter(entryPointReqsCounter...),
entryPointReqsTLSCounter: multi.NewCounter(entryPointReqsTLSCounter...),
entryPointReqDurationHistogram: NewMultiHistogram(entryPointReqDurationHistogram...),
@ -133,6 +141,7 @@ type standardRegistry struct {
configReloadsFailureCounter metrics.Counter
lastConfigReloadSuccessGauge metrics.Gauge
lastConfigReloadFailureGauge metrics.Gauge
tlsCertsNotAfterTimestampGauge metrics.Gauge
entryPointReqsCounter metrics.Counter
entryPointReqsTLSCounter metrics.Counter
entryPointReqDurationHistogram ScalableHistogram
@ -169,6 +178,10 @@ func (r *standardRegistry) LastConfigReloadFailureGauge() metrics.Gauge {
return r.lastConfigReloadFailureGauge
}
func (r *standardRegistry) TLSCertsNotAfterTimestampGauge() metrics.Gauge {
return r.tlsCertsNotAfterTimestampGauge
}
func (r *standardRegistry) EntryPointReqsCounter() metrics.Counter {
return r.entryPointReqsCounter
}

View file

@ -29,6 +29,10 @@ const (
configLastReloadSuccessName = metricConfigPrefix + "last_reload_success"
configLastReloadFailureName = metricConfigPrefix + "last_reload_failure"
// TLS.
metricsTLSPrefix = MetricNamePrefix + "tls_"
tlsCertsNotAfterTimestamp = metricsTLSPrefix + "certs_not_after"
// entry point.
metricEntryPointPrefix = MetricNamePrefix + "entrypoint_"
entryPointReqsTotalName = metricEntryPointPrefix + "requests_total"
@ -121,21 +125,27 @@ func initStandardRegistry(config *types.Prometheus) Registry {
Name: configLastReloadFailureName,
Help: "Last config reload failure",
}, []string{})
tlsCertsNotAfterTimesptamp := newGaugeFrom(promState.collectors, stdprometheus.GaugeOpts{
Name: tlsCertsNotAfterTimestamp,
Help: "Certificate expiration timestamp",
}, []string{"cn", "serial", "sans"})
promState.describers = []func(chan<- *stdprometheus.Desc){
configReloads.cv.Describe,
configReloadsFailures.cv.Describe,
lastConfigReloadSuccess.gv.Describe,
lastConfigReloadFailure.gv.Describe,
tlsCertsNotAfterTimesptamp.gv.Describe,
}
reg := &standardRegistry{
epEnabled: config.AddEntryPointsLabels,
svcEnabled: config.AddServicesLabels,
configReloadsCounter: configReloads,
configReloadsFailureCounter: configReloadsFailures,
lastConfigReloadSuccessGauge: lastConfigReloadSuccess,
lastConfigReloadFailureGauge: lastConfigReloadFailure,
epEnabled: config.AddEntryPointsLabels,
svcEnabled: config.AddServicesLabels,
configReloadsCounter: configReloads,
configReloadsFailureCounter: configReloadsFailures,
lastConfigReloadSuccessGauge: lastConfigReloadSuccess,
lastConfigReloadFailureGauge: lastConfigReloadFailure,
tlsCertsNotAfterTimestampGauge: tlsCertsNotAfterTimesptamp,
}
if config.AddEntryPointsLabels {
@ -163,11 +173,13 @@ func initStandardRegistry(config *types.Prometheus) Registry {
entryPointReqDurations.hv.Describe,
entryPointOpenConns.gv.Describe,
}...)
reg.entryPointReqsCounter = entryPointReqs
reg.entryPointReqsTLSCounter = entryPointReqsTLS
reg.entryPointReqDurationHistogram, _ = NewHistogramWithScale(entryPointReqDurations, time.Second)
reg.entryPointOpenConnsGauge = entryPointOpenConns
}
if config.AddServicesLabels {
serviceReqs := newCounterFrom(promState.collectors, stdprometheus.CounterOpts{
Name: serviceReqsTotalName,

View file

@ -116,6 +116,11 @@ func TestPrometheus(t *testing.T) {
prometheusRegistry.LastConfigReloadSuccessGauge().Set(float64(time.Now().Unix()))
prometheusRegistry.LastConfigReloadFailureGauge().Set(float64(time.Now().Unix()))
prometheusRegistry.
TLSCertsNotAfterTimestampGauge().
With("cn", "value", "serial", "value", "sans", "value").
Set(float64(time.Now().Unix()))
prometheusRegistry.
EntryPointReqsCounter().
With("code", strconv.Itoa(http.StatusOK), "method", http.MethodGet, "protocol", "http", "entrypoint", "http").
@ -175,6 +180,15 @@ func TestPrometheus(t *testing.T) {
name: configLastReloadFailureName,
assert: buildTimestampAssert(t, configLastReloadFailureName),
},
{
name: tlsCertsNotAfterTimestamp,
labels: map[string]string{
"cn": "value",
"serial": "value",
"sans": "value",
},
assert: buildTimestampAssert(t, tlsCertsNotAfterTimestamp),
},
{
name: entryPointReqsTotalName,
labels: map[string]string{

View file

@ -17,18 +17,19 @@ var (
)
const (
statsdMetricsServiceReqsName = "service.request.total"
statsdMetricsServiceLatencyName = "service.request.duration"
statsdRetriesTotalName = "service.retries.total"
statsdConfigReloadsName = "config.reload.total"
statsdConfigReloadsFailureName = statsdConfigReloadsName + ".failure"
statsdLastConfigReloadSuccessName = "config.reload.lastSuccessTimestamp"
statsdLastConfigReloadFailureName = "config.reload.lastFailureTimestamp"
statsdEntryPointReqsName = "entrypoint.request.total"
statsdEntryPointReqDurationName = "entrypoint.request.duration"
statsdEntryPointOpenConnsName = "entrypoint.connections.open"
statsdOpenConnsName = "service.connections.open"
statsdServerUpName = "service.server.up"
statsdMetricsServiceReqsName = "service.request.total"
statsdMetricsServiceLatencyName = "service.request.duration"
statsdRetriesTotalName = "service.retries.total"
statsdConfigReloadsName = "config.reload.total"
statsdConfigReloadsFailureName = statsdConfigReloadsName + ".failure"
statsdLastConfigReloadSuccessName = "config.reload.lastSuccessTimestamp"
statsdLastConfigReloadFailureName = "config.reload.lastFailureTimestamp"
statsdEntryPointReqsName = "entrypoint.request.total"
statsdEntryPointReqDurationName = "entrypoint.request.duration"
statsdEntryPointOpenConnsName = "entrypoint.connections.open"
statsdOpenConnsName = "service.connections.open"
statsdServerUpName = "service.server.up"
statsdTLSCertsNotAfterTimestampName = "tls.certs.notAfterTimestamp"
)
// RegisterStatsd registers the metrics pusher if this didn't happen yet and creates a statsd Registry instance.
@ -48,10 +49,11 @@ func RegisterStatsd(ctx context.Context, config *types.Statsd) Registry {
}
registry := &standardRegistry{
configReloadsCounter: statsdClient.NewCounter(statsdConfigReloadsName, 1.0),
configReloadsFailureCounter: statsdClient.NewCounter(statsdConfigReloadsFailureName, 1.0),
lastConfigReloadSuccessGauge: statsdClient.NewGauge(statsdLastConfigReloadSuccessName),
lastConfigReloadFailureGauge: statsdClient.NewGauge(statsdLastConfigReloadFailureName),
configReloadsCounter: statsdClient.NewCounter(statsdConfigReloadsName, 1.0),
configReloadsFailureCounter: statsdClient.NewCounter(statsdConfigReloadsFailureName, 1.0),
lastConfigReloadSuccessGauge: statsdClient.NewGauge(statsdLastConfigReloadSuccessName),
lastConfigReloadFailureGauge: statsdClient.NewGauge(statsdLastConfigReloadFailureName),
tlsCertsNotAfterTimestampGauge: statsdClient.NewGauge(statsdTLSCertsNotAfterTimestampName),
}
if config.AddEntryPointsLabels {

View file

@ -35,6 +35,7 @@ func TestStatsD(t *testing.T) {
"traefik.entrypoint.request.duration:10000.000000|ms",
"traefik.entrypoint.connections.open:1.000000|g\n",
"traefik.service.server.up:1.000000|g\n",
"tls.certs.notAfterTimestamp:1.000000|g\n",
}
udp.ShouldReceiveAll(t, expected, func() {
@ -49,6 +50,7 @@ func TestStatsD(t *testing.T) {
statsdRegistry.EntryPointReqDurationHistogram().With("entrypoint", "test").Observe(10000)
statsdRegistry.EntryPointOpenConnsGauge().With("entrypoint", "test").Set(1)
statsdRegistry.ServiceServerUpGauge().With("service:test", "url", "http://127.0.0.1").Set(1)
statsdRegistry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1)
})
}
@ -75,6 +77,7 @@ func TestStatsDWithPrefix(t *testing.T) {
"testPrefix.entrypoint.request.duration:10000.000000|ms",
"testPrefix.entrypoint.connections.open:1.000000|g\n",
"testPrefix.service.server.up:1.000000|g\n",
"tls.certs.notAfterTimestamp:1.000000|g\n",
}
udp.ShouldReceiveAll(t, expected, func() {
@ -89,5 +92,6 @@ func TestStatsDWithPrefix(t *testing.T) {
statsdRegistry.EntryPointReqDurationHistogram().With("entrypoint", "test").Observe(10000)
statsdRegistry.EntryPointOpenConnsGauge().With("entrypoint", "test").Set(1)
statsdRegistry.ServiceServerUpGauge().With("service:test", "url", "http://127.0.0.1").Set(1)
statsdRegistry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1)
})
}

View file

@ -70,6 +70,11 @@ const (
Overhead = "Overhead"
// RetryAttempts is the map key used for the amount of attempts the request was retried.
RetryAttempts = "RetryAttempts"
// TLSVersion is the version of TLS used in the request.
TLSVersion = "TLSVersion"
// TLSCipher is the cipher used in the request.
TLSCipher = "TLSCipher"
)
// These are written out in the default case when no config is provided to specify keys of interest.
@ -111,6 +116,8 @@ func init() {
allCoreKeys[StartLocal] = struct{}{}
allCoreKeys[Overhead] = struct{}{}
allCoreKeys[RetryAttempts] = struct{}{}
allCoreKeys[TLSVersion] = struct{}{}
allCoreKeys[TLSCipher] = struct{}{}
}
// CoreLogData holds the fields computed from the request/response.

View file

@ -18,6 +18,7 @@ import (
"github.com/sirupsen/logrus"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v2/pkg/log"
traefiktls "github.com/traefik/traefik/v2/pkg/tls"
"github.com/traefik/traefik/v2/pkg/types"
)
@ -209,6 +210,8 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http
core[RequestScheme] = "http"
if req.TLS != nil {
core[RequestScheme] = "https"
core[TLSVersion] = traefiktls.GetVersion(req.TLS)
core[TLSCipher] = traefiktls.GetCipherName(req.TLS)
}
core[ClientAddr] = req.RemoteAddr

View file

@ -346,9 +346,11 @@ func TestLoggerJSON(t *testing.T) {
Duration: assertFloat64NotZero(),
Overhead: assertFloat64NotZero(),
RetryAttempts: assertFloat64(float64(testRetryAttempts)),
TLSVersion: assertString("1.3"),
TLSCipher: assertString("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"),
"time": assertNotEmpty(),
"StartLocal": assertNotEmpty(),
"StartUTC": assertNotEmpty(),
StartLocal: assertNotEmpty(),
StartUTC: assertNotEmpty(),
},
},
{
@ -750,7 +752,10 @@ func doLoggingTLSOpt(t *testing.T, config *types.AccessLog, enableTLS bool) {
},
}
if enableTLS {
req.TLS = &tls.ConnectionState{}
req.TLS = &tls.ConnectionState{
Version: tls.VersionTLS13,
CipherSuite: tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
}
}
logger.ServeHTTP(httptest.NewRecorder(), req, http.HandlerFunc(logWriterTestHandlerFunc))

View file

@ -2,7 +2,6 @@ package metrics
import (
"context"
"crypto/tls"
"net/http"
"strconv"
"strings"
@ -90,7 +89,7 @@ func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request)
if req.TLS != nil {
var tlsLabels []string
tlsLabels = append(tlsLabels, m.baseLabels...)
tlsLabels = append(tlsLabels, "tls_version", getRequestTLSVersion(req), "tls_cipher", getRequestTLSCipher(req))
tlsLabels = append(tlsLabels, "tls_version", traefiktls.GetVersion(req.TLS), "tls_cipher", traefiktls.GetCipherName(req.TLS))
m.reqsTLSCounter.With(tlsLabels...).Add(1)
}
@ -147,29 +146,6 @@ func getMethod(r *http.Request) string {
return r.Method
}
func getRequestTLSVersion(req *http.Request) string {
switch req.TLS.Version {
case tls.VersionTLS10:
return "1.0"
case tls.VersionTLS11:
return "1.1"
case tls.VersionTLS12:
return "1.2"
case tls.VersionTLS13:
return "1.3"
default:
return "unknown"
}
}
func getRequestTLSCipher(req *http.Request) string {
if version, ok := traefiktls.CipherSuitesReversed[req.TLS.CipherSuite]; ok {
return version
}
return "unknown"
}
type retryMetrics interface {
ServiceRetriesCounter() gokitmetrics.Counter
}

View file

@ -9,7 +9,7 @@ import (
"text/template"
"unicode"
"github.com/Masterminds/sprig"
"github.com/Masterminds/sprig/v3"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/log"
)

View file

@ -10,7 +10,7 @@ import (
"strings"
"text/template"
"github.com/Masterminds/sprig"
"github.com/Masterminds/sprig/v3"
"github.com/traefik/paerser/file"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/log"

View file

@ -146,6 +146,43 @@ spec:
app: traefiklabs
task: whoamiudp
---
apiVersion: v1
kind: Service
metadata:
name: external-svc
namespace: default
spec:
externalName: external.domain
type: ExternalName
---
apiVersion: v1
kind: Service
metadata:
name: external.service.with.port
namespace: default
spec:
externalName: external.domain
type: ExternalName
ports:
- name: http
protocol: TCP
port: 80
---
apiVersion: v1
kind: Service
metadata:
name: external.service.without.port
namespace: default
spec:
externalName: external.domain
type: ExternalName
ports:
- name: http
protocol: TCP
---
kind: Endpoints
apiVersion: v1

View file

@ -0,0 +1,14 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteUDP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- services:
- name: external-svc
port: 8000

View file

@ -0,0 +1,14 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteUDP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- services:
- name: external.service.with.port
port: 80

View file

@ -0,0 +1,13 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteUDP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- services:
- name: external-svc

View file

@ -5,6 +5,7 @@ import (
"bytes"
"context"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"os"
@ -23,7 +24,9 @@ import (
"github.com/traefik/traefik/v2/pkg/safe"
"github.com/traefik/traefik/v2/pkg/tls"
corev1 "k8s.io/api/core/v1"
apiextensionv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/intstr"
)
const (
@ -218,6 +221,24 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
conf.HTTP.Services[serviceName] = errorPageService
}
plugin, err := createPluginMiddleware(middleware.Spec.Plugin)
if err != nil {
log.FromContext(ctxMid).Errorf("Error while reading plugins middleware: %v", err)
continue
}
rateLimit, err := createRateLimitMiddleware(middleware.Spec.RateLimit)
if err != nil {
log.FromContext(ctxMid).Errorf("Error while reading rateLimit middleware: %v", err)
continue
}
retry, err := createRetryMiddleware(middleware.Spec.Retry)
if err != nil {
log.FromContext(ctxMid).Errorf("Error while reading retry middleware: %v", err)
continue
}
conf.HTTP.Middlewares[id] = &dynamic.Middleware{
AddPrefix: middleware.Spec.AddPrefix,
StripPrefix: middleware.Spec.StripPrefix,
@ -228,7 +249,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
IPWhiteList: middleware.Spec.IPWhiteList,
Headers: middleware.Spec.Headers,
Errors: errorPage,
RateLimit: middleware.Spec.RateLimit,
RateLimit: rateLimit,
RedirectRegex: middleware.Spec.RedirectRegex,
RedirectScheme: middleware.Spec.RedirectScheme,
BasicAuth: basicAuth,
@ -239,9 +260,9 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
CircuitBreaker: middleware.Spec.CircuitBreaker,
Compress: middleware.Spec.Compress,
PassTLSClientCert: middleware.Spec.PassTLSClientCert,
Retry: middleware.Spec.Retry,
Retry: retry,
ContentType: middleware.Spec.ContentType,
Plugin: middleware.Spec.Plugin,
Plugin: plugin,
}
}
@ -323,18 +344,18 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
return conf
}
func getServicePort(svc *corev1.Service, port int32) (*corev1.ServicePort, error) {
func getServicePort(svc *corev1.Service, port intstr.IntOrString) (*corev1.ServicePort, error) {
if svc == nil {
return nil, errors.New("service is not defined")
}
if port == 0 {
if (port.Type == intstr.Int && port.IntVal == 0) || (port.Type == intstr.String && port.StrVal == "") {
return nil, errors.New("ingressRoute service port not defined")
}
hasValidPort := false
for _, p := range svc.Spec.Ports {
if p.Port == port {
if (port.Type == intstr.Int && port.IntVal == p.Port) || (port.Type == intstr.String && port.StrVal == p.Name) {
return &p, nil
}
@ -343,8 +364,8 @@ func getServicePort(svc *corev1.Service, port int32) (*corev1.ServicePort, error
}
}
if svc.Spec.Type != corev1.ServiceTypeExternalName {
return nil, fmt.Errorf("service port not found: %d", port)
if svc.Spec.Type != corev1.ServiceTypeExternalName || port.Type == intstr.String {
return nil, fmt.Errorf("service port not found: %s", &port)
}
if hasValidPort {
@ -352,7 +373,63 @@ func getServicePort(svc *corev1.Service, port int32) (*corev1.ServicePort, error
Warning("The port %d from IngressRoute doesn't match with ports defined in the ExternalName service %s/%s.", port, svc.Namespace, svc.Name)
}
return &corev1.ServicePort{Port: port}, nil
return &corev1.ServicePort{Port: port.IntVal}, nil
}
func createPluginMiddleware(plugins map[string]apiextensionv1.JSON) (map[string]dynamic.PluginConf, error) {
if plugins == nil {
return nil, nil
}
data, err := json.Marshal(plugins)
if err != nil {
return nil, err
}
pc := map[string]dynamic.PluginConf{}
err = json.Unmarshal(data, &pc)
if err != nil {
return nil, err
}
return pc, nil
}
func createRateLimitMiddleware(rateLimit *v1alpha1.RateLimit) (*dynamic.RateLimit, error) {
if rateLimit == nil {
return nil, nil
}
rl := &dynamic.RateLimit{Average: rateLimit.Average}
rl.SetDefaults()
if rateLimit.Burst != nil {
rl.Burst = *rateLimit.Burst
}
if rateLimit.Period != nil {
err := rl.Period.Set(rateLimit.Period.String())
if err != nil {
return nil, err
}
}
return rl, nil
}
func createRetryMiddleware(retry *v1alpha1.Retry) (*dynamic.Retry, error) {
if retry == nil {
return nil, nil
}
r := &dynamic.Retry{Attempts: retry.Attempts}
err := r.InitialInterval.Set(retry.InitialInterval.String())
if err != nil {
return nil, err
}
return r, nil
}
func (p *Provider) createErrorPageMiddleware(client Client, namespace string, errorPage *v1alpha1.ErrorPage) (*dynamic.ErrorPage, *dynamic.Service, error) {

View file

@ -14,6 +14,7 @@ import (
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
"github.com/traefik/traefik/v2/pkg/tls"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
const (
@ -399,7 +400,7 @@ func (c configBuilder) nameAndService(ctx context.Context, parentNamespace strin
return fullName, serversLB, nil
case service.Kind == "TraefikService":
return fullServiceName(svcCtx, namespace, service, 0), nil, nil
return fullServiceName(svcCtx, namespace, service, intstr.FromInt(0)), nil, nil
default:
return "", nil, fmt.Errorf("unsupported service kind %s", service.Kind)
}
@ -414,9 +415,9 @@ func splitSvcNameProvider(name string) (string, string) {
return svc, pvd
}
func fullServiceName(ctx context.Context, namespace string, service v1alpha1.LoadBalancerSpec, port int32) string {
if port != 0 {
return provider.Normalize(fmt.Sprintf("%s-%s-%d", namespace, service.Name, port))
func fullServiceName(ctx context.Context, namespace string, service v1alpha1.LoadBalancerSpec, port intstr.IntOrString) string {
if (port.Type == intstr.Int && port.IntVal != 0) || (port.Type == intstr.String && port.StrVal != "") {
return provider.Normalize(fmt.Sprintf("%s-%s-%s", namespace, service.Name, &port))
}
if !strings.Contains(service.Name, providerNamespaceSeparator) {

View file

@ -71,7 +71,7 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client
break
}
serviceKey := fmt.Sprintf("%s-%s-%d", serviceName, service.Name, service.Port)
serviceKey := fmt.Sprintf("%s-%s-%s", serviceName, service.Name, &service.Port)
conf.Services[serviceKey] = balancerServerTCP
srv := dynamic.TCPWRRService{Name: serviceKey}

View file

@ -18,6 +18,7 @@ import (
"github.com/traefik/traefik/v2/pkg/tls"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
kubefake "k8s.io/client-go/kubernetes/fake"
)
@ -81,11 +82,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
@ -122,11 +121,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
@ -136,11 +133,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
@ -178,11 +173,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
@ -422,11 +415,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
@ -466,11 +457,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
@ -531,11 +520,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
@ -594,11 +581,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
@ -656,11 +641,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
@ -707,11 +690,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
@ -758,11 +739,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
@ -800,11 +779,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
@ -843,11 +820,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
TerminationDelay: Int(500),
@ -892,11 +867,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
@ -937,7 +910,6 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "external.domain:8000",
Port: "",
},
},
},
@ -975,7 +947,6 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "external.domain:80",
Port: "",
},
},
},
@ -3035,11 +3006,9 @@ func TestLoadIngressRoutes(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
ProxyProtocol: &dynamic.ProxyProtocol{Version: 2},
@ -3392,11 +3361,9 @@ func TestLoadIngressRouteUDPs(t *testing.T) {
Servers: []dynamic.UDPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
@ -3437,11 +3404,9 @@ func TestLoadIngressRouteUDPs(t *testing.T) {
Servers: []dynamic.UDPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
@ -3451,11 +3416,9 @@ func TestLoadIngressRouteUDPs(t *testing.T) {
Servers: []dynamic.UDPServer{
{
Address: "10.10.0.3:8080",
Port: "",
},
{
Address: "10.10.0.4:8080",
Port: "",
},
},
},
@ -3621,6 +3584,104 @@ func TestLoadIngressRouteUDPs(t *testing.T) {
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Simple Ingress Route, with externalName service",
paths: []string{"udp/services.yml", "udp/with_externalname.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"default-test.route-0": {
EntryPoints: []string{"foo"},
Service: "default-test.route-0",
},
},
Services: map[string]*dynamic.UDPService{
"default-test.route-0": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "external.domain:8000",
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
ServersTransports: map[string]*dynamic.ServersTransport{},
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Ingress Route, externalName service with port",
paths: []string{"udp/services.yml", "udp/with_externalname_with_port.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"default-test.route-0": {
EntryPoints: []string{"foo"},
Service: "default-test.route-0",
},
},
Services: map[string]*dynamic.UDPService{
"default-test.route-0": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "external.domain:80",
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
ServersTransports: map[string]*dynamic.ServersTransport{},
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Ingress Route, externalName service without port",
paths: []string{"udp/services.yml", "udp/with_externalname_without_ports.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"default-test.route-0": {
EntryPoints: []string{"foo"},
Service: "default-test.route-0",
},
},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
ServersTransports: map[string]*dynamic.ServersTransport{},
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Ingress class does not match",
paths: []string{"udp/services.yml", "udp/simple.yml"},
@ -3738,7 +3799,7 @@ func TestGetServicePort(t *testing.T) {
testCases := []struct {
desc string
svc *corev1.Service
port int32
port intstr.IntOrString
expected *corev1.ServicePort
expectError bool
}{
@ -3757,7 +3818,7 @@ func TestGetServicePort(t *testing.T) {
},
},
},
port: 80,
port: intstr.FromInt(80),
expected: &corev1.ServicePort{
Port: 80,
},
@ -3785,12 +3846,57 @@ func TestGetServicePort(t *testing.T) {
},
expectError: true,
},
{
desc: "Matching named port",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Name: "http",
Port: 80,
},
},
},
},
port: intstr.FromString("http"),
expected: &corev1.ServicePort{
Name: "http",
Port: 80,
},
},
{
desc: "Matching named port (with external name)",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeExternalName,
Ports: []corev1.ServicePort{
{
Name: "http",
Port: 80,
},
},
},
},
port: intstr.FromString("http"),
expected: &corev1.ServicePort{
Name: "http",
Port: 80,
},
},
{
desc: "Mismatching, only port(Ingress) defined",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{},
},
port: 80,
port: intstr.FromInt(80),
expectError: true,
},
{
desc: "Mismatching, only named port(Ingress) defined",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{},
},
port: intstr.FromString("http"),
expectError: true,
},
{
@ -3800,11 +3906,21 @@ func TestGetServicePort(t *testing.T) {
Type: corev1.ServiceTypeExternalName,
},
},
port: 80,
port: intstr.FromInt(80),
expected: &corev1.ServicePort{
Port: 80,
},
},
{
desc: "Mismatching, only named port(Ingress) defined with external name",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeExternalName,
},
},
port: intstr.FromString("http"),
expectError: true,
},
{
desc: "Mismatching, only Service port defined",
svc: &corev1.Service{
@ -3843,7 +3959,22 @@ func TestGetServicePort(t *testing.T) {
},
},
},
port: 443,
port: intstr.FromInt(443),
expectError: true,
},
{
desc: "Two different named ports defined",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Name: "foo",
Port: 80,
},
},
},
},
port: intstr.FromString("bar"),
expectError: true,
},
{
@ -3858,11 +3989,27 @@ func TestGetServicePort(t *testing.T) {
},
},
},
port: 443,
port: intstr.FromInt(443),
expected: &corev1.ServicePort{
Port: 443,
},
},
{
desc: "Two different named ports defined (with external name)",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeExternalName,
Ports: []corev1.ServicePort{
{
Name: "foo",
Port: 80,
},
},
},
},
port: intstr.FromString("bar"),
expectError: true,
},
}
for _, test := range testCases {
test := test
@ -4272,11 +4419,9 @@ func TestCrossNamespace(t *testing.T) {
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
@ -4332,11 +4477,9 @@ func TestCrossNamespace(t *testing.T) {
Servers: []dynamic.UDPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},

View file

@ -52,7 +52,7 @@ func (p *Provider) loadIngressRouteUDPConfiguration(ctx context.Context, client
break
}
serviceKey := fmt.Sprintf("%s-%s-%d", serviceName, service.Name, service.Port)
serviceKey := fmt.Sprintf("%s-%s-%s", serviceName, service.Name, &service.Port)
conf.Services[serviceKey] = balancerServerUDP
srv := dynamic.UDPWRRService{Name: serviceKey}
@ -111,23 +111,15 @@ func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([
return nil, errors.New("service not found")
}
var portSpec *corev1.ServicePort
for _, p := range service.Spec.Ports {
p := p
if svc.Port == p.Port {
portSpec = &p
break
}
}
if portSpec == nil {
return nil, errors.New("service port not found")
svcPort, err := getServicePort(service, svc.Port)
if err != nil {
return nil, err
}
var servers []dynamic.UDPServer
if service.Spec.Type == corev1.ServiceTypeExternalName {
servers = append(servers, dynamic.UDPServer{
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(portSpec.Port))),
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))),
})
} else {
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
@ -146,7 +138,7 @@ func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([
var port int32
for _, subset := range endpoints.Subsets {
for _, p := range subset.Ports {
if portSpec.Name == p.Name {
if svcPort.Name == p.Name {
port = p.Port
break
}

View file

@ -4,22 +4,24 @@ import (
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
// IngressRouteSpec is a specification for a IngressRouteSpec resource.
type IngressRouteSpec struct {
Routes []Route `json:"routes"`
EntryPoints []string `json:"entryPoints"`
EntryPoints []string `json:"entryPoints,omitempty"`
TLS *TLS `json:"tls,omitempty"`
}
// Route contains the set of routes.
type Route struct {
Match string `json:"match"`
Match string `json:"match"`
// +kubebuilder:validation:Enum=Rule
Kind string `json:"kind"`
Priority int `json:"priority"`
Priority int `json:"priority,omitempty"`
Services []Service `json:"services,omitempty"`
Middlewares []MiddlewareRef `json:"middlewares"`
Middlewares []MiddlewareRef `json:"middlewares,omitempty"`
}
// TLS contains the TLS certificates configuration of the routes.
@ -33,7 +35,7 @@ type Route struct {
type TLS struct {
// SecretName is the name of the referenced Kubernetes Secret to specify the
// certificate details.
SecretName string `json:"secretName"`
SecretName string `json:"secretName,omitempty"`
// Options is a reference to a TLSOption, that specifies the parameters of the TLS connection.
Options *TLSOptionRef `json:"options,omitempty"`
// Store is a reference to a TLSStore, that specifies the parameters of the TLS store.
@ -45,13 +47,13 @@ type TLS struct {
// TLSOptionRef is a ref to the TLSOption resources.
type TLSOptionRef struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Namespace string `json:"namespace,omitempty"`
}
// TLSStoreRef is a ref to the TLSStore resource.
type TLSStoreRef struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Namespace string `json:"namespace,omitempty"`
}
// LoadBalancerSpec can reference either a Kubernetes Service object (a load-balancer of servers),
@ -60,14 +62,16 @@ type LoadBalancerSpec struct {
// Name is a reference to a Kubernetes Service object (for a load-balancer of servers),
// or to a TraefikService object (service load-balancer, mirroring, etc).
// The differentiation between the two is specified in the Kind field.
Name string `json:"name"`
Kind string `json:"kind"`
Namespace string `json:"namespace"`
Name string `json:"name"`
// +kubebuilder:validation:Enum=Service;TraefikService
Kind string `json:"kind,omitempty"`
Namespace string `json:"namespace,omitempty"`
Sticky *dynamic.Sticky `json:"sticky,omitempty"`
// Port and all the fields below are related to a servers load-balancer,
// and therefore should only be specified when Name references a Kubernetes Service.
Port int32 `json:"port"`
Port intstr.IntOrString `json:"port,omitempty"`
Scheme string `json:"scheme,omitempty"`
Strategy string `json:"strategy,omitempty"`
PassHostHeader *bool `json:"passHostHeader,omitempty"`
@ -81,17 +85,18 @@ type LoadBalancerSpec struct {
// Service defines an upstream to proxy traffic.
type Service struct {
LoadBalancerSpec
LoadBalancerSpec `json:",inline"`
}
// MiddlewareRef is a ref to the Middleware resources.
type MiddlewareRef struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Namespace string `json:"namespace,omitempty"`
}
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:storageversion
// IngressRoute is an Ingress CRD specification.
type IngressRoute struct {

View file

@ -4,12 +4,13 @@ import (
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
// IngressRouteTCPSpec is a specification for a IngressRouteTCPSpec resource.
type IngressRouteTCPSpec struct {
Routes []RouteTCP `json:"routes"`
EntryPoints []string `json:"entryPoints"`
EntryPoints []string `json:"entryPoints,omitempty"`
TLS *TLSTCP `json:"tls,omitempty"`
}
@ -30,33 +31,33 @@ type RouteTCP struct {
type TLSTCP struct {
// SecretName is the name of the referenced Kubernetes Secret to specify the
// certificate details.
SecretName string `json:"secretName"`
Passthrough bool `json:"passthrough"`
SecretName string `json:"secretName,omitempty"`
Passthrough bool `json:"passthrough,omitempty"`
// Options is a reference to a TLSOption, that specifies the parameters of the TLS connection.
Options *TLSOptionTCPRef `json:"options"`
Options *TLSOptionTCPRef `json:"options,omitempty"`
// Store is a reference to a TLSStore, that specifies the parameters of the TLS store.
Store *TLSStoreTCPRef `json:"store"`
CertResolver string `json:"certResolver"`
Store *TLSStoreTCPRef `json:"store,omitempty"`
CertResolver string `json:"certResolver,omitempty"`
Domains []types.Domain `json:"domains,omitempty"`
}
// TLSOptionTCPRef is a ref to the TLSOption resources.
type TLSOptionTCPRef struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Namespace string `json:"namespace,omitempty"`
}
// TLSStoreTCPRef is a ref to the TLSStore resources.
type TLSStoreTCPRef struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Namespace string `json:"namespace,omitempty"`
}
// ServiceTCP defines an upstream to proxy traffic.
type ServiceTCP struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Port int32 `json:"port"`
Namespace string `json:"namespace,omitempty"`
Port intstr.IntOrString `json:"port"`
Weight *int `json:"weight,omitempty"`
TerminationDelay *int `json:"terminationDelay,omitempty"`
ProxyProtocol *dynamic.ProxyProtocol `json:"proxyProtocol,omitempty"`
@ -64,6 +65,7 @@ type ServiceTCP struct {
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:storageversion
// IngressRouteTCP is an Ingress CRD specification.
type IngressRouteTCP struct {

View file

@ -2,12 +2,13 @@ package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
// IngressRouteUDPSpec is a specification for a IngressRouteUDPSpec resource.
type IngressRouteUDPSpec struct {
Routes []RouteUDP `json:"routes"`
EntryPoints []string `json:"entryPoints"`
EntryPoints []string `json:"entryPoints,omitempty"`
}
// RouteUDP contains the set of routes.
@ -18,19 +19,20 @@ type RouteUDP struct {
// TLSOptionUDPRef is a ref to the TLSOption resources.
type TLSOptionUDPRef struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Namespace string `json:"namespace,omitempty"`
}
// ServiceUDP defines an upstream to proxy traffic.
type ServiceUDP struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Port int32 `json:"port"`
Weight *int `json:"weight,omitempty"`
Name string `json:"name"`
Namespace string `json:"namespace,omitempty"`
Port intstr.IntOrString `json:"port"`
Weight *int `json:"weight,omitempty"`
}
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:storageversion
// IngressRouteUDP is an Ingress CRD specification.
type IngressRouteUDP struct {

View file

@ -2,11 +2,14 @@ package v1alpha1
import (
"github.com/traefik/traefik/v2/pkg/config/dynamic"
apiextensionv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:storageversion
// Middleware is a specification for a Middleware resource.
type Middleware struct {
@ -20,29 +23,29 @@ type Middleware struct {
// MiddlewareSpec holds the Middleware configuration.
type MiddlewareSpec struct {
AddPrefix *dynamic.AddPrefix `json:"addPrefix,omitempty"`
StripPrefix *dynamic.StripPrefix `json:"stripPrefix,omitempty"`
StripPrefixRegex *dynamic.StripPrefixRegex `json:"stripPrefixRegex,omitempty"`
ReplacePath *dynamic.ReplacePath `json:"replacePath,omitempty"`
ReplacePathRegex *dynamic.ReplacePathRegex `json:"replacePathRegex,omitempty"`
Chain *Chain `json:"chain,omitempty"`
IPWhiteList *dynamic.IPWhiteList `json:"ipWhiteList,omitempty"`
Headers *dynamic.Headers `json:"headers,omitempty"`
Errors *ErrorPage `json:"errors,omitempty"`
RateLimit *dynamic.RateLimit `json:"rateLimit,omitempty"`
RedirectRegex *dynamic.RedirectRegex `json:"redirectRegex,omitempty"`
RedirectScheme *dynamic.RedirectScheme `json:"redirectScheme,omitempty"`
BasicAuth *BasicAuth `json:"basicAuth,omitempty"`
DigestAuth *DigestAuth `json:"digestAuth,omitempty"`
ForwardAuth *ForwardAuth `json:"forwardAuth,omitempty"`
InFlightReq *dynamic.InFlightReq `json:"inFlightReq,omitempty"`
Buffering *dynamic.Buffering `json:"buffering,omitempty"`
CircuitBreaker *dynamic.CircuitBreaker `json:"circuitBreaker,omitempty"`
Compress *dynamic.Compress `json:"compress,omitempty"`
PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty"`
Retry *dynamic.Retry `json:"retry,omitempty"`
ContentType *dynamic.ContentType `json:"contentType,omitempty"`
Plugin map[string]dynamic.PluginConf `json:"plugin,omitempty"`
AddPrefix *dynamic.AddPrefix `json:"addPrefix,omitempty"`
StripPrefix *dynamic.StripPrefix `json:"stripPrefix,omitempty"`
StripPrefixRegex *dynamic.StripPrefixRegex `json:"stripPrefixRegex,omitempty"`
ReplacePath *dynamic.ReplacePath `json:"replacePath,omitempty"`
ReplacePathRegex *dynamic.ReplacePathRegex `json:"replacePathRegex,omitempty"`
Chain *Chain `json:"chain,omitempty"`
IPWhiteList *dynamic.IPWhiteList `json:"ipWhiteList,omitempty"`
Headers *dynamic.Headers `json:"headers,omitempty"`
Errors *ErrorPage `json:"errors,omitempty"`
RateLimit *RateLimit `json:"rateLimit,omitempty"`
RedirectRegex *dynamic.RedirectRegex `json:"redirectRegex,omitempty"`
RedirectScheme *dynamic.RedirectScheme `json:"redirectScheme,omitempty"`
BasicAuth *BasicAuth `json:"basicAuth,omitempty"`
DigestAuth *DigestAuth `json:"digestAuth,omitempty"`
ForwardAuth *ForwardAuth `json:"forwardAuth,omitempty"`
InFlightReq *dynamic.InFlightReq `json:"inFlightReq,omitempty"`
Buffering *dynamic.Buffering `json:"buffering,omitempty"`
CircuitBreaker *dynamic.CircuitBreaker `json:"circuitBreaker,omitempty"`
Compress *dynamic.Compress `json:"compress,omitempty"`
PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty"`
Retry *Retry `json:"retry,omitempty"`
ContentType *dynamic.ContentType `json:"contentType,omitempty"`
Plugin map[string]apiextensionv1.JSON `json:"plugin,omitempty"`
}
// +k8s:deepcopy-gen=true
@ -110,3 +113,21 @@ type MiddlewareList struct {
Items []Middleware `json:"items"`
}
// +k8s:deepcopy-gen=true
// RateLimit holds the rate limiting configuration for a given router.
type RateLimit struct {
Average int64 `json:"average,omitempty"`
Period *intstr.IntOrString `json:"period,omitempty"`
Burst *int64 `json:"burst,omitempty"`
SourceCriterion *dynamic.SourceCriterion `json:"sourceCriterion,omitempty"`
}
// +k8s:deepcopy-gen=true
// Retry holds the retry configuration.
type Retry struct {
Attempts int `json:"attempts,omitempty"`
InitialInterval intstr.IntOrString `json:"initialInterval,omitempty"`
}

View file

@ -7,6 +7,7 @@ import (
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:storageversion
// ServersTransport is a specification for a ServersTransport resource.
type ServersTransport struct {
@ -20,21 +21,33 @@ type ServersTransport struct {
// ServersTransportSpec options to configure communication between Traefik and the servers.
type ServersTransportSpec struct {
ServerName string `description:"ServerName used to contact the server" json:"serverName,omitempty" toml:"serverName,omitempty" yaml:"serverName,omitempty" export:"true"`
InsecureSkipVerify bool `description:"Disable SSL certificate verification." json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"`
RootCAsSecrets []string `description:"Add cert file for self-signed certificate." json:"rootCAsSecrets,omitempty" toml:"rootCAsSecrets,omitempty" yaml:"rootCAsSecrets,omitempty"`
CertificatesSecrets []string `description:"Certificates for mTLS." json:"certificatesSecrets,omitempty" toml:"certificatesSecrets,omitempty" yaml:"certificatesSecrets,omitempty"`
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" json:"maxIdleConnsPerHost,omitempty" toml:"maxIdleConnsPerHost,omitempty" yaml:"maxIdleConnsPerHost,omitempty" export:"true"`
ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers." json:"forwardingTimeouts,omitempty" toml:"forwardingTimeouts,omitempty" yaml:"forwardingTimeouts,omitempty" export:"true"`
// ServerName used to contact the server.
ServerName string `json:"serverName,omitempty"`
// Disable SSL certificate verification.
InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"`
// Add cert file for self-signed certificate.
RootCAsSecrets []string `json:"rootCAsSecrets,omitempty"`
// Certificates for mTLS.
CertificatesSecrets []string `json:"certificatesSecrets,omitempty"`
// If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used.
MaxIdleConnsPerHost int `json:"maxIdleConnsPerHost,omitempty"`
// Timeouts for requests forwarded to the backend servers.
ForwardingTimeouts *ForwardingTimeouts `json:"forwardingTimeouts,omitempty"`
// Disable HTTP/2 for connections with backend servers.
DisableHTTP2 bool `json:"disableHTTP2,omitempty"`
}
// +k8s:deepcopy-gen=true
// ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers.
type ForwardingTimeouts struct {
DialTimeout *intstr.IntOrString `description:"The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists." json:"dialTimeout,omitempty" toml:"dialTimeout,omitempty" yaml:"dialTimeout,omitempty" export:"true"`
ResponseHeaderTimeout *intstr.IntOrString `description:"The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists." json:"responseHeaderTimeout,omitempty" toml:"responseHeaderTimeout,omitempty" yaml:"responseHeaderTimeout,omitempty" export:"true"`
IdleConnTimeout *intstr.IntOrString `description:"The maximum period for which an idle HTTP keep-alive connection will remain open before closing itself" json:"idleConnTimeout,omitempty" toml:"idleConnTimeout,omitempty" yaml:"idleConnTimeout,omitempty" export:"true"`
// The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists.
DialTimeout *intstr.IntOrString `json:"dialTimeout,omitempty"`
// The amount of time to wait for a server's response headers after fully writing the request (including its body, if any).
// If zero, no timeout exists.
ResponseHeaderTimeout *intstr.IntOrString `json:"responseHeaderTimeout,omitempty"`
// The maximum period for which an idle HTTP keep-alive connection will remain open before closing itself.
IdleConnTimeout *intstr.IntOrString `json:"idleConnTimeout,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View file

@ -7,6 +7,7 @@ import (
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:storageversion
// TraefikService is the specification for a service (that an IngressRoute refers
// to) that is usually not a terminal service (i.e. not a pod of servers), as
@ -43,8 +44,9 @@ type ServiceSpec struct {
// Mirroring defines a mirroring service, which is composed of a main
// load-balancer, and a list of mirrors.
type Mirroring struct {
LoadBalancerSpec
MaxBodySize *int64
LoadBalancerSpec `json:",inline"`
MaxBodySize *int64 `json:"maxBodySize,omitempty"`
Mirrors []MirrorService `json:"mirrors,omitempty"`
}
@ -52,7 +54,8 @@ type Mirroring struct {
// MirrorService defines one of the mirrors of a Mirroring service.
type MirrorService struct {
LoadBalancerSpec
LoadBalancerSpec `json:",inline"`
Percent int `json:"percent,omitempty"`
}

View file

@ -6,6 +6,7 @@ import (
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:storageversion
// TLSOption is a specification for a TLSOption resource.
type TLSOption struct {
@ -32,12 +33,11 @@ type TLSOptionSpec struct {
// ClientAuth defines the parameters of the client authentication part of the TLS connection, if any.
type ClientAuth struct {
// SecretName is the name of the referenced Kubernetes Secret to specify the
// certificate details.
SecretNames []string `json:"secretNames"`
// SecretName is the name of the referenced Kubernetes Secret to specify the certificate details.
SecretNames []string `json:"secretNames,omitempty"`
// +kubebuilder:validation:Enum=NoClientCert;RequestClientCert;VerifyClientCertIfGiven;RequireAndVerifyClientCert
// ClientAuthType defines the client authentication type to apply.
// The available values are: "NoClientCert", "RequestClientCert", "VerifyClientCertIfGiven" and "RequireAndVerifyClientCert".
ClientAuthType string `json:"clientAuthType"`
ClientAuthType string `json:"clientAuthType,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View file

@ -6,6 +6,7 @@ import (
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:storageversion
// TLSStore is a specification for a TLSStore resource.
type TLSStore struct {
@ -26,9 +27,8 @@ type TLSStoreSpec struct {
// DefaultCertificate holds a secret name for the TLSOption resource.
type DefaultCertificate struct {
// SecretName is the name of the referenced Kubernetes Secret to specify the
// certificate details.
SecretName string `json:"secretName,omitempty"`
// SecretName is the name of the referenced Kubernetes Secret to specify the certificate details.
SecretName string `json:"secretName"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View file

@ -31,6 +31,7 @@ package v1alpha1
import (
dynamic "github.com/traefik/traefik/v2/pkg/config/dynamic"
types "github.com/traefik/traefik/v2/pkg/types"
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
intstr "k8s.io/apimachinery/pkg/util/intstr"
)
@ -507,6 +508,7 @@ func (in *LoadBalancerSpec) DeepCopyInto(out *LoadBalancerSpec) {
*out = new(dynamic.Sticky)
(*in).DeepCopyInto(*out)
}
out.Port = in.Port
if in.PassHostHeader != nil {
in, out := &in.PassHostHeader, &out.PassHostHeader
*out = new(bool)
@ -661,7 +663,7 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) {
}
if in.RateLimit != nil {
in, out := &in.RateLimit, &out.RateLimit
*out = new(dynamic.RateLimit)
*out = new(RateLimit)
(*in).DeepCopyInto(*out)
}
if in.RedirectRegex != nil {
@ -716,7 +718,7 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) {
}
if in.Retry != nil {
in, out := &in.Retry, &out.Retry
*out = new(dynamic.Retry)
*out = new(Retry)
**out = **in
}
if in.ContentType != nil {
@ -726,7 +728,7 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) {
}
if in.Plugin != nil {
in, out := &in.Plugin, &out.Plugin
*out = make(map[string]dynamic.PluginConf, len(*in))
*out = make(map[string]v1.JSON, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
@ -790,6 +792,54 @@ func (in *Mirroring) DeepCopy() *Mirroring {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RateLimit) DeepCopyInto(out *RateLimit) {
*out = *in
if in.Period != nil {
in, out := &in.Period, &out.Period
*out = new(intstr.IntOrString)
**out = **in
}
if in.Burst != nil {
in, out := &in.Burst, &out.Burst
*out = new(int64)
**out = **in
}
if in.SourceCriterion != nil {
in, out := &in.SourceCriterion, &out.SourceCriterion
*out = new(dynamic.SourceCriterion)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimit.
func (in *RateLimit) DeepCopy() *RateLimit {
if in == nil {
return nil
}
out := new(RateLimit)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Retry) DeepCopyInto(out *Retry) {
*out = *in
out.InitialInterval = in.InitialInterval
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Retry.
func (in *Retry) DeepCopy() *Retry {
if in == nil {
return nil
}
out := new(Retry)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Route) DeepCopyInto(out *Route) {
*out = *in
@ -1001,6 +1051,7 @@ func (in *ServiceSpec) DeepCopy() *ServiceSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceTCP) DeepCopyInto(out *ServiceTCP) {
*out = *in
out.Port = in.Port
if in.Weight != nil {
in, out := &in.Weight, &out.Weight
*out = new(int)
@ -1032,6 +1083,7 @@ func (in *ServiceTCP) DeepCopy() *ServiceTCP {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceUDP) DeepCopyInto(out *ServiceUDP) {
*out = *in
out.Port = in.Port
if in.Weight != nil {
in, out := &in.Weight, &out.Weight
*out = new(int)

View file

@ -0,0 +1,52 @@
---
kind: GatewayClass
apiVersion: networking.x-k8s.io/v1alpha1
metadata:
name: my-gateway-class
spec:
controller: traefik.io/gateway-controller
---
kind: Gateway
apiVersion: networking.x-k8s.io/v1alpha1
metadata:
name: my-gateway
namespace: default
spec:
gatewayClassName: my-gateway-class
listeners: # Use GatewayClass defaults for listener definition.
- protocol: HTTP
port: 80
routes:
kind: HTTPRoute
namespaces:
from: Same
selector:
app: foo
---
kind: HTTPRoute
apiVersion: networking.x-k8s.io/v1alpha1
metadata:
name: http-app-1
namespace: default
labels:
app: foo
spec:
hostnames:
- "foo.com"
rules:
- matches:
- path:
type: Exact
value: /bar
forwardTo:
- weight: 1
backendRef:
group: traefik.containo.us
kind: TraefikService
name: service@file
port: 80
- serviceName: whoami
port: 80
weight: 1

View file

@ -0,0 +1,49 @@
---
kind: GatewayClass
apiVersion: networking.x-k8s.io/v1alpha1
metadata:
name: my-gateway-class
spec:
controller: traefik.io/gateway-controller
---
kind: Gateway
apiVersion: networking.x-k8s.io/v1alpha1
metadata:
name: my-gateway
namespace: default
spec:
gatewayClassName: my-gateway-class
listeners: # Use GatewayClass defaults for listener definition.
- protocol: HTTP
port: 80
routes:
kind: HTTPRoute
namespaces:
from: Same
selector:
app: foo
---
kind: HTTPRoute
apiVersion: networking.x-k8s.io/v1alpha1
metadata:
name: http-app-1
namespace: default
labels:
app: foo
spec:
hostnames:
- "foo.com"
rules:
- matches:
- path:
type: Exact
value: /bar
forwardTo:
- weight: 1
backendRef:
group: traefik.containo.us
kind: TraefikService
name: api@internal
port: 80

View file

@ -28,7 +28,11 @@ import (
"sigs.k8s.io/gateway-api/apis/v1alpha1"
)
const providerName = "kubernetesgateway"
const (
providerName = "kubernetesgateway"
traefikServiceKind = "TraefikService"
traefikServiceGroupName = "traefik.containo.us"
)
// Provider holds configurations of the provider.
type Provider struct {
@ -463,29 +467,34 @@ func (p *Provider) fillGatewayConf(client Client, gateway *v1alpha1.Gateway, con
}
if routeRule.ForwardTo != nil {
wrrService, subServices, err := loadServices(client, gateway.Namespace, routeRule.ForwardTo)
if err != nil {
// update "ResolvedRefs" status true with "DroppedRoutes" reason
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{
Type: string(v1alpha1.ListenerConditionResolvedRefs),
Status: metav1.ConditionFalse,
LastTransitionTime: metav1.Now(),
Reason: string(v1alpha1.ListenerReasonDegradedRoutes),
Message: fmt.Sprintf("Cannot load service from HTTPRoute %s/%s : %v", gateway.Namespace, httpRoute.Name, err),
})
// Traefik internal service can be used only if there is only one ForwardTo service reference.
if len(routeRule.ForwardTo) == 1 && isInternalService(routeRule.ForwardTo[0]) {
router.Service = routeRule.ForwardTo[0].BackendRef.Name
} else {
wrrService, subServices, err := loadServices(client, gateway.Namespace, routeRule.ForwardTo)
if err != nil {
// update "ResolvedRefs" status true with "DroppedRoutes" reason
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{
Type: string(v1alpha1.ListenerConditionResolvedRefs),
Status: metav1.ConditionFalse,
LastTransitionTime: metav1.Now(),
Reason: string(v1alpha1.ListenerReasonDegradedRoutes),
Message: fmt.Sprintf("Cannot load service from HTTPRoute %s/%s : %v", gateway.Namespace, httpRoute.Name, err),
})
// TODO update the RouteStatus condition / deduplicate conditions on listener
continue
// TODO update the RouteStatus condition / deduplicate conditions on listener
continue
}
for svcName, svc := range subServices {
conf.HTTP.Services[svcName] = svc
}
serviceName := provider.Normalize(routerKey + "-wrr")
conf.HTTP.Services[serviceName] = wrrService
router.Service = serviceName
}
for svcName, svc := range subServices {
conf.HTTP.Services[svcName] = svc
}
serviceName := provider.Normalize(routerKey + "-wrr")
conf.HTTP.Services[serviceName] = wrrService
router.Service = serviceName
}
if router.Service != "" {
@ -766,6 +775,21 @@ func loadServices(client Client, namespace string, targets []v1alpha1.HTTPRouteF
}
for _, forwardTo := range targets {
weight := int(forwardTo.Weight)
if forwardTo.ServiceName == nil && forwardTo.BackendRef != nil {
if !(forwardTo.BackendRef.Group == traefikServiceGroupName && forwardTo.BackendRef.Kind == traefikServiceKind) {
continue
}
if strings.HasSuffix(forwardTo.BackendRef.Name, "@internal") {
return nil, nil, fmt.Errorf("traefik internal service %s is not allowed in a WRR loadbalancer", forwardTo.BackendRef.Name)
}
wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.WRRService{Name: forwardTo.BackendRef.Name, Weight: &weight})
continue
}
if forwardTo.ServiceName == nil {
continue
}
@ -776,8 +800,6 @@ func loadServices(client Client, namespace string, targets []v1alpha1.HTTPRouteF
},
}
// TODO Handle BackendRefs
service, exists, err := client.GetService(namespace, *forwardTo.ServiceName)
if err != nil {
return nil, nil, err
@ -856,11 +878,10 @@ func loadServices(client Client, namespace string, targets []v1alpha1.HTTPRouteF
serviceName := provider.Normalize(makeID(service.Namespace, service.Name) + "-" + portStr)
services[serviceName] = &svc
weight := int(forwardTo.Weight)
wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.WRRService{Name: serviceName, Weight: &weight})
}
if len(services) == 0 {
if len(wrrSvc.Weighted.Services) == 0 {
return nil, nil, errors.New("no service has been created")
}
@ -905,3 +926,11 @@ func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *s
return eventsChanBuffered
}
func isInternalService(forwardTo v1alpha1.HTTPRouteForwardTo) bool {
return forwardTo.ServiceName == nil &&
forwardTo.BackendRef != nil &&
forwardTo.BackendRef.Kind == traefikServiceKind &&
forwardTo.BackendRef.Group == traefikServiceGroupName &&
strings.HasSuffix(forwardTo.BackendRef.Name, "@internal")
}

View file

@ -234,6 +234,92 @@ func TestLoadHTTPRoutes(t *testing.T) {
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Simple HTTPRoute, with api@internal service",
paths: []string{"services.yml", "simple_to_api_internal.yml"},
entryPoints: map[string]Entrypoint{"web": {
Address: ":80",
}},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06": {
EntryPoints: []string{"web"},
Service: "api@internal",
Rule: "Host(`foo.com`) && Path(`/bar`)",
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Simple HTTPRoute, with myservice@file service",
paths: []string{"services.yml", "simple_cross_provider.yml"},
entryPoints: map[string]Entrypoint{"web": {
Address: ":80",
}},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06": {
EntryPoints: []string{"web"},
Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr",
Rule: "Host(`foo.com`) && Path(`/bar`)",
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "service@file",
Weight: func(i int) *int { return &i }(1),
},
{
Name: "default-whoami-80",
Weight: func(i int) *int { return &i }(1),
},
},
},
},
"default-whoami-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: Bool(true),
},
},
},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Simple HTTPRoute with protocol HTTPS",
paths: []string{"services.yml", "with_protocol_https.yml"},

View file

@ -1,11 +1,9 @@
package ingress
import (
"k8s.io/api/networking/v1beta1"
)
import networkingv1 "k8s.io/api/networking/v1"
func buildIngress(opts ...func(*v1beta1.Ingress)) *v1beta1.Ingress {
i := &v1beta1.Ingress{}
func buildIngress(opts ...func(*networkingv1.Ingress)) *networkingv1.Ingress {
i := &networkingv1.Ingress{}
i.Kind = "Ingress"
for _, opt := range opts {
opt(i)
@ -13,15 +11,15 @@ func buildIngress(opts ...func(*v1beta1.Ingress)) *v1beta1.Ingress {
return i
}
func iNamespace(value string) func(*v1beta1.Ingress) {
return func(i *v1beta1.Ingress) {
func iNamespace(value string) func(*networkingv1.Ingress) {
return func(i *networkingv1.Ingress) {
i.Namespace = value
}
}
func iRules(opts ...func(*v1beta1.IngressSpec)) func(*v1beta1.Ingress) {
return func(i *v1beta1.Ingress) {
s := &v1beta1.IngressSpec{}
func iRules(opts ...func(*networkingv1.IngressSpec)) func(*networkingv1.Ingress) {
return func(i *networkingv1.Ingress) {
s := &networkingv1.IngressSpec{}
for _, opt := range opts {
opt(s)
}
@ -29,9 +27,9 @@ func iRules(opts ...func(*v1beta1.IngressSpec)) func(*v1beta1.Ingress) {
}
}
func iRule(opts ...func(*v1beta1.IngressRule)) func(*v1beta1.IngressSpec) {
return func(spec *v1beta1.IngressSpec) {
r := &v1beta1.IngressRule{}
func iRule(opts ...func(*networkingv1.IngressRule)) func(*networkingv1.IngressSpec) {
return func(spec *networkingv1.IngressSpec) {
r := &networkingv1.IngressRule{}
for _, opt := range opts {
opt(r)
}
@ -39,24 +37,24 @@ func iRule(opts ...func(*v1beta1.IngressRule)) func(*v1beta1.IngressSpec) {
}
}
func iHost(name string) func(*v1beta1.IngressRule) {
return func(rule *v1beta1.IngressRule) {
func iHost(name string) func(*networkingv1.IngressRule) {
return func(rule *networkingv1.IngressRule) {
rule.Host = name
}
}
func iTLSes(opts ...func(*v1beta1.IngressTLS)) func(*v1beta1.Ingress) {
return func(i *v1beta1.Ingress) {
func iTLSes(opts ...func(*networkingv1.IngressTLS)) func(*networkingv1.Ingress) {
return func(i *networkingv1.Ingress) {
for _, opt := range opts {
iTLS := v1beta1.IngressTLS{}
iTLS := networkingv1.IngressTLS{}
opt(&iTLS)
i.Spec.TLS = append(i.Spec.TLS, iTLS)
}
}
}
func iTLS(secret string, hosts ...string) func(*v1beta1.IngressTLS) {
return func(i *v1beta1.IngressTLS) {
func iTLS(secret string, hosts ...string) func(*networkingv1.IngressTLS) {
return func(i *networkingv1.IngressTLS) {
i.SecretName = secret
i.Hosts = hosts
}

View file

@ -13,11 +13,12 @@ import (
"github.com/traefik/traefik/v2/pkg/log"
traefikversion "github.com/traefik/traefik/v2/pkg/version"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
networkingv1beta1 "k8s.io/api/networking/v1beta1"
kubeerror "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
@ -54,12 +55,12 @@ func (reh *resourceEventHandler) OnDelete(obj interface{}) {
// The stores can then be accessed via the Get* functions.
type Client interface {
WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error)
GetIngresses() []*networkingv1beta1.Ingress
GetIngresses() []*networkingv1.Ingress
GetIngressClasses() ([]*networkingv1beta1.IngressClass, error)
GetService(namespace, name string) (*corev1.Service, bool, error)
GetSecret(namespace, name string) (*corev1.Secret, bool, error)
GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error)
UpdateIngressStatus(ing *networkingv1beta1.Ingress, ingStatus []corev1.LoadBalancerIngress) error
UpdateIngressStatus(ing *networkingv1.Ingress, ingStatus []corev1.LoadBalancerIngress) error
GetServerVersion() (*version.Version, error)
}
@ -167,9 +168,20 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
opts.LabelSelector = c.ingressLabelSelector
}
serverVersion, err := c.GetServerVersion()
if err != nil {
return nil, fmt.Errorf("failed to get server version: %w", err)
}
for _, ns := range namespaces {
factoryIngress := informers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, informers.WithNamespace(ns), informers.WithTweakListOptions(matchesLabelSelector))
factoryIngress.Extensions().V1beta1().Ingresses().Informer().AddEventHandler(eventHandler)
if supportsNetworkingV1Ingress(serverVersion) {
factoryIngress.Networking().V1().Ingresses().Informer().AddEventHandler(eventHandler)
} else {
factoryIngress.Networking().V1beta1().Ingresses().Informer().AddEventHandler(eventHandler)
}
c.factoriesIngress[ns] = factoryIngress
factoryKube := informers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, informers.WithNamespace(ns))
@ -208,12 +220,6 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
}
}
serverVersion, err := c.GetServerVersion()
if err != nil {
log.WithoutContext().Errorf("Failed to get server version: %v", err)
return eventCh, nil
}
if supportsIngressClass(serverVersion) {
c.clusterFactory = informers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod)
c.clusterFactory.Networking().V1beta1().IngressClasses().Informer().AddEventHandler(eventHandler)
@ -230,42 +236,59 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
}
// GetIngresses returns all Ingresses for observed namespaces in the cluster.
func (c *clientWrapper) GetIngresses() []*networkingv1beta1.Ingress {
var results []*networkingv1beta1.Ingress
func (c *clientWrapper) GetIngresses() []*networkingv1.Ingress {
var results []*networkingv1.Ingress
serverVersion, err := c.GetServerVersion()
if err != nil {
log.Errorf("Failed to get server version: %v", err)
return results
}
isNetworkingV1Supported := supportsNetworkingV1Ingress(serverVersion)
for ns, factory := range c.factoriesIngress {
// extensions
ings, err := factory.Extensions().V1beta1().Ingresses().Lister().List(labels.Everything())
if err != nil {
log.Errorf("Failed to list ingresses in namespace %s: %v", ns, err)
}
for _, ing := range ings {
n, err := extensionsToNetworking(ing)
if isNetworkingV1Supported {
// networking
listNew, err := factory.Networking().V1().Ingresses().Lister().List(labels.Everything())
if err != nil {
log.Errorf("Failed to convert ingress %s from extensions/v1beta1 to networking/v1beta1: %v", ns, err)
log.WithoutContext().Errorf("Failed to list ingresses in namespace %s: %v", ns, err)
continue
}
results = append(results, n)
results = append(results, listNew...)
continue
}
// networking
// networking beta
list, err := factory.Networking().V1beta1().Ingresses().Lister().List(labels.Everything())
if err != nil {
log.Errorf("Failed to list ingresses in namespace %s: %v", ns, err)
log.WithoutContext().Errorf("Failed to list ingresses in namespace %s: %v", ns, err)
continue
}
for _, ing := range list {
n, err := toNetworkingV1(ing)
if err != nil {
log.WithoutContext().Errorf("Failed to convert ingress %s from networking/v1beta1 to networking/v1: %v", ns, err)
continue
}
addServiceFromV1Beta1(n, *ing)
results = append(results, n)
}
results = append(results, list...)
}
return results
}
func extensionsToNetworking(ing marshaler) (*networkingv1beta1.Ingress, error) {
func toNetworkingV1(ing marshaler) (*networkingv1.Ingress, error) {
data, err := ing.Marshal()
if err != nil {
return nil, err
}
ni := &networkingv1beta1.Ingress{}
ni := &networkingv1.Ingress{}
err = ni.Unmarshal(data)
if err != nil {
return nil, err
@ -274,16 +297,95 @@ func extensionsToNetworking(ing marshaler) (*networkingv1beta1.Ingress, error) {
return ni, nil
}
func addServiceFromV1Beta1(ing *networkingv1.Ingress, old networkingv1beta1.Ingress) {
if old.Spec.Backend != nil {
port := networkingv1.ServiceBackendPort{}
if old.Spec.Backend.ServicePort.Type == intstr.Int {
port.Number = old.Spec.Backend.ServicePort.IntVal
} else {
port.Name = old.Spec.Backend.ServicePort.StrVal
}
if old.Spec.Backend.ServiceName != "" {
ing.Spec.DefaultBackend = &networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: old.Spec.Backend.ServiceName,
Port: port,
},
}
}
}
for rc, rule := range ing.Spec.Rules {
if rule.HTTP == nil {
continue
}
for pc, path := range rule.HTTP.Paths {
if path.Backend.Service == nil {
oldBackend := old.Spec.Rules[rc].HTTP.Paths[pc].Backend
port := networkingv1.ServiceBackendPort{}
if oldBackend.ServicePort.Type == intstr.Int {
port.Number = oldBackend.ServicePort.IntVal
} else {
port.Name = oldBackend.ServicePort.StrVal
}
svc := networkingv1.IngressServiceBackend{
Name: oldBackend.ServiceName,
Port: port,
}
ing.Spec.Rules[rc].HTTP.Paths[pc].Backend.Service = &svc
}
}
}
}
// UpdateIngressStatus updates an Ingress with a provided status.
func (c *clientWrapper) UpdateIngressStatus(src *networkingv1beta1.Ingress, ingStatus []corev1.LoadBalancerIngress) error {
func (c *clientWrapper) UpdateIngressStatus(src *networkingv1.Ingress, ingStatus []corev1.LoadBalancerIngress) error {
if !c.isWatchedNamespace(src.Namespace) {
return fmt.Errorf("failed to get ingress %s/%s: namespace is not within watched namespaces", src.Namespace, src.Name)
}
if src.GetObjectKind().GroupVersionKind().Group != "networking.k8s.io" {
serverVersion, err := c.GetServerVersion()
if err != nil {
log.WithoutContext().Errorf("Failed to get server version: %v", err)
return err
}
if !supportsNetworkingV1Ingress(serverVersion) {
return c.updateIngressStatusOld(src, ingStatus)
}
ing, err := c.factoriesIngress[c.lookupNamespace(src.Namespace)].Networking().V1().Ingresses().Lister().Ingresses(src.Namespace).Get(src.Name)
if err != nil {
return fmt.Errorf("failed to get ingress %s/%s: %w", src.Namespace, src.Name, err)
}
logger := log.WithoutContext().WithField("namespace", ing.Namespace).WithField("ingress", ing.Name)
if isLoadBalancerIngressEquals(ing.Status.LoadBalancer.Ingress, ingStatus) {
logger.Debug("Skipping ingress status update")
return nil
}
ingCopy := ing.DeepCopy()
ingCopy.Status = networkingv1.IngressStatus{LoadBalancer: corev1.LoadBalancerStatus{Ingress: ingStatus}}
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
_, err = c.clientset.NetworkingV1().Ingresses(ingCopy.Namespace).UpdateStatus(ctx, ingCopy, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("failed to update ingress status %s/%s: %w", src.Namespace, src.Name, err)
}
logger.Info("Updated ingress status")
return nil
}
func (c *clientWrapper) updateIngressStatusOld(src *networkingv1.Ingress, ingStatus []corev1.LoadBalancerIngress) error {
ing, err := c.factoriesIngress[c.lookupNamespace(src.Namespace)].Networking().V1beta1().Ingresses().Lister().Ingresses(src.Namespace).Get(src.Name)
if err != nil {
return fmt.Errorf("failed to get ingress %s/%s: %w", src.Namespace, src.Name, err)
@ -306,35 +408,6 @@ func (c *clientWrapper) UpdateIngressStatus(src *networkingv1beta1.Ingress, ingS
if err != nil {
return fmt.Errorf("failed to update ingress status %s/%s: %w", src.Namespace, src.Name, err)
}
logger.Info("Updated ingress status")
return nil
}
func (c *clientWrapper) updateIngressStatusOld(src *networkingv1beta1.Ingress, ingStatus []corev1.LoadBalancerIngress) error {
ing, err := c.factoriesIngress[c.lookupNamespace(src.Namespace)].Extensions().V1beta1().Ingresses().Lister().Ingresses(src.Namespace).Get(src.Name)
if err != nil {
return fmt.Errorf("failed to get ingress %s/%s: %w", src.Namespace, src.Name, err)
}
logger := log.WithoutContext().WithField("namespace", ing.Namespace).WithField("ingress", ing.Name)
if isLoadBalancerIngressEquals(ing.Status.LoadBalancer.Ingress, ingStatus) {
logger.Debug("Skipping ingress status update")
return nil
}
ingCopy := ing.DeepCopy()
ingCopy.Status = extensionsv1beta1.IngressStatus{LoadBalancer: corev1.LoadBalancerStatus{Ingress: ingStatus}}
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
_, err = c.clientset.ExtensionsV1beta1().Ingresses(ingCopy.Namespace).UpdateStatus(ctx, ingCopy, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("failed to update ingress status %s/%s: %w", src.Namespace, src.Name, err)
}
logger.Info("Updated ingress status")
return nil
}
@ -475,3 +548,24 @@ func supportsIngressClass(serverVersion *version.Version) bool {
return ingressClassVersion.LessThanOrEqual(serverVersion)
}
// filterIngressClassByName return a slice containing ingressclasses with the correct name.
func filterIngressClassByName(ingressClassName string, ics []*networkingv1beta1.IngressClass) []*networkingv1beta1.IngressClass {
var ingressClasses []*networkingv1beta1.IngressClass
for _, ic := range ics {
if ic.Name == ingressClassName {
ingressClasses = append(ingressClasses, ic)
}
}
return ingressClasses
}
// Ingress in networking.k8s.io/v1 is supported starting 1.19.
// thus, we query it in K8s starting 1.19.
func supportsNetworkingV1Ingress(serverVersion *version.Version) bool {
ingressNetworkingVersion := version.Must(version.NewVersion("1.19"))
return serverVersion.GreaterThanOrEqual(ingressNetworkingVersion)
}

View file

@ -7,14 +7,14 @@ import (
"github.com/hashicorp/go-version"
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/k8s"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
networkingv1beta1 "k8s.io/api/networking/v1beta1"
)
var _ Client = (*clientMock)(nil)
type clientMock struct {
ingresses []*networkingv1beta1.Ingress
ingresses []*networkingv1.Ingress
services []*corev1.Service
secrets []*corev1.Secret
endpoints []*corev1.Endpoints
@ -51,13 +51,14 @@ func newClientMock(serverVersion string, paths ...string) clientMock {
case *corev1.Endpoints:
c.endpoints = append(c.endpoints, o)
case *networkingv1beta1.Ingress:
c.ingresses = append(c.ingresses, o)
case *extensionsv1beta1.Ingress:
ing, err := extensionsToNetworking(o)
ing, err := toNetworkingV1(o)
if err != nil {
panic(err)
}
addServiceFromV1Beta1(ing, *o)
c.ingresses = append(c.ingresses, ing)
case *networkingv1.Ingress:
c.ingresses = append(c.ingresses, o)
case *networkingv1beta1.IngressClass:
c.ingressClasses = append(c.ingressClasses, o)
default:
@ -69,7 +70,7 @@ func newClientMock(serverVersion string, paths ...string) clientMock {
return c
}
func (c clientMock) GetIngresses() []*networkingv1beta1.Ingress {
func (c clientMock) GetIngresses() []*networkingv1.Ingress {
return c.ingresses
}
@ -125,6 +126,6 @@ func (c clientMock) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-cha
return c.watchChan, nil
}
func (c clientMock) UpdateIngressStatus(_ *networkingv1beta1.Ingress, _ []corev1.LoadBalancerIngress) error {
func (c clientMock) UpdateIngressStatus(_ *networkingv1.Ingress, _ []corev1.LoadBalancerIngress) error {
return c.apiIngressStatusError
}

View file

@ -8,9 +8,13 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/api/networking/v1beta1"
kubeerror "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/version"
fakediscovery "k8s.io/client-go/discovery/fake"
kubefake "k8s.io/client-go/kubernetes/fake"
)
@ -149,6 +153,11 @@ func TestClientIgnoresHelmOwnedSecrets(t *testing.T) {
kubeClient := kubefake.NewSimpleClientset(helmSecret, secret)
discovery, _ := kubeClient.Discovery().(*fakediscovery.FakeDiscovery)
discovery.FakedServerVersion = &version.Info{
GitVersion: "v1.19",
}
client := newClientImpl(kubeClient)
stopCh := make(chan struct{})
@ -180,3 +189,72 @@ func TestClientIgnoresHelmOwnedSecrets(t *testing.T) {
require.NoError(t, err)
assert.False(t, found)
}
func TestClientUsesCorrectServerVersion(t *testing.T) {
ingressV1Beta := &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "ingress-v1beta",
},
}
ingressV1 := &networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "ingress-v1",
},
}
kubeClient := kubefake.NewSimpleClientset(ingressV1Beta, ingressV1)
discovery, _ := kubeClient.Discovery().(*fakediscovery.FakeDiscovery)
discovery.FakedServerVersion = &version.Info{
GitVersion: "v1.18.12+foobar",
}
stopCh := make(chan struct{})
client := newClientImpl(kubeClient)
eventCh, err := client.WatchAll(nil, stopCh)
require.NoError(t, err)
select {
case event := <-eventCh:
ingress, ok := event.(*v1beta1.Ingress)
require.True(t, ok)
assert.Equal(t, "ingress-v1beta", ingress.Name)
case <-time.After(50 * time.Millisecond):
assert.Fail(t, "expected to receive event for ingress")
}
select {
case <-eventCh:
assert.Fail(t, "received more than one event")
case <-time.After(50 * time.Millisecond):
}
discovery.FakedServerVersion = &version.Info{
GitVersion: "v1.19",
}
eventCh, err = client.WatchAll(nil, stopCh)
require.NoError(t, err)
select {
case event := <-eventCh:
ingress, ok := event.(*networkingv1.Ingress)
require.True(t, ok)
assert.Equal(t, "ingress-v1", ingress.Name)
case <-time.After(50 * time.Millisecond):
assert.Fail(t, "expected to receive event for ingress")
}
select {
case <-eventCh:
assert.Fail(t, "received more than one event")
case <-time.After(50 * time.Millisecond):
}
}

View file

@ -0,0 +1,11 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service1
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080

View file

@ -0,0 +1,30 @@
kind: Ingress
apiVersion: networking.k8s.io/v1beta1
metadata:
name: ""
namespace: testing
spec:
ingressClassName: traefik-lb
rules:
- http:
paths:
- path: /bar
backend:
serviceName: service1
servicePort: 80
---
kind: Ingress
apiVersion: networking.k8s.io/v1beta1
metadata:
name: ""
namespace: testing
spec:
ingressClassName: traefik-lb2
rules:
- http:
paths:
- path: /foo
backend:
serviceName: service1
servicePort: 80

View file

@ -0,0 +1,14 @@
apiVersion: networking.k8s.io/v1beta1
kind: IngressClass
metadata:
name: traefik-lb2
spec:
controller: traefik.io/ingress-controller
---
apiVersion: networking.k8s.io/v1beta1
kind: IngressClass
metadata:
name: traefik-lb
spec:
controller: traefik.io/ingress-controller

View file

@ -0,0 +1,10 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- port: 80
clusterIP: 10.0.0.1

View file

@ -0,0 +1,24 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service1
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 80
---
kind: Endpoints
apiVersion: v1
metadata:
name: defaultservice
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080

View file

@ -0,0 +1,12 @@
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: defaultbackend
namespace: testing
spec:
defaultBackend:
service:
name: defaultservice
port:
number: 8080

View file

@ -0,0 +1,22 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- port: 80
clusterIP: 10.0.0.1
---
kind: Service
apiVersion: v1
metadata:
name: defaultservice
namespace: testing
spec:
ports:
- port: 8080
clusterIP: 10.0.0.1

View file

@ -0,0 +1,11 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service1
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080

View file

@ -0,0 +1,18 @@
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: ""
namespace: testing
annotations:
traefik.ingress.kubernetes.io/router.pathmatcher: Path
spec:
rules:
- http:
paths:
- path: /bar
pathType: ""
backend:
service:
name: service1
port:
number: 80

View file

@ -0,0 +1,10 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- port: 80
clusterIP: 10.0.0.1

Some files were not shown because too many files have changed in this diff Show more