diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index 0a772de1e..c150f5f5e 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -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 diff --git a/cmd/traefik/traefik_test.go b/cmd/traefik/traefik_test.go new file mode 100644 index 000000000..26bc8643c --- /dev/null +++ b/cmd/traefik/traefik_test.go @@ -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) + }) + } +} diff --git a/docs/content/middlewares/overview.md b/docs/content/middlewares/overview.md index 86fe2808c..69e7d8000 100644 --- a/docs/content/middlewares/overview.md +++ b/docs/content/middlewares/overview.md @@ -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 diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index d75e6e5c1..f4b9c8efc 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -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. diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index 829be2a4f..51859c4e1 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -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 diff --git a/docs/content/providers/kubernetes-crd.md b/docs/content/providers/kubernetes-crd.md index 4465b66f2..54c7f512a 100644 --- a/docs/content/providers/kubernetes-crd.md +++ b/docs/content/providers/kubernetes-crd.md @@ -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" diff --git a/docs/content/providers/kubernetes-ingress.md b/docs/content/providers/kubernetes-ingress.md index 438d16a29..ccbde7969 100644 --- a/docs/content/providers/kubernetes-ingress.md +++ b/docs/content/providers/kubernetes-ingress.md @@ -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,11 +250,13 @@ 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 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. @@ -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" diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index ea5e29fcf..6f2cd4d52 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -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" diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index f707c4a03..563eb3cf2 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -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: diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml new file mode 100644 index 000000000..3ec900d5f --- /dev/null +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -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" diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1beta1.yml similarity index 100% rename from docs/content/reference/dynamic-configuration/kubernetes-crd-definition.yml rename to docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1beta1.yml diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml index cbe4b3e34..56c5e7419 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml @@ -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 diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd.md b/docs/content/reference/dynamic-configuration/kubernetes-crd.md index 477b082f9..ccda41843 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd.md +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd.md @@ -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 diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml b/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml index 821a8fccc..c25130ac5 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml @@ -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 diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutes.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutes.yaml new file mode 100644 index 000000000..726341ba8 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutes.yaml @@ -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: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutetcps.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutetcps.yaml new file mode 100644 index 000000000..1347fa776 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutetcps.yaml @@ -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: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressrouteudps.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressrouteudps.yaml new file mode 100644 index 000000000..86b045463 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressrouteudps.yaml @@ -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: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml new file mode 100644 index 000000000..a5b8029d9 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml @@ -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: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml new file mode 100644 index 000000000..0e7efb766 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml @@ -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: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml new file mode 100644 index 000000000..3b9bdc2f2 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml @@ -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: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsstores.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsstores.yaml new file mode 100644 index 000000000..8bc871801 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsstores.yaml @@ -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: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_traefikservices.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_traefikservices.yaml new file mode 100644 index 000000000..a0792d77c --- /dev/null +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_traefikservices.yaml @@ -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: [] diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index b612b2693..090eb1ec1 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -102,6 +102,9 @@ Entry points definition. (Default: ```false```) `--entrypoints..address`: Entry point address. +`--entrypoints..enablehttp3`: +Enable HTTP3. (Default: ```false```) + `--entrypoints..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..transport.respondingtimeouts.writetimeout`: WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```) +`--entrypoints..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. diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 26e87c629..603921993 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -102,6 +102,9 @@ Entry points definition. (Default: ```false```) `TRAEFIK_ENTRYPOINTS__ADDRESS`: Entry point address. +`TRAEFIK_ENTRYPOINTS__ENABLEHTTP3`: +Enable HTTP3. (Default: ```false```) + `TRAEFIK_ENTRYPOINTS__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__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__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. diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index d54b7d356..1b46ccb86 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -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 diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index 5f4749ade..25480afb2 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -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 diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index 444b2f57a..36db2cc9f 100644 --- a/docs/content/routing/entrypoints.md +++ b/docs/content/routing/entrypoints.md @@ -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 +``` diff --git a/docs/content/routing/providers/kubernetes-crd.md b/docs/content/routing/providers/kubernetes-crd.md index d54175da1..808884ecb 100644 --- a/docs/content/routing/providers/kubernetes-crd.md +++ b/docs/content/routing/providers/kubernetes-crd.md @@ -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). diff --git a/docs/content/routing/providers/kubernetes-gateway.md b/docs/content/routing/providers/kubernetes-gateway.md index f9e0772e4..755374d75 100644 --- a/docs/content/routing/providers/kubernetes-gateway.md +++ b/docs/content/routing/providers/kubernetes-gateway.md @@ -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. | diff --git a/docs/content/routing/providers/kubernetes-ingress.md b/docs/content/routing/providers/kubernetes-ingress.md index ba6e3bd81..9550bd8ec 100644 --- a/docs/content/routing/providers/kubernetes-ingress.md +++ b/docs/content/routing/providers/kubernetes-ingress.md @@ -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 @@ -732,6 +814,31 @@ For more options, please refer to the available [annotations](#on-ingress). tls: - 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 @@ -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. diff --git a/docs/content/routing/routers/index.md b/docs/content/routing/routers/index.md index 8592ecc1e..914f5008c 100644 --- a/docs/content/routing/routers/index.md +++ b/docs/content/routing/routers/index.md @@ -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 diff --git a/docs/content/routing/services/index.md b/docs/content/routing/services/index.md index 2d69d42e6..5add1bbbd 100644 --- a/docs/content/routing/services/index.md +++ b/docs/content/routing/services/index.md @@ -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. diff --git a/docs/content/user-guides/crd-acme/index.md b/docs/content/user-guides/crd-acme/index.md index 100f63b89..04e63807b 100644 --- a/docs/content/user-guides/crd-acme/index.md +++ b/docs/content/user-guides/crd-acme/index.md @@ -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" diff --git a/go.mod b/go.mod index 3d76db417..f53580f17 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index ed9ed62ce..e02163ffe 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/integration/fixtures/k8s/01-crd.yml b/integration/fixtures/k8s/01-crd.yml deleted file mode 100644 index 2bf53b37a..000000000 --- a/integration/fixtures/k8s/01-crd.yml +++ /dev/null @@ -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 diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml new file mode 100644 index 000000000..956e36f2f --- /dev/null +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -0,0 +1,1380 @@ + +--- +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: [] + +--- +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: [] + +--- +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: [] + +--- +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: [] + +--- +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: [] + +--- +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: [] + +--- +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: [] + +--- +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: [] diff --git a/integration/fixtures/k8s/05-ingressrouteudp.yml b/integration/fixtures/k8s/05-ingressrouteudp.yml index 535a9e4d4..358559c23 100644 --- a/integration/fixtures/k8s/05-ingressrouteudp.yml +++ b/integration/fixtures/k8s/05-ingressrouteudp.yml @@ -12,3 +12,5 @@ spec: - name: whoamiudp namespace: default port: 8090 + - name: externalname-svc + port: 9090 diff --git a/integration/fixtures/k8s/08-ingressclass.yml b/integration/fixtures/k8s/08-ingressclass.yml new file mode 100644 index 000000000..7ae47a208 --- /dev/null +++ b/integration/fixtures/k8s/08-ingressclass.yml @@ -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 diff --git a/integration/fixtures/k8s_ingressclass.toml b/integration/fixtures/k8s_ingressclass.toml new file mode 100644 index 000000000..51848e026 --- /dev/null +++ b/integration/fixtures/k8s_ingressclass.toml @@ -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" diff --git a/integration/k8s_test.go b/integration/k8s_test.go index 392c327cf..d2d658300 100644 --- a/integration/k8s_test.go +++ b/integration/k8s_test.go @@ -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) diff --git a/integration/resources/compose/k8s.yml b/integration/resources/compose/k8s.yml index 83f313c29..01316d95c 100644 --- a/integration/resources/compose/k8s.yml +++ b/integration/resources/compose/k8s.yml @@ -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 diff --git a/integration/testdata/rawdata-crd.json b/integration/testdata/rawdata-crd.json index ea43e5b5d..8b7eaad6e 100644 --- a/integration/testdata/rawdata-crd.json +++ b/integration/testdata/rawdata-crd.json @@ -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" diff --git a/integration/testdata/rawdata-ingress.json b/integration/testdata/rawdata-ingress.json index ffa2315ec..920154fcc 100644 --- a/integration/testdata/rawdata-ingress.json +++ b/integration/testdata/rawdata-ingress.json @@ -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": [ diff --git a/integration/testdata/rawdata-ingressclass.json b/integration/testdata/rawdata-ingressclass.json new file mode 100644 index 000000000..53db99cef --- /dev/null +++ b/integration/testdata/rawdata-ingressclass.json @@ -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" + } + } +} \ No newline at end of file diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index 1d727a2b8..ec359a656 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -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 diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go index 3823cb4d6..823a14e10 100644 --- a/pkg/config/static/entrypoints.go +++ b/pkg/config/static/entrypoints.go @@ -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) +} diff --git a/pkg/config/static/experimental.go b/pkg/config/static/experimental.go index fbb91fa9f..e6be2525c 100644 --- a/pkg/config/static/experimental.go +++ b/pkg/config/static/experimental.go @@ -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"` } diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index 3bac3b04f..c9c41888b 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -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") diff --git a/pkg/metrics/datadog.go b/pkg/metrics/datadog.go index 605426689..e7f28d65d 100644 --- a/pkg/metrics/datadog.go +++ b/pkg/metrics/datadog.go @@ -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 { diff --git a/pkg/metrics/datadog_test.go b/pkg/metrics/datadog_test.go index e10626e75..af086f725 100644 --- a/pkg/metrics/datadog_test.go +++ b/pkg/metrics/datadog_test.go @@ -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) }) } diff --git a/pkg/metrics/influxdb.go b/pkg/metrics/influxdb.go index 6cf33433e..8c6b5b03f 100644 --- a/pkg/metrics/influxdb.go +++ b/pkg/metrics/influxdb.go @@ -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 { diff --git a/pkg/metrics/influxdb_test.go b/pkg/metrics/influxdb_test.go index 124392d06..30b381e59 100644 --- a/pkg/metrics/influxdb_test.go +++ b/pkg/metrics/influxdb_test.go @@ -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) { diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index fe48f4555..a5c28571d 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -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 } diff --git a/pkg/metrics/prometheus.go b/pkg/metrics/prometheus.go index 735f6ee29..a41fc67ff 100644 --- a/pkg/metrics/prometheus.go +++ b/pkg/metrics/prometheus.go @@ -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, diff --git a/pkg/metrics/prometheus_test.go b/pkg/metrics/prometheus_test.go index af6ed5d2a..6fc10ee73 100644 --- a/pkg/metrics/prometheus_test.go +++ b/pkg/metrics/prometheus_test.go @@ -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{ diff --git a/pkg/metrics/statsd.go b/pkg/metrics/statsd.go index 552741f36..3ef6a72ac 100644 --- a/pkg/metrics/statsd.go +++ b/pkg/metrics/statsd.go @@ -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 { diff --git a/pkg/metrics/statsd_test.go b/pkg/metrics/statsd_test.go index eb3af9e5d..3b8deca06 100644 --- a/pkg/metrics/statsd_test.go +++ b/pkg/metrics/statsd_test.go @@ -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) }) } diff --git a/pkg/middlewares/accesslog/logdata.go b/pkg/middlewares/accesslog/logdata.go index 37f415ed5..1db168446 100644 --- a/pkg/middlewares/accesslog/logdata.go +++ b/pkg/middlewares/accesslog/logdata.go @@ -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. diff --git a/pkg/middlewares/accesslog/logger.go b/pkg/middlewares/accesslog/logger.go index b0e273a1f..b3657b67a 100644 --- a/pkg/middlewares/accesslog/logger.go +++ b/pkg/middlewares/accesslog/logger.go @@ -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 diff --git a/pkg/middlewares/accesslog/logger_test.go b/pkg/middlewares/accesslog/logger_test.go index 3f37b8312..02f4e6989 100644 --- a/pkg/middlewares/accesslog/logger_test.go +++ b/pkg/middlewares/accesslog/logger_test.go @@ -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)) diff --git a/pkg/middlewares/metrics/metrics.go b/pkg/middlewares/metrics/metrics.go index c824b5749..116afa850 100644 --- a/pkg/middlewares/metrics/metrics.go +++ b/pkg/middlewares/metrics/metrics.go @@ -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 } diff --git a/pkg/provider/configuration.go b/pkg/provider/configuration.go index 972111f91..a708c72af 100644 --- a/pkg/provider/configuration.go +++ b/pkg/provider/configuration.go @@ -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" ) diff --git a/pkg/provider/file/file.go b/pkg/provider/file/file.go index d6a8847c5..37ee21d9f 100644 --- a/pkg/provider/file/file.go +++ b/pkg/provider/file/file.go @@ -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" diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/services.yml b/pkg/provider/kubernetes/crd/fixtures/udp/services.yml index 887ddc385..daf6dd2ad 100644 --- a/pkg/provider/kubernetes/crd/fixtures/udp/services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/udp/services.yml @@ -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 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname.yml new file mode 100644 index 000000000..14c3ab658 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname.yml @@ -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 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_with_port.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_with_port.yml new file mode 100644 index 000000000..962178ed5 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_with_port.yml @@ -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 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_without_ports.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_without_ports.yml new file mode 100644 index 000000000..6478e6aa4 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_without_ports.yml @@ -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 diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index ccda4a173..086757fe9 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -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) { diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index 57a16cab5..79f848035 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -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) { diff --git a/pkg/provider/kubernetes/crd/kubernetes_tcp.go b/pkg/provider/kubernetes/crd/kubernetes_tcp.go index fe84dffbe..115522032 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_tcp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_tcp.go @@ -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} diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index 9b2fa2e60..51b1dd465 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -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: "", }, }, }, diff --git a/pkg/provider/kubernetes/crd/kubernetes_udp.go b/pkg/provider/kubernetes/crd/kubernetes_udp.go index 0346084a2..808b6bff0 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_udp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_udp.go @@ -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 } diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go index 670c295e6..0de26e8fa 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go @@ -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 { diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go index ab38c16ea..a508328e7 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go @@ -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 { diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressrouteudp.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressrouteudp.go index 509de5b3c..eb44cd64f 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressrouteudp.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressrouteudp.go @@ -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 { diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go index 07e96611f..2fff164a5 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go @@ -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"` +} diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go index 37f211ff1..dd8b00ff5 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go @@ -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 diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/service.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/service.go index 2e01f8506..fa70a253b 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/service.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/service.go @@ -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"` } diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go index 6c6b67ad8..52eb84a48 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go @@ -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 diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go index 25dee994a..404f07f96 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go @@ -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 diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go index 987fffe09..eb264ce72 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go @@ -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) diff --git a/pkg/provider/kubernetes/gateway/fixtures/simple_cross_provider.yml b/pkg/provider/kubernetes/gateway/fixtures/simple_cross_provider.yml new file mode 100644 index 000000000..744599728 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/simple_cross_provider.yml @@ -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 \ No newline at end of file diff --git a/pkg/provider/kubernetes/gateway/fixtures/simple_to_api_internal.yml b/pkg/provider/kubernetes/gateway/fixtures/simple_to_api_internal.yml new file mode 100644 index 000000000..b86f2773f --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/simple_to_api_internal.yml @@ -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 diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go index 4fe66f89f..2efdf9d42 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes.go +++ b/pkg/provider/kubernetes/gateway/kubernetes.go @@ -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") +} diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go index 676f976b5..68ec0f74f 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes_test.go +++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go @@ -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"}, diff --git a/pkg/provider/kubernetes/ingress/builder_ingress_test.go b/pkg/provider/kubernetes/ingress/builder_ingress_test.go index f08f325ac..56ba2d286 100644 --- a/pkg/provider/kubernetes/ingress/builder_ingress_test.go +++ b/pkg/provider/kubernetes/ingress/builder_ingress_test.go @@ -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 } diff --git a/pkg/provider/kubernetes/ingress/client.go b/pkg/provider/kubernetes/ingress/client.go index a37fc1d4d..87351953a 100644 --- a/pkg/provider/kubernetes/ingress/client.go +++ b/pkg/provider/kubernetes/ingress/client.go @@ -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) +} diff --git a/pkg/provider/kubernetes/ingress/client_mock_test.go b/pkg/provider/kubernetes/ingress/client_mock_test.go index 94a282990..493bdf5ec 100644 --- a/pkg/provider/kubernetes/ingress/client_mock_test.go +++ b/pkg/provider/kubernetes/ingress/client_mock_test.go @@ -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 } diff --git a/pkg/provider/kubernetes/ingress/client_test.go b/pkg/provider/kubernetes/ingress/client_test.go index d65484219..9e32a618b 100644 --- a/pkg/provider/kubernetes/ingress/client_test.go +++ b/pkg/provider/kubernetes/ingress/client_test.go @@ -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): + } +} diff --git a/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_ingress.yml new file mode 100644 index 000000000..fb4eec709 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_ingress.yml @@ -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 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_ingressclass.yml b/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_ingressclass.yml new file mode 100644 index 000000000..c0dd6d23a --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_ingressclass.yml @@ -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 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_endpoint.yml new file mode 100644 index 000000000..0e64b4434 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_endpoint.yml @@ -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 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_ingress.yml new file mode 100644 index 000000000..58b7aac63 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_ingress.yml @@ -0,0 +1,12 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: defaultbackend + namespace: testing + +spec: + defaultBackend: + service: + name: defaultservice + port: + number: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_service.yml new file mode 100644 index 000000000..1bca9be2b --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_service.yml @@ -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 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_ingress.yml new file mode 100644 index 000000000..2ea3bf486 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_ingress.yml @@ -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 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_ingress.yml new file mode 100644 index 000000000..ca25ad48e --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_ingress.yml @@ -0,0 +1,16 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing +spec: + rules: + - http: + paths: + - path: /bar + pathType: Exact + backend: + service: + name: service1 + port: + number: 80 \ No newline at end of file diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_ingress.yml new file mode 100644 index 000000000..9804e7eba --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_ingress.yml @@ -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: ImplementationSpecific + backend: + service: + name: service1 + port: + number: 80 \ No newline at end of file diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_ingress.yml new file mode 100644 index 000000000..1e8f60949 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_ingress.yml @@ -0,0 +1,17 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing + annotations: + kubernetes.io/ingress.class: traefik +spec: + rules: + - http: + paths: + - path: /bar + backend: + service: + name: service1 + port: + number: 80 \ No newline at end of file diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_ingress.yml new file mode 100644 index 000000000..22c268983 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_ingress.yml @@ -0,0 +1,16 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing +spec: + ingressClassName: traefik-lb + rules: + - http: + paths: + - path: /bar + backend: + service: + name: service1 + port: + number: 80 \ No newline at end of file diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_ingressclass.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_ingressclass.yml new file mode 100644 index 000000000..b96f42518 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_ingressclass.yml @@ -0,0 +1,6 @@ +apiVersion: networking.k8s.io/v1beta1 +kind: IngressClass +metadata: + name: traefik-lb +spec: + controller: traefik.io/ingress-controller diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_ingress.yml new file mode 100644 index 000000000..b14b1a4bb --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_ingress.yml @@ -0,0 +1,16 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing +spec: + ingressClassName: traefik-lb + rules: + - http: + paths: + - path: /bar + backend: + service: + name: service1 + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_endpoint.yml new file mode 100644 index 000000000..bf2e7526e --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_endpoint.yml @@ -0,0 +1,12 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: + - addresses: + - ip: 10.10.0.1 + ports: + - name: foobar + port: 4711 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_ingress.yml new file mode 100644 index 000000000..12f17bc54 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_ingress.yml @@ -0,0 +1,16 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing +spec: + rules: + - http: + paths: + - path: /bar + pathType: Prefix + backend: + service: + name: service1 + port: + name: foobar diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_service.yml new file mode 100644 index 000000000..c064e5872 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_service.yml @@ -0,0 +1,12 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - name: foobar + port: 4711 + clusterIP: 10.0.0.1 + diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_ingress.yml new file mode 100644 index 000000000..6327a89a5 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_ingress.yml @@ -0,0 +1,17 @@ +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 + backend: + service: + name: service1 + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_ingress.yml new file mode 100644 index 000000000..bc88fc058 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_ingress.yml @@ -0,0 +1,16 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing +spec: + rules: + - http: + paths: + - path: /bar + pathType: Prefix + backend: + service: + name: service1 + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index d6b2ed225..31fe71aa0 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -23,9 +23,9 @@ import ( "github.com/traefik/traefik/v2/pkg/safe" "github.com/traefik/traefik/v2/pkg/tls" corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" networkingv1beta1 "k8s.io/api/networking/v1beta1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/intstr" ) const ( @@ -42,7 +42,7 @@ type Provider struct { CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"` Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"` LabelSelector string `description:"Kubernetes Ingress label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"` - IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"` + IngressClass string `description:"Value of kubernetes.io/ingress.class annotation or IngressClass name to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"` IngressEndpoint *EndpointIngress `description:"Kubernetes Ingress Endpoint." json:"ingressEndpoint,omitempty" toml:"ingressEndpoint,omitempty" yaml:"ingressEndpoint,omitempty" export:"true"` ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` lastConfiguration safe.Safe @@ -198,7 +198,11 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl log.FromContext(ctx).Warnf("Failed to list ingress classes: %v", err) } - ingressClasses = ics + if p.IngressClass != "" { + ingressClasses = filterIngressClassByName(p.IngressClass, ics) + } else { + ingressClasses = ics + } } ingresses := client.GetIngresses() @@ -222,17 +226,17 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl log.FromContext(ctx).Errorf("Error configuring TLS: %v", err) } - if len(ingress.Spec.Rules) == 0 && ingress.Spec.Backend != nil { + if len(ingress.Spec.Rules) == 0 && ingress.Spec.DefaultBackend != nil { if _, ok := conf.HTTP.Services["default-backend"]; ok { log.FromContext(ctx).Error("The default backend already exists.") continue } - service, err := loadService(client, ingress.Namespace, *ingress.Spec.Backend) + service, err := loadService(client, ingress.Namespace, *ingress.Spec.DefaultBackend) if err != nil { log.FromContext(ctx). - WithField("serviceName", ingress.Spec.Backend.ServiceName). - WithField("servicePort", ingress.Spec.Backend.ServicePort.String()). + WithField("serviceName", ingress.Spec.DefaultBackend.Service.Name). + WithField("servicePort", ingress.Spec.DefaultBackend.Service.Port.String()). Errorf("Cannot create service: %v", err) continue } @@ -268,13 +272,19 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl service, err := loadService(client, ingress.Namespace, pa.Backend) if err != nil { log.FromContext(ctx). - WithField("serviceName", pa.Backend.ServiceName). - WithField("servicePort", pa.Backend.ServicePort.String()). + WithField("serviceName", pa.Backend.Service.Name). + WithField("servicePort", pa.Backend.Service.Port.String()). Errorf("Cannot create service: %v", err) continue } - serviceName := provider.Normalize(ingress.Namespace + "-" + pa.Backend.ServiceName + "-" + pa.Backend.ServicePort.String()) + portString := pa.Backend.Service.Port.Name + + if len(pa.Backend.Service.Port.Name) == 0 { + portString = fmt.Sprint(pa.Backend.Service.Port.Number) + } + + serviceName := provider.Normalize(ingress.Namespace + "-" + pa.Backend.Service.Name + "-" + portString) conf.HTTP.Services[serviceName] = service routerKey := strings.TrimPrefix(provider.Normalize(ingress.Name+"-"+ingress.Namespace+"-"+rule.Host+pa.Path), "-") @@ -312,7 +322,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl return conf } -func (p *Provider) updateIngressStatus(ing *networkingv1beta1.Ingress, k8sClient Client) error { +func (p *Provider) updateIngressStatus(ing *networkingv1.Ingress, k8sClient Client) error { // Only process if an EndpointIngress has been configured. if p.IngressEndpoint == nil { return nil @@ -351,7 +361,7 @@ func (p *Provider) updateIngressStatus(ing *networkingv1beta1.Ingress, k8sClient return k8sClient.UpdateIngressStatus(ing, service.Status.LoadBalancer.Ingress) } -func (p *Provider) shouldProcessIngress(ingress *networkingv1beta1.Ingress, ingressClasses []*networkingv1beta1.IngressClass) bool { +func (p *Provider) shouldProcessIngress(ingress *networkingv1.Ingress, ingressClasses []*networkingv1beta1.IngressClass) bool { // configuration through the new kubernetes ingressClass if ingress.Spec.IngressClassName != nil { for _, ic := range ingressClasses { @@ -375,7 +385,7 @@ func buildHostRule(host string) string { return "Host(`" + host + "`)" } -func getCertificates(ctx context.Context, ingress *networkingv1beta1.Ingress, k8sClient Client, tlsConfigs map[string]*tls.CertAndStores) error { +func getCertificates(ctx context.Context, ingress *networkingv1.Ingress, k8sClient Client, tlsConfigs map[string]*tls.CertAndStores) error { for _, t := range ingress.Spec.TLS { if t.SecretName == "" { log.FromContext(ctx).Debugf("Skipping TLS sub-section: No secret name provided") @@ -460,8 +470,8 @@ func getTLSConfig(tlsConfigs map[string]*tls.CertAndStores) []*tls.CertAndStores return configs } -func loadService(client Client, namespace string, backend networkingv1beta1.IngressBackend) (*dynamic.Service, error) { - service, exists, err := client.GetService(namespace, backend.ServiceName) +func loadService(client Client, namespace string, backend networkingv1.IngressBackend) (*dynamic.Service, error) { + service, exists, err := client.GetService(namespace, backend.Service.Name) if err != nil { return nil, err } @@ -474,8 +484,7 @@ func loadService(client Client, namespace string, backend networkingv1beta1.Ingr var portSpec corev1.ServicePort var match bool for _, p := range service.Spec.Ports { - if (backend.ServicePort.Type == intstr.Int && backend.ServicePort.IntVal == p.Port) || - (backend.ServicePort.Type == intstr.String && backend.ServicePort.StrVal == p.Name) { + if backend.Service.Port.Number == p.Port || (backend.Service.Port.Name == p.Name && len(p.Name) > 0) { portName = p.Name portSpec = p match = true @@ -516,7 +525,7 @@ func loadService(client Client, namespace string, backend networkingv1beta1.Ingr return svc, nil } - endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, backend.ServiceName) + endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, backend.Service.Name) if endpointsErr != nil { return nil, endpointsErr } @@ -584,7 +593,7 @@ func makeRouterKeyWithHash(key, rule string) (string, error) { return dupKey, nil } -func loadRouter(rule networkingv1beta1.IngressRule, pa networkingv1beta1.HTTPIngressPath, rtConfig *RouterConfig, serviceName string) *dynamic.Router { +func loadRouter(rule networkingv1.IngressRule, pa networkingv1.HTTPIngressPath, rtConfig *RouterConfig, serviceName string) *dynamic.Router { var rules []string if len(rule.Host) > 0 { rules = []string{buildHostRule(rule.Host)} @@ -593,11 +602,11 @@ func loadRouter(rule networkingv1beta1.IngressRule, pa networkingv1beta1.HTTPIng if len(pa.Path) > 0 { matcher := defaultPathMatcher - if pa.PathType == nil || *pa.PathType == "" || *pa.PathType == networkingv1beta1.PathTypeImplementationSpecific { + if pa.PathType == nil || *pa.PathType == "" || *pa.PathType == networkingv1.PathTypeImplementationSpecific { if rtConfig != nil && rtConfig.Router != nil && rtConfig.Router.PathMatcher != "" { matcher = rtConfig.Router.PathMatcher } - } else if *pa.PathType == networkingv1beta1.PathTypeExact { + } else if *pa.PathType == networkingv1.PathTypeExact { matcher = "Path" } diff --git a/pkg/provider/kubernetes/ingress/kubernetes_test.go b/pkg/provider/kubernetes/ingress/kubernetes_test.go index fe8b15230..66d05c9f5 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes_test.go +++ b/pkg/provider/kubernetes/ingress/kubernetes_test.go @@ -14,7 +14,7 @@ import ( "github.com/traefik/traefik/v2/pkg/tls" "github.com/traefik/traefik/v2/pkg/types" corev1 "k8s.io/api/core/v1" - "k8s.io/api/networking/v1beta1" + networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -1300,6 +1300,300 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { }, }, }, + { + desc: "v18 Ingress with ingressClasses filter", + serverVersion: "v1.18", + ingressClass: "traefik-lb2", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-foo": { + Rule: "PathPrefix(`/foo`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "v19 Ingress with prefix pathType", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar": { + Rule: "PathPrefix(`/bar`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "v19 Ingress with no pathType", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar": { + Rule: "Path(`/bar`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "v19 Ingress with empty pathType", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar": { + Rule: "Path(`/bar`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "v19 Ingress with exact pathType", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar": { + Rule: "Path(`/bar`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "v19 Ingress with implementationSpecific pathType", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar": { + Rule: "Path(`/bar`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "v19 Ingress with ingress annotation", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar": { + Rule: "PathPrefix(`/bar`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "v19 Ingress with ingressClass", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar": { + Rule: "PathPrefix(`/bar`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "v19 Ingress with named port", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar": { + Rule: "PathPrefix(`/bar`)", + Service: "testing-service1-foobar", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-foobar": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:4711", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "v19 Ingress with missing ingressClass", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{}, + Services: map[string]*dynamic.Service{}, + }, + }, + }, + { + desc: "v19 Ingress with defaultbackend", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "default-router": { + Rule: "PathPrefix(`/`)", + Priority: math.MinInt32, + Service: "default-backend", + }, + }, + Services: map[string]*dynamic.Service{ + "default-backend": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, } for _, test := range testCases { @@ -1373,7 +1667,7 @@ func TestGetCertificates(t *testing.T) { testCases := []struct { desc string - ingress *v1beta1.Ingress + ingress *networkingv1.Ingress client Client result map[string]*tls.CertAndStores errResult string diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index 38693fcc3..bf62b47eb 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -125,6 +125,8 @@ type TCPEntryPoint struct { tracker *connectionTracker httpServer *httpServer httpsServer *httpServer + + http3Server *http3server } // NewTCPEntryPoint creates a new TCPEntryPoint. @@ -136,24 +138,29 @@ func NewTCPEntryPoint(ctx context.Context, configuration *static.EntryPoint) (*T return nil, fmt.Errorf("error preparing server: %w", err) } - router := &tcp.Router{} + rt := &tcp.Router{} httpServer, err := createHTTPServer(ctx, listener, configuration, true) if err != nil { return nil, fmt.Errorf("error preparing httpServer: %w", err) } - router.HTTPForwarder(httpServer.Forwarder) + rt.HTTPForwarder(httpServer.Forwarder) httpsServer, err := createHTTPServer(ctx, listener, configuration, false) if err != nil { return nil, fmt.Errorf("error preparing httpsServer: %w", err) } - router.HTTPSForwarder(httpsServer.Forwarder) + h3server, err := newHTTP3Server(ctx, configuration, httpsServer) + if err != nil { + return nil, err + } + + rt.HTTPSForwarder(httpsServer.Forwarder) tcpSwitcher := &tcp.HandlerSwitcher{} - tcpSwitcher.Switch(router) + tcpSwitcher.Switch(rt) return &TCPEntryPoint{ listener: listener, @@ -162,6 +169,7 @@ func NewTCPEntryPoint(ctx context.Context, configuration *static.EntryPoint) (*T tracker: tracker, httpServer: httpServer, httpsServer: httpsServer, + http3Server: h3server, }, nil } @@ -170,6 +178,10 @@ func (e *TCPEntryPoint) Start(ctx context.Context) { logger := log.FromContext(ctx) logger.Debugf("Start TCP Server") + if e.http3Server != nil { + go func() { _ = e.http3Server.Start() }() + } + for { conn, err := e.listener.Accept() if err != nil { @@ -230,7 +242,7 @@ func (e *TCPEntryPoint) Shutdown(ctx context.Context) { var wg sync.WaitGroup - shutdownServer := func(server stoppableServer) { + shutdownServer := func(server stoppable) { defer wg.Done() err := server.Shutdown(ctx) if err == nil { @@ -257,6 +269,11 @@ func (e *TCPEntryPoint) Shutdown(ctx context.Context) { if e.httpsServer.Server != nil { wg.Add(1) go shutdownServer(e.httpsServer.Server) + + if e.http3Server != nil { + wg.Add(1) + go shutdownServer(e.http3Server) + } } if e.tracker != nil { @@ -299,6 +316,10 @@ func (e *TCPEntryPoint) SwitchRouter(rt *tcp.Router) { e.httpsServer.Switcher.UpdateHandler(httpsHandler) e.switcher.Switch(rt) + + if e.http3Server != nil { + e.http3Server.Switch(rt) + } } // writeCloserWrapper wraps together a connection, and the concrete underlying @@ -463,9 +484,13 @@ func (c *connectionTracker) Close() { } } -type stoppableServer interface { +type stoppable interface { Shutdown(context.Context) error Close() error +} + +type stoppableServer interface { + stoppable Serve(listener net.Listener) error } @@ -504,7 +529,7 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati listener := newHTTPForwarder(ln) go func() { err := serverHTTP.Serve(listener) - if err != nil { + if err != nil && !errors.Is(err, http.ErrServerClosed) { log.FromContext(ctx).Errorf("Error while starting server: %v", err) } }() diff --git a/pkg/server/server_entrypoint_tcp_http3.go b/pkg/server/server_entrypoint_tcp_http3.go new file mode 100644 index 000000000..2c4a28fab --- /dev/null +++ b/pkg/server/server_entrypoint_tcp_http3.go @@ -0,0 +1,87 @@ +package server + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net" + "net/http" + "sync" + "time" + + "github.com/lucas-clemente/quic-go/http3" + "github.com/traefik/traefik/v2/pkg/config/static" + "github.com/traefik/traefik/v2/pkg/log" + "github.com/traefik/traefik/v2/pkg/tcp" +) + +type http3server struct { + *http3.Server + + http3conn net.PacketConn + + lock sync.RWMutex + getter func(info *tls.ClientHelloInfo) (*tls.Config, error) +} + +func newHTTP3Server(ctx context.Context, configuration *static.EntryPoint, httpsServer *httpServer) (*http3server, error) { + if !configuration.EnableHTTP3 { + return nil, nil + } + + conn, err := net.ListenPacket("udp", configuration.GetAddress()) + if err != nil { + return nil, fmt.Errorf("error while starting http3 listener: %w", err) + } + + h3 := &http3server{ + http3conn: conn, + getter: func(info *tls.ClientHelloInfo) (*tls.Config, error) { + return nil, errors.New("no tls config") + }, + } + + h3.Server = &http3.Server{ + Server: &http.Server{ + Addr: configuration.GetAddress(), + Handler: httpsServer.Server.(*http.Server).Handler, + ErrorLog: httpServerLogger, + ReadTimeout: time.Duration(configuration.Transport.RespondingTimeouts.ReadTimeout), + WriteTimeout: time.Duration(configuration.Transport.RespondingTimeouts.WriteTimeout), + IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout), + TLSConfig: &tls.Config{GetConfigForClient: h3.getGetConfigForClient}, + }, + } + + previousHandler := httpsServer.Server.(*http.Server).Handler + + httpsServer.Server.(*http.Server).Handler = http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + err := h3.Server.SetQuicHeaders(rw.Header()) + if err != nil { + log.FromContext(ctx).Errorf("failed to set HTTP3 headers: %v", err) + } + + previousHandler.ServeHTTP(rw, req) + }) + + return h3, nil +} + +func (e *http3server) Start() error { + return e.Serve(e.http3conn) +} + +func (e *http3server) Switch(rt *tcp.Router) { + e.lock.Lock() + defer e.lock.Unlock() + + e.getter = rt.GetTLSGetClientInfo() +} + +func (e *http3server) getGetConfigForClient(info *tls.ClientHelloInfo) (*tls.Config, error) { + e.lock.RLock() + defer e.lock.RUnlock() + + return e.getter(info) +} diff --git a/pkg/server/server_entrypoint_udp.go b/pkg/server/server_entrypoint_udp.go index 32a225096..163a719af 100644 --- a/pkg/server/server_entrypoint_udp.go +++ b/pkg/server/server_entrypoint_udp.go @@ -89,7 +89,8 @@ func NewUDPEntryPoint(cfg *static.EntryPoint) (*UDPEntryPoint, error) { if err != nil { return nil, err } - listener, err := udp.Listen("udp", addr) + + listener, err := udp.Listen("udp", addr, time.Duration(cfg.UDP.Timeout)) if err != nil { return nil, err } diff --git a/pkg/server/server_entrypoint_udp_test.go b/pkg/server/server_entrypoint_udp_test.go index 2aa5e70d8..f219cd98f 100644 --- a/pkg/server/server_entrypoint_udp_test.go +++ b/pkg/server/server_entrypoint_udp_test.go @@ -14,14 +14,17 @@ import ( ) func TestShutdownUDPConn(t *testing.T) { - entryPoint, err := NewUDPEntryPoint(&static.EntryPoint{ + ep := static.EntryPoint{ Address: ":0", Transport: &static.EntryPointsTransport{ LifeCycle: &static.LifeCycle{ GraceTimeOut: ptypes.Duration(5 * time.Second), }, }, - }) + } + ep.SetDefaults() + + entryPoint, err := NewUDPEntryPoint(&ep) require.NoError(t, err) go entryPoint.Start(context.Background()) diff --git a/pkg/server/service/roundtripper.go b/pkg/server/service/roundtripper.go index 96384c4bc..364cd9e69 100644 --- a/pkg/server/service/roundtripper.go +++ b/pkg/server/service/roundtripper.go @@ -100,7 +100,7 @@ func (r *RoundTripperManager) Get(name string) (http.RoundTripper, error) { // createRoundTripper creates an http.RoundTripper configured with the Transport configuration settings. // For the settings that can't be configured in Traefik it uses the default http.Transport settings. -// An exception to this is the MaxIdleConns setting as we only provide the option MaxIdleConnsPerHostin Traefik at this point in time. +// An exception to this is the MaxIdleConns setting as we only provide the option MaxIdleConnsPerHost in Traefik at this point in time. // Setting this value to the default of 100 could lead to confusing behavior and backwards compatibility issues. func createRoundTripper(cfg *dynamic.ServersTransport) (http.RoundTripper, error) { if cfg == nil { @@ -127,15 +127,6 @@ func createRoundTripper(cfg *dynamic.ServersTransport) (http.RoundTripper, error WriteBufferSize: 64 * 1024, } - transport.RegisterProtocol("h2c", &h2cTransportWrapper{ - Transport: &http2.Transport{ - DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) { - return net.Dial(netw, addr) - }, - AllowHTTP: true, - }, - }) - if cfg.ForwardingTimeouts != nil { transport.ResponseHeaderTimeout = time.Duration(cfg.ForwardingTimeouts.ResponseHeaderTimeout) transport.IdleConnTimeout = time.Duration(cfg.ForwardingTimeouts.IdleConnTimeout) @@ -150,6 +141,20 @@ func createRoundTripper(cfg *dynamic.ServersTransport) (http.RoundTripper, error } } + // Return directly HTTP/1.1 transport when HTTP/2 is disabled + if cfg.DisableHTTP2 { + return transport, nil + } + + transport.RegisterProtocol("h2c", &h2cTransportWrapper{ + Transport: &http2.Transport{ + DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) { + return net.Dial(netw, addr) + }, + AllowHTTP: true, + }, + }) + return newSmartRoundTripper(transport) } diff --git a/pkg/server/service/roundtripper_test.go b/pkg/server/service/roundtripper_test.go index 7049bbf83..b58a26e33 100644 --- a/pkg/server/service/roundtripper_test.go +++ b/pkg/server/service/roundtripper_test.go @@ -227,3 +227,69 @@ func TestMTLS(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) } + +func TestDisableHTTP2(t *testing.T) { + testCases := []struct { + desc string + disableHTTP2 bool + serverHTTP2 bool + expectedProto string + }{ + { + desc: "HTTP1 capable client with HTTP1 server", + disableHTTP2: true, + expectedProto: "HTTP/1.1", + }, + { + desc: "HTTP1 capable client with HTTP2 server", + disableHTTP2: true, + serverHTTP2: true, + expectedProto: "HTTP/1.1", + }, + { + desc: "HTTP2 capable client with HTTP1 server", + expectedProto: "HTTP/1.1", + }, + { + desc: "HTTP2 capable client with HTTP2 server", + serverHTTP2: true, + expectedProto: "HTTP/2.0", + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + srv := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + })) + + srv.EnableHTTP2 = test.serverHTTP2 + srv.StartTLS() + + rtManager := NewRoundTripperManager() + + dynamicConf := map[string]*dynamic.ServersTransport{ + "test": { + DisableHTTP2: test.disableHTTP2, + InsecureSkipVerify: true, + }, + } + + rtManager.Update(dynamicConf) + + tr, err := rtManager.Get("test") + require.NoError(t, err) + + client := http.Client{Transport: tr} + + resp, err := client.Get(srv.URL) + require.NoError(t, err) + + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.Equal(t, test.expectedProto, resp.Proto) + }) + } +} diff --git a/pkg/tcp/proxy.go b/pkg/tcp/proxy.go index 552fe0f3b..06dc2884a 100644 --- a/pkg/tcp/proxy.go +++ b/pkg/tcp/proxy.go @@ -31,7 +31,7 @@ func NewProxy(address string, terminationDelay time.Duration, proxyProtocol *dyn return nil, fmt.Errorf("unknown proxyProtocol version: %d", proxyProtocol.Version) } - // enable the refresh of the target only if the address in an IP + // enable the refresh of the target only if the address in not an IP refreshTarget := false if host, _, err := net.SplitHostPort(address); err == nil && net.ParseIP(host) == nil { refreshTarget = true @@ -48,23 +48,14 @@ func NewProxy(address string, terminationDelay time.Duration, proxyProtocol *dyn // ServeTCP forwards the connection to a service. func (p *Proxy) ServeTCP(conn WriteCloser) { - log.Debugf("Handling connection from %s", conn.RemoteAddr()) + log.WithoutContext().Debugf("Handling connection from %s", conn.RemoteAddr()) // needed because of e.g. server.trackedConnection defer conn.Close() - if p.refreshTarget { - tcpAddr, err := net.ResolveTCPAddr("tcp", p.address) - if err != nil { - log.Errorf("Error resolving tcp address: %v", err) - return - } - p.target = tcpAddr - } - - connBackend, err := net.DialTCP("tcp", nil, p.target) + connBackend, err := p.dialBackend() if err != nil { - log.Errorf("Error while connection to backend: %v", err) + log.WithoutContext().Errorf("Error while connecting to backend: %v", err) return } @@ -91,6 +82,19 @@ func (p *Proxy) ServeTCP(conn WriteCloser) { <-errChan } +func (p Proxy) dialBackend() (*net.TCPConn, error) { + if !p.refreshTarget { + return net.DialTCP("tcp", nil, p.target) + } + + conn, err := net.Dial("tcp", p.address) + if err != nil { + return nil, err + } + + return conn.(*net.TCPConn), nil +} + func (p Proxy) connCopy(dst, src WriteCloser, errCh chan error) { _, err := io.Copy(dst, src) errCh <- err diff --git a/pkg/tcp/proxy_test.go b/pkg/tcp/proxy_test.go index 11fdb47c4..e70b36796 100644 --- a/pkg/tcp/proxy_test.go +++ b/pkg/tcp/proxy_test.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "net" - "sync" "testing" "time" @@ -175,19 +174,18 @@ func TestProxyProtocol(t *testing.T) { func TestLookupAddress(t *testing.T) { testCases := []struct { - desc string - address string - expectSame assert.ComparisonAssertionFunc + desc string + address string + expectRefresh bool }{ { - desc: "IP doesn't need refresh", - address: "8.8.4.4:53", - expectSame: assert.Same, + desc: "IP doesn't need refresh", + address: "8.8.4.4:53", }, { - desc: "Hostname needs refresh", - address: "dns.google:53", - expectSame: assert.NotSame, + desc: "Hostname needs refresh", + address: "dns.google:53", + expectRefresh: true, }, } @@ -201,44 +199,13 @@ func TestLookupAddress(t *testing.T) { require.NotNil(t, proxy.target) - proxyListener, err := net.Listen("tcp", ":0") + conn, err := proxy.dialBackend() require.NoError(t, err) - var wg sync.WaitGroup - go func(wg *sync.WaitGroup) { - for { - conn, err := proxyListener.Accept() - require.NoError(t, err) - - proxy.ServeTCP(conn.(*net.TCPConn)) - - wg.Done() - } - }(&wg) - - var lastTarget *net.TCPAddr - - for i := 0; i < 3; i++ { - wg.Add(1) - - conn, err := net.Dial("tcp", proxyListener.Addr().String()) - require.NoError(t, err) - - _, err = conn.Write([]byte("ping\n")) - require.NoError(t, err) - - err = conn.Close() - require.NoError(t, err) - - wg.Wait() - - assert.NotNil(t, proxy.target) - - if lastTarget != nil { - test.expectSame(t, lastTarget, proxy.target) - } - - lastTarget = proxy.target + if test.expectRefresh { + assert.NotEqual(t, test.address, conn.RemoteAddr().String()) + } else { + assert.Equal(t, test.address, conn.RemoteAddr().String()) } }) } diff --git a/pkg/tcp/router.go b/pkg/tcp/router.go index ea0f406e7..9bbfa29b9 100644 --- a/pkg/tcp/router.go +++ b/pkg/tcp/router.go @@ -27,6 +27,16 @@ type Router struct { hostHTTPTLSConfig map[string]*tls.Config // TLS configs keyed by SNI } +// GetTLSGetClientInfo is called after a ClientHello is received from a client. +func (r *Router) GetTLSGetClientInfo() func(info *tls.ClientHelloInfo) (*tls.Config, error) { + return func(info *tls.ClientHelloInfo) (*tls.Config, error) { + if tlsConfig, ok := r.hostHTTPTLSConfig[info.ServerName]; ok { + return tlsConfig, nil + } + return r.httpsTLSConfig, nil + } +} + // ServeTCP forwards the connection to the right TCP/HTTP handler. func (r *Router) ServeTCP(conn WriteCloser) { // FIXME -- Check if ProxyProtocol changes the first bytes of the request diff --git a/pkg/tls/certificate_store.go b/pkg/tls/certificate_store.go index 2db8afd1c..91bd00ef5 100644 --- a/pkg/tls/certificate_store.go +++ b/pkg/tls/certificate_store.go @@ -56,15 +56,16 @@ func (c CertificateStore) getDefaultCertificateDomains() []string { // GetAllDomains return a slice with all the certificate domain. func (c CertificateStore) GetAllDomains() []string { - allCerts := c.getDefaultCertificateDomains() + allDomains := c.getDefaultCertificateDomains() // Get dynamic certificates if c.DynamicCerts != nil && c.DynamicCerts.Get() != nil { - for domains := range c.DynamicCerts.Get().(map[string]*tls.Certificate) { - allCerts = append(allCerts, domains) + for domain := range c.DynamicCerts.Get().(map[string]*tls.Certificate) { + allDomains = append(allDomains, domain) } } - return allCerts + + return allDomains } // GetBestCertificate returns the best match certificate, and caches the response. diff --git a/pkg/tls/cipher.go b/pkg/tls/cipher.go index 07cbfd7d6..8925cd534 100644 --- a/pkg/tls/cipher.go +++ b/pkg/tls/cipher.go @@ -69,3 +69,13 @@ var ( tls.TLS_FALLBACK_SCSV: `TLS_FALLBACK_SCSV`, } ) + +// GetCipherName returns the Cipher suite name. +// Available CipherSuites defined at https://golang.org/pkg/crypto/tls/#pkg-constants +func GetCipherName(connState *tls.ConnectionState) string { + if cipher, ok := CipherSuitesReversed[connState.CipherSuite]; ok { + return cipher + } + + return "unknown" +} diff --git a/pkg/tls/tlsmanager.go b/pkg/tls/tlsmanager.go index 1beebfc64..88fae8357 100644 --- a/pkg/tls/tlsmanager.go +++ b/pkg/tls/tlsmanager.go @@ -131,6 +131,27 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) { return tlsConfig, err } +// GetCertificates returns all stored certificates. +func (m *Manager) GetCertificates() []*x509.Certificate { + var certificates []*x509.Certificate + + // We iterate over all the certificates. + for _, store := range m.stores { + if store.DynamicCerts != nil && store.DynamicCerts.Get() != nil { + for _, cert := range store.DynamicCerts.Get().(map[string]*tls.Certificate) { + x509Cert, err := x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + continue + } + + certificates = append(certificates, x509Cert) + } + } + } + + return certificates +} + func (m *Manager) getStore(storeName string) *CertificateStore { _, ok := m.stores[storeName] if !ok { diff --git a/pkg/tls/version.go b/pkg/tls/version.go new file mode 100644 index 000000000..8e6b5cfe0 --- /dev/null +++ b/pkg/tls/version.go @@ -0,0 +1,20 @@ +package tls + +import "crypto/tls" + +// GetVersion returns the normalized TLS version. +// Available TLS versions defined at https://golang.org/pkg/crypto/tls/#pkg-constants +func GetVersion(connState *tls.ConnectionState) string { + switch connState.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" + } + + return "unknown" +} diff --git a/pkg/tracing/datadog/datadog.go b/pkg/tracing/datadog/datadog.go index a64f81b4a..12c86c018 100644 --- a/pkg/tracing/datadog/datadog.go +++ b/pkg/tracing/datadog/datadog.go @@ -2,6 +2,8 @@ package datadog import ( "io" + "net" + "os" "strings" "github.com/opentracing/opentracing-go" @@ -27,10 +29,17 @@ type Config struct { // SetDefaults sets the default values. func (c *Config) SetDefaults() { - c.LocalAgentHostPort = "localhost:8126" - c.GlobalTag = "" - c.Debug = false - c.PrioritySampling = false + host, ok := os.LookupEnv("DD_AGENT_HOST") + if !ok { + host = "localhost" + } + + port, ok := os.LookupEnv("DD_TRACE_AGENT_PORT") + if !ok { + port = "8126" + } + + c.LocalAgentHostPort = net.JoinHostPort(host, port) } // Setup sets up the tracer. diff --git a/pkg/types/metrics.go b/pkg/types/metrics.go index f3391fc21..7fd9bc49f 100644 --- a/pkg/types/metrics.go +++ b/pkg/types/metrics.go @@ -1,6 +1,8 @@ package types import ( + "net" + "os" "time" "github.com/traefik/paerser/types" @@ -41,7 +43,16 @@ type Datadog struct { // SetDefaults sets the default values. func (d *Datadog) SetDefaults() { - d.Address = "localhost:8125" + host, ok := os.LookupEnv("DD_AGENT_HOST") + if !ok { + host = "localhost" + } + + port, ok := os.LookupEnv("DD_DOGSTATSD_PORT") + if !ok { + port = "8125" + } + d.Address = net.JoinHostPort(host, port) d.PushInterval = types.Duration(10 * time.Second) d.AddEntryPointsLabels = true d.AddServicesLabels = true diff --git a/pkg/udp/conn.go b/pkg/udp/conn.go index 9aef31f2a..d8b38f0e9 100644 --- a/pkg/udp/conn.go +++ b/pkg/udp/conn.go @@ -12,12 +12,6 @@ const receiveMTU = 8192 const closeRetryInterval = 500 * time.Millisecond -// connTimeout determines how long to wait on an idle session, -// before releasing all resources related to that session. -const connTimeout = 3 * time.Second - -var timeoutTicker = connTimeout / 10 - var errClosedListener = errors.New("udp: listener closed") // Listener augments a session-oriented Listener over a UDP PacketConn. @@ -31,10 +25,18 @@ type Listener struct { accepting bool acceptCh chan *Conn // no need for a Once, already indirectly guarded by accepting. + + // timeout defines how long to wait on an idle session, + // before releasing its related resources. + timeout time.Duration } // Listen creates a new listener. -func Listen(network string, laddr *net.UDPAddr) (*Listener, error) { +func Listen(network string, laddr *net.UDPAddr, timeout time.Duration) (*Listener, error) { + if timeout <= 0 { + return nil, errors.New("timeout should be greater than zero") + } + conn, err := net.ListenUDP(network, laddr) if err != nil { return nil, err @@ -45,6 +47,7 @@ func Listen(network string, laddr *net.UDPAddr) (*Listener, error) { acceptCh: make(chan *Conn), conns: make(map[string]*Conn), accepting: true, + timeout: timeout, } go l.readLoop() @@ -179,7 +182,7 @@ func (l *Listener) newConn(rAddr net.Addr) *Conn { readCh: make(chan []byte), sizeCh: make(chan int), doneCh: make(chan struct{}), - timeout: timeoutTicker, + timeout: l.timeout, } } @@ -206,7 +209,7 @@ type Conn struct { // that is to say it waits on readCh to receive the slice of bytes that the Read operation wants to read onto. // The Read operation receives the signal that the data has been written to the slice of bytes through the sizeCh. func (c *Conn) readLoop() { - ticker := time.NewTicker(c.timeout) + ticker := time.NewTicker(c.timeout / 10) defer ticker.Stop() for { @@ -216,7 +219,7 @@ func (c *Conn) readLoop() { c.msgs = append(c.msgs, msg) case <-ticker.C: c.muActivity.RLock() - deadline := c.lastActivity.Add(connTimeout) + deadline := c.lastActivity.Add(c.timeout) c.muActivity.RUnlock() if time.Now().After(deadline) { c.Close() @@ -236,7 +239,7 @@ func (c *Conn) readLoop() { c.msgs = append(c.msgs, msg) case <-ticker.C: c.muActivity.RLock() - deadline := c.lastActivity.Add(connTimeout) + deadline := c.lastActivity.Add(c.timeout) c.muActivity.RUnlock() if time.Now().After(deadline) { c.Close() diff --git a/pkg/udp/conn_test.go b/pkg/udp/conn_test.go index 3f0116e07..dce924bc9 100644 --- a/pkg/udp/conn_test.go +++ b/pkg/udp/conn_test.go @@ -15,7 +15,7 @@ func TestConsecutiveWrites(t *testing.T) { addr, err := net.ResolveUDPAddr("udp", ":0") require.NoError(t, err) - ln, err := Listen("udp", addr) + ln, err := Listen("udp", addr, 3*time.Second) require.NoError(t, err) defer func() { err := ln.Close() @@ -77,7 +77,7 @@ func TestListenNotBlocking(t *testing.T) { require.NoError(t, err) - ln, err := Listen("udp", addr) + ln, err := Listen("udp", addr, 3*time.Second) require.NoError(t, err) defer func() { err := ln.Close() @@ -162,6 +162,14 @@ func TestListenNotBlocking(t *testing.T) { } } +func TestListenWithZeroTimeout(t *testing.T) { + addr, err := net.ResolveUDPAddr("udp", ":0") + require.NoError(t, err) + + _, err = Listen("udp", addr, 0) + assert.Error(t, err) +} + func TestTimeoutWithRead(t *testing.T) { testTimeout(t, true) } @@ -176,7 +184,7 @@ func testTimeout(t *testing.T, withRead bool) { addr, err := net.ResolveUDPAddr("udp", ":0") require.NoError(t, err) - ln, err := Listen("udp", addr) + ln, err := Listen("udp", addr, 3*time.Second) require.NoError(t, err) defer func() { err := ln.Close() @@ -212,7 +220,7 @@ func testTimeout(t *testing.T, withRead bool) { assert.Equal(t, 10, len(ln.conns)) - time.Sleep(4 * time.Second) + time.Sleep(ln.timeout + time.Second) assert.Equal(t, 0, len(ln.conns)) } @@ -220,7 +228,7 @@ func TestShutdown(t *testing.T) { addr, err := net.ResolveUDPAddr("udp", ":0") require.NoError(t, err) - l, err := Listen("udp", addr) + l, err := Listen("udp", addr, 3*time.Second) require.NoError(t, err) go func() { diff --git a/pkg/udp/proxy_test.go b/pkg/udp/proxy_test.go index e2995846d..120cbc457 100644 --- a/pkg/udp/proxy_test.go +++ b/pkg/udp/proxy_test.go @@ -46,7 +46,7 @@ func newServer(t *testing.T, addr string, handler Handler) { addrL, err := net.ResolveUDPAddr("udp", addr) require.NoError(t, err) - listener, err := Listen("udp", addrL) + listener, err := Listen("udp", addrL, 3*time.Second) require.NoError(t, err) for { diff --git a/script/update-generated-crd-code.sh b/script/update-generated-crd-code.sh index 78b70420c..a58af5fe7 100755 --- a/script/update-generated-crd-code.sh +++ b/script/update-generated-crd-code.sh @@ -9,6 +9,7 @@ rm -rf "${REPO_ROOT}"/vendor go mod vendor chmod +x "${REPO_ROOT}"/vendor/k8s.io/code-generator/*.sh +# Generate the crd client "${REPO_ROOT}"/vendor/k8s.io/code-generator/generate-groups.sh \ all \ github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/provider/kubernetes/crd/generated \ @@ -18,12 +19,21 @@ chmod +x "${REPO_ROOT}"/vendor/k8s.io/code-generator/*.sh "$@" deepcopy-gen \ ---input-dirs github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/config/dynamic \ ---input-dirs github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/tls \ ---input-dirs github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/types \ ---output-package github.com/traefik/traefik \ --O zz_generated.deepcopy --go-header-file "${HACK_DIR}"/boilerplate.go.tmpl + --input-dirs github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/config/dynamic \ + --input-dirs github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/tls \ + --input-dirs github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/types \ + --output-package github.com/traefik/traefik \ + -O zz_generated.deepcopy --go-header-file "${HACK_DIR}"/boilerplate.go.tmpl cp -r "${REPO_ROOT}"/"${TRAEFIK_MODULE_VERSION:?}"/* "${REPO_ROOT}"; rm -rf "${REPO_ROOT}"/"${TRAEFIK_MODULE_VERSION:?}" +# Generate the CRD definitions for the documentation +go run "${REPO_ROOT}"/vendor/sigs.k8s.io/controller-tools/cmd/controller-gen \ + crd:crdVersions=v1 \ + paths="${REPO_ROOT}"/pkg/provider/kubernetes/crd/traefik/v1alpha1/... \ + output:dir="${REPO_ROOT}"/docs/content/reference/dynamic-configuration/ + +# Concatenate the CRD definitions for the integration tests +cat "${REPO_ROOT}"/docs/content/reference/dynamic-configuration/traefik.containo.us_*.yaml > "${REPO_ROOT}"/integration/fixtures/k8s/01-traefik-crd.yml + rm -rf "${REPO_ROOT}"/vendor diff --git a/tools.go b/tools.go index 1185af9e3..cb6b3341b 100644 --- a/tools.go +++ b/tools.go @@ -4,4 +4,5 @@ package main import ( _ "k8s.io/code-generator" + _ "sigs.k8s.io/controller-tools/cmd/controller-gen" )