diff --git a/docs/content/providers/kubernetes-gateway.md b/docs/content/providers/kubernetes-gateway.md new file mode 100644 index 000000000..f5247ec83 --- /dev/null +++ b/docs/content/providers/kubernetes-gateway.md @@ -0,0 +1,252 @@ +# Traefik & Kubernetes with Gateway API + +The Kubernetes Gateway API, The Experimental Way. +{: .subtitle } + +Gateway API is the evolution of Kubernetes APIs that relate to `Services`, e.g. `Ingress`. +The Gateway API project is part of Kubernetes, working under SIG-NETWORK. + +The Kubernetes Gateway provider is a Traefik implementation of the [service apis](https://github.com/kubernetes-sigs/service-apis) +specifications from the Kubernetes SIGs. + +This provider is proposed as an experimental feature and partially supports the service apis [v0.1.0](https://github.com/kubernetes-sigs/service-apis/releases/tag/v0.1.0) specification. + +!!! warning "Enabling The Experimental Kubernetes Gateway Provider" + + As this provider is in experimental stage, it needs to be activated in the experimental section of the static configuration. + + ```toml tab="File (TOML)" + [experimental] + kubernetesGateway = true + + [providers.kubernetesGateway] + #... + ``` + + ```yaml tab="File (YAML)" + experimental: + kubernetesGateway: true + + providers: + kubernetesGateway: {} + #... + ``` + + ```bash tab="CLI" + --experimental.kubernetesgateway=true --providers.kubernetesgateway=true #... + ``` + +## Configuration Requirements + +!!! tip "All Steps for a Successful Deployment" + + * Add/update the Kubernetes Gateway API [definitions](../reference/dynamic-configuration/kubernetes-gateway.md#definitions). + * Add/update the [RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) for the Traefik custom resources. + * Add all needed Kubernetes Gateway API [resources](../reference/dynamic-configuration/kubernetes-gateway.md#resources). + +## Examples + +??? example "Kubernetes Gateway Provider Basic Example" + + ```yaml tab="Gateway API" + --8<-- "content/reference/dynamic-configuration/kubernetes-gateway-simple-https.yml" + ``` + + ```yaml tab="Whoami Service" + --8<-- "content/reference/dynamic-configuration/kubernetes-whoami-svc.yml" + ``` + + ```yaml tab="Traefik Service" + --8<-- "content/reference/dynamic-configuration/kubernetes-gateway-traefik-lb-svc.yml" + ``` + + ```yaml tab="Gateway API CRDs" + # All resources definition must be declared + --8<-- "content/reference/dynamic-configuration/networking.x-k8s.io_gatewayclasses.yaml" + --8<-- "content/reference/dynamic-configuration/networking.x-k8s.io_gateways.yaml" + --8<-- "content/reference/dynamic-configuration/networking.x-k8s.io_httproutes.yaml" + ``` + + ```yaml tab="RBAC" + --8<-- "content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml" + ``` + +The Kubernetes Service APIs provides several [guides](https://kubernetes-sigs.github.io/service-apis/guides/) of how to use their API. +Those guides will help you to go further than the example above. +The [getting started](https://kubernetes-sigs.github.io/service-apis/getting-started/) show you how to install the CRDs from their repository. +Thus, keep in mind that the Traefik Gateway provider only supports the `v0.1.0`. + +For now, the Traefik Gateway Provider could be used to achieve the following set-up guides: + +* [Simple Gateway](https://kubernetes-sigs.github.io/service-apis/simple-gateway/) +* [HTTP routing](https://kubernetes-sigs.github.io/service-apis/http-routing/) +* [TLS](https://kubernetes-sigs.github.io/service-apis/tls/) (Partial support: only on listeners with terminate mode) + +## Resource Configuration + +When using Kubernetes Gateway API as a provider, +Traefik uses Kubernetes +[Custom Resource Definition](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) +to retrieve its routing configuration. + +All concepts can be found in the official API concepts [documentation](https://kubernetes-sigs.github.io/service-apis/api-overview/). +Traefik implements the following resources: + +* `GatewayClass` defines a set of Gateways that share a common configuration and behaviour. +* `Gateway` describes how traffic can be translated to Services within the cluster. +* `HTTPRoute` define HTTP rules for mapping requests from a Gateway to Kubernetes Services. + +## Provider Configuration + +### `endpoint` + +_Optional, Default=empty_ + +```toml tab="File (TOML)" +[providers.kubernetesGateway] + endpoint = "http://localhost:8080" + # ... +``` + +```yaml tab="File (YAML)" +providers: + kubernetesGateway: + endpoint: "http://localhost:8080" + # ... +``` + +```bash tab="CLI" +--providers.kubernetesgateway.endpoint=http://localhost:8080 +``` + +The Kubernetes server endpoint as URL. + +When deployed into Kubernetes, Traefik will read the environment variables `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` or `KUBECONFIG` to construct the endpoint. + +The access token will be looked up in `/var/run/secrets/kubernetes.io/serviceaccount/token` and the SSL CA certificate in `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt`. +Both are mounted automatically when deployed inside Kubernetes. + +The endpoint may be specified to override the environment variable values inside a cluster. + +When the environment variables are not found, Traefik will try to connect to the Kubernetes API server with an external-cluster client. +In this case, the endpoint is required. +Specifically, it may be set to the URL used by `kubectl proxy` to connect to a Kubernetes cluster using the granted authentication and authorization of the associated kubeconfig. + +### `token` + +_Optional, Default=empty_ + +```toml tab="File (TOML)" +[providers.kubernetesGateway] + token = "mytoken" + # ... +``` + +```yaml tab="File (YAML)" +providers: + kubernetesGateway: + token = "mytoken" + # ... +``` + +```bash tab="CLI" +--providers.kubernetesgateway.token=mytoken +``` + +Bearer token used for the Kubernetes client configuration. + +### `certAuthFilePath` + +_Optional, Default=empty_ + +```toml tab="File (TOML)" +[providers.kubernetesGateway] + certAuthFilePath = "/my/ca.crt" + # ... +``` + +```yaml tab="File (YAML)" +providers: + kubernetesGateway: + certAuthFilePath: "/my/ca.crt" + # ... +``` + +```bash tab="CLI" +--providers.kubernetesgateway.certauthfilepath=/my/ca.crt +``` + +Path to the certificate authority file. +Used for the Kubernetes client configuration. + +### `namespaces` + +_Optional, Default: all namespaces (empty array)_ + +```toml tab="File (TOML)" +[providers.kubernetesGateway] + namespaces = ["default", "production"] + # ... +``` + +```yaml tab="File (YAML)" +providers: + kubernetesGateway: + namespaces: + - "default" + - "production" + # ... +``` + +```bash tab="CLI" +--providers.kubernetesgateway.namespaces=default,production +``` + +Array of namespaces to watch. + +### `labelselector` + +_Optional, Default: empty (process all resources)_ + +```toml tab="File (TOML)" +[providers.kubernetesGateway] + labelselector = "app=traefik" + # ... +``` + +```yaml tab="File (YAML)" +providers: + kubernetesGateway: + labelselector: "app=traefik" + # ... +``` + +```bash tab="CLI" +--providers.kubernetesgateway.labelselector="app=traefik" +``` + +By default, Traefik processes all resource objects in the configured namespaces. +A label selector can be defined to filter on specific GatewayClass objects only. + +See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. + +### `throttleDuration` + +_Optional, Default: 0 (no throttling)_ + +```toml tab="File (TOML)" +[providers.kubernetesGateway] + throttleDuration = "10s" + # ... +``` + +```yaml tab="File (YAML)" +providers: + kubernetesGateway: + throttleDuration: "10s" + # ... +``` + +```bash tab="CLI" +--providers.kubernetesgateway.throttleDuration=10s +``` diff --git a/docs/content/providers/kubernetes-ingress.md b/docs/content/providers/kubernetes-ingress.md index 3e165389e..0e6ed03a8 100644 --- a/docs/content/providers/kubernetes-ingress.md +++ b/docs/content/providers/kubernetes-ingress.md @@ -158,29 +158,6 @@ providers: Path to the certificate authority file. Used for the Kubernetes client configuration. -### `disablePassHostHeaders` - -_Optional, Default=false_ - -```toml tab="File (TOML)" -[providers.kubernetesIngress] - disablePassHostHeaders = true - # ... -``` - -```yaml tab="File (YAML)" -providers: - kubernetesIngress: - disablePassHostHeaders: true - # ... -``` - -```bash tab="CLI" ---providers.kubernetesingress.disablepasshostheaders=true -``` - -Whether to disable PassHost Headers. - ### `namespaces` _Optional, Default: all namespaces (empty array)_ diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml b/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml new file mode 100644 index 000000000..c2a46c0e0 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml @@ -0,0 +1,49 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: gateway-role +rules: + - apiGroups: + - "" + resources: + - services + - endpoints + - secrets + verbs: + - get + - list + - watch + - apiGroups: + - networking.x-k8s.io + resources: + - gatewayclasses + - gateways + - httproutes + verbs: + - get + - list + - watch + - apiGroups: + - networking.x-k8s.io + resources: + - gatewayclasses/status + - gateways/status + - httproutes/status + verbs: + - update + +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: gateway-controller + +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: gateway-role +subjects: + - kind: ServiceAccount + name: traefik-controller + namespace: default diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml b/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml new file mode 100644 index 000000000..821a8fccc --- /dev/null +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml @@ -0,0 +1,46 @@ +--- +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: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-simple-http.yml b/docs/content/reference/dynamic-configuration/kubernetes-gateway-simple-http.yml new file mode 100644 index 000000000..5721c96de --- /dev/null +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway-simple-http.yml @@ -0,0 +1,47 @@ +--- +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: + - protocol: HTTP + port: 80 + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + matchLabels: + app: foo + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-1 + namespace: default + labels: + app: foo +spec: + hostnames: + - "whoami" + rules: + - matches: + - path: + type: Exact + value: /bar + forwardTo: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-simple-https.yml b/docs/content/reference/dynamic-configuration/kubernetes-gateway-simple-https.yml new file mode 100644 index 000000000..24e4efff8 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway-simple-https.yml @@ -0,0 +1,48 @@ +--- +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 +spec: + gatewayClassName: my-gateway-class + listeners: + - protocol: HTTPS + port: 443 + tls: + certificateRef: + group: "core" + kind: "Secret" + name: "mysecret" + routes: + kind: HTTPRoute + selector: + matchLabels: + app: foo +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-1 + namespace: default + labels: + app: foo +spec: + hostnames: + - "whoami" + rules: + - matches: + - path: + type: Exact + value: /foo + forwardTo: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-traefik-lb-svc.yml b/docs/content/reference/dynamic-configuration/kubernetes-gateway-traefik-lb-svc.yml new file mode 100644 index 000000000..1e90422a4 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway-traefik-lb-svc.yml @@ -0,0 +1,56 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: traefik-controller + +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: traefik + +spec: + replicas: 1 + selector: + matchLabels: + app: traefik-lb + template: + metadata: + labels: + app: traefik-lb + spec: + serviceAccountName: traefik-controller + containers: + - name: traefik + image: traefik/traefik:latest + imagePullPolicy: IfNotPresent + args: + - --entrypoints.web.address=:80 + - --entrypoints.websecure.address=:443 + - --experimental.kubernetesgateway + - --providers.kubernetesgateway + ports: + - name: web + containerPort: 80 + - name: websecure + containerPort: 443 + +--- +apiVersion: v1 +kind: Service +metadata: + name: traefik +spec: + selector: + app: traefik-lb + ports: + - protocol: TCP + port: 80 + targetPort: web + name: web + - protocol: TCP + port: 443 + targetPort: websecure + name: websecure + type: LoadBalancer diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway.md b/docs/content/reference/dynamic-configuration/kubernetes-gateway.md new file mode 100644 index 000000000..2c9de2e90 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway.md @@ -0,0 +1,24 @@ +# Kubernetes Configuration Reference + +Dynamic configuration with Kubernetes Gateway provider. +{: .subtitle } + +## Definitions + +```yaml +--8<-- "content/reference/dynamic-configuration/networking.x-k8s.io_gatewayclasses.yaml" +--8<-- "content/reference/dynamic-configuration/networking.x-k8s.io_gateways.yaml" +--8<-- "content/reference/dynamic-configuration/networking.x-k8s.io_httproutes.yaml" +``` + +## Resources + +```yaml +--8<-- "content/reference/dynamic-configuration/kubernetes-gateway-resource.yml" +``` + +## RBAC + +```yaml +--8<-- "content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml" +``` diff --git a/docs/content/reference/dynamic-configuration/kubernetes-whoami-svc.yml b/docs/content/reference/dynamic-configuration/kubernetes-whoami-svc.yml new file mode 100644 index 000000000..486e5d195 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/kubernetes-whoami-svc.yml @@ -0,0 +1,32 @@ +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: whoami + +spec: + replicas: 2 + selector: + matchLabels: + app: whoami + template: + metadata: + labels: + app: whoami + spec: + containers: + - name: whoami + image: traefik/whoami + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami + +spec: + ports: + - protocol: TCP + port: 80 + selector: + app: whoami diff --git a/docs/content/reference/dynamic-configuration/networking.x-k8s.io_gatewayclasses.yaml b/docs/content/reference/dynamic-configuration/networking.x-k8s.io_gatewayclasses.yaml new file mode 100644 index 000000000..1d4f4d4f6 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/networking.x-k8s.io_gatewayclasses.yaml @@ -0,0 +1,146 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: gatewayclasses.networking.x-k8s.io +spec: + group: networking.x-k8s.io + names: + kind: GatewayClass + listKind: GatewayClassList + plural: gatewayclasses + shortNames: + - gc + singular: gatewayclass + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.controller + name: Controller + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: "GatewayClass describes a class of Gateways available to the user for creating Gateway resources. \n GatewayClass is a Cluster level resource. \n Support: Core." + 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: Spec for this GatewayClass. + properties: + controller: + description: "Controller is a domain/path string that indicates the controller that is managing Gateways of this class. \n Example: \"acme.io/gateway-controller\". \n This field is not mutable and cannot be empty. \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Support: Core" + maxLength: 253 + type: string + parametersRef: + description: "ParametersRef is a controller-specific resource containing the configuration parameters corresponding to this class. This is optional if the controller does not require any additional configuration. \n Parameters resources are implementation specific custom resources. These resources must be cluster-scoped. \n If the referent cannot be found, the GatewayClass's \"InvalidParameters\" status condition will be true. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + required: + - controller + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: InvalidParameters + description: Status of the GatewayClass. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: "False" + type: Admitted + description: Conditions is the current status from the controller for this GatewayClass. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/networking.x-k8s.io_gateways.yaml b/docs/content/reference/dynamic-configuration/networking.x-k8s.io_gateways.yaml new file mode 100644 index 000000000..4fb0114d2 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/networking.x-k8s.io_gateways.yaml @@ -0,0 +1,414 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: gateways.networking.x-k8s.io +spec: + group: networking.x-k8s.io + names: + kind: Gateway + listKind: GatewayList + plural: gateways + shortNames: + - gtw + singular: gateway + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: "Gateway represents an instantiation of a service-traffic handling infrastructure by binding Listeners to a set of IP addresses. \n Implementations should add the `gateway-exists-finalizer.networking.x-k8s.io` finalizer on the associated GatewayClass whenever Gateway(s) is running. This ensures that a GatewayClass associated with a Gateway(s) is not deleted while in use." + 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: "GatewaySpec defines the desired state of Gateway. \n Not all possible combinations of options specified in the Spec are valid. Some invalid configurations can be caught synchronously via a webhook, but there are many cases that will require asynchronous signaling via the GatewayStatus block." + properties: + addresses: + description: "Addresses requested for this gateway. This is optional and behavior can depend on the GatewayClass. If a value is set in the spec and the requested address is invalid, the GatewayClass MUST indicate this in the associated entry in GatewayStatus.Addresses. \n If no Addresses are specified, the GatewayClass may schedule the Gateway in an implementation-defined manner, assigning an appropriate set of Addresses. \n The GatewayClass MUST bind all Listeners to every GatewayAddress that it assigns to the Gateway. \n Support: Core" + items: + description: GatewayAddress describes an address that can be bound to a Gateway. + properties: + type: + default: IPAddress + description: "Type of the Address. This is either \"IPAddress\" or \"NamedAddress\". \n Support: Extended" + enum: + - IPAddress + - NamedAddress + type: string + value: + description: 'Value. Examples: "1.2.3.4", "128::1", "my-ip-address". Validity of the values will depend on `Type` and support by the controller.' + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + gatewayClassName: + description: GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + listeners: + description: "Listeners associated with this Gateway. Listeners define logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. \n An implementation MAY group Listeners by Port and then collapse each group of Listeners into a single Listener if the implementation determines that the Listeners in the group are \"compatible\". An implementation MAY also group together and collapse compatible Listeners belonging to different Gateways. \n For example, an implementation might consider Listeners to be compatible with each other if all of the following conditions are met: \n 1. Either each Listener within the group specifies the \"HTTP\" Protocol or each Listener within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. \n 2. Each Listener within the group specifies a Hostname that is unique within the group. \n 3. As a special case, one Listener within a group may omit Hostname, in which case this Listener matches when no other Listener matches. \n If the implementation does collapse compatible Listeners, the hostname provided in the incoming client request MUST be matched to a Listener to find the correct set of Routes. The incoming hostname MUST be matched using the Hostname field for each Listener in order of most to least specific. That is, exact matches must be processed before wildcard matches. \n If this field specifies multiple Listeners that have the same Port value but are not compatible, the implementation must raise a \"Conflicted\" condition in the Listener status. \n Support: Core" + items: + description: Listener embodies the concept of a logical endpoint where a Gateway can accept network connections. Each listener in a Gateway must have a unique combination of Hostname, Port, and Protocol. This will be enforced by a validating webhook. + properties: + hostname: + description: "Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified or \"*\", all hostnames are matched. This field can be omitted for protocols that don't require hostname based matching. \n Hostname is the fully qualified domain name of a network host, as defined by RFC 3986. Note the following deviations from the \"host\" part of the URI as defined in the RFC: \n 1. IP literals are not allowed. 2. The `:` delimiter is not respected because ports are not allowed. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. \"*.example.com\"). The wildcard character '*' must appear by itself as the first DNS label and matches only a single label. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + port: + description: "Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: "Protocol specifies the network protocol this listener expects to receive. The GatewayClass MUST apply the Hostname match appropriately for each protocol: \n * For the \"TLS\" protocol, the Hostname match MUST be applied to the [SNI](https://tools.ietf.org/html/rfc6066#section-3) server name offered by the client. * For the \"HTTP\" protocol, the Hostname match MUST be applied to the host portion of the [effective request URI](https://tools.ietf.org/html/rfc7230#section-5.5) or the [:authority pseudo-header](https://tools.ietf.org/html/rfc7540#section-8.1.2.3) * For the \"HTTPS\" protocol, the Hostname match MUST be applied at both the TLS and HTTP protocol layers. \n Support: Core" + type: string + routes: + description: "Routes specifies a schema for associating routes with the Listener using selectors. A Route is a resource capable of servicing a request and allows a cluster operator to expose a cluster resource (i.e. Service) by externally-reachable URL, load-balance traffic and terminate SSL/TLS. Typically, a route is a \"HTTPRoute\" or \"TCPRoute\" in group \"networking.x-k8s.io\", however, an implementation may support other types of resources. \n The Routes selector MUST select a set of objects that are compatible with the application protocol specified in the Protocol field. \n Although a client request may technically match multiple route rules, only one rule may ultimately receive the request. Matching precedence MUST be determined in order of the following criteria: \n * The most specific match. For example, the most specific HTTPRoute match is determined by the longest matching combination of hostname and path. * The oldest Route based on creation timestamp. For example, a Route with a creation timestamp of \"2020-09-08 01:02:03\" is given precedence over a Route with a creation timestamp of \"2020-09-08 01:02:04\". * If everything else is equivalent, the Route appearing first in alphabetical order (namespace/name) should be given precedence. For example, foo/bar is given precedence over foo/baz. \n All valid portions of a Route selected by this field should be supported. Invalid portions of a Route can be ignored (sometimes that will mean the full Route). If a portion of a Route transitions from valid to invalid, support for that portion of the Route should be dropped to ensure consistency. For example, even if a filter specified by a Route is invalid, the rest of the Route should still be supported. \n Support: Core" + properties: + group: + default: networking.x-k8s.io + description: "Group is the group of the route resource to select. Omitting the value or specifying the empty string indicates the networking.x-k8s.io API group. For example, use the following to select an HTTPRoute: \n routes: kind: HTTPRoute \n Otherwise, if an alternative API group is desired, specify the desired group: \n routes: group: acme.io kind: FooRoute \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + kind: + description: "Kind is the kind of the route resource to select. \n Kind MUST correspond to kinds of routes that are compatible with the application protocol specified in the Listener's Protocol field. \n If an implementation does not support or recognize this resource type, it SHOULD raise a \"ConditionInvalidRoutes\" condition for the affected Listener. \n Support: Core" + type: string + namespaces: + default: + from: Same + description: "Namespaces indicates in which namespaces Routes should be selected for this Gateway. This is restricted to the namespace of this Gateway by default. \n Support: Core" + properties: + from: + description: "From indicates where Routes will be selected for this Gateway. Possible values are: * All: Routes in all namespaces may be used by this Gateway. * Selector: Routes in namespaces selected by the selector may be used by this Gateway. * Same: Only Routes in the same namespace may be used by this Gateway. \n Support: Core" + enum: + - All + - Selector + - Same + type: string + selector: + description: "Selector must be specified when From is set to \"Selector\". In that case, only Routes in Namespaces matching this Selector will be selected by this Gateway. This field is ignored for other values of \"From\". \n Support: Core" + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + type: object + selector: + description: "Selector specifies a set of route labels used for selecting routes to associate with the Gateway. If RouteSelector is defined, only routes matching the RouteSelector are associated with the Gateway. An empty RouteSelector matches all routes. \n Support: Core" + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + required: + - kind + type: object + tls: + description: "TLS is the TLS configuration for the Listener. This field is required if the Protocol field is \"HTTPS\" or \"TLS\" and ignored otherwise. \n The association of SNIs to Certificate defined in GatewayTLSConfig is defined based on the Hostname field for this listener. \n The GatewayClass MUST use the longest matching SNI out of all available certificates for any TLS handshake. \n Support: Core" + properties: + certificateRef: + description: 'CertificateRef is the reference to Kubernetes object that contain a TLS certificate and private key. This certificate MUST be used for TLS handshakes for the domain this GatewayTLSConfig is associated with. If an entry in this list omits or specifies the empty string for both the group and the resource, the resource defaults to "secrets". An implementation may support other resources (for example, resource "mycertificates" in group "networking.acme.io"). Support: Core (Kubernetes Secrets) Support: Implementation-specific (Other resource types)' + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + mode: + description: 'Mode defines the TLS behavior for the TLS session initiated by the client. There are two possible modes: - Terminate: The TLS session between the downstream client and the Gateway is terminated at the Gateway. - Passthrough: The TLS session is NOT terminated by the Gateway. This implies that the Gateway can''t decipher the TLS stream except for the ClientHello message of the TLS protocol. CertificateRef field is ignored in this mode.' + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + type: string + description: "Options are a list of key/value pairs to give extended options to the provider. \n There variation among providers as to how ciphersuites are expressed. If there is a common subset for expressing ciphers then it will make sense to loft that as a core API construct. \n Support: Implementation-specific." + type: object + routeOverride: + default: + certificate: Deny + description: "RouteOverride dictates if TLS settings can be configured via Routes or not. \n CertificateRef must be defined even if `routeOverride.certificate` is set to 'Allow' as it will be used as the default certificate for the listener." + properties: + certificate: + default: Deny + description: "Certificate dictates if TLS certificates can be configured via Routes. If set to 'Allow', a TLS certificate for a hostname defined in a Route takes precedence over the certificate defined in Gateway. \n Support: Core" + enum: + - Allow + - Deny + type: string + required: + - certificate + type: object + type: object + required: + - port + - protocol + - routes + type: object + maxItems: 64 + minItems: 1 + type: array + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: NotReconciled + status: "False" + type: Scheduled + description: GatewayStatus defines the observed state of Gateway. + properties: + addresses: + description: "Addresses lists the IP addresses that have actually been bound to the Gateway. These addresses may differ from the addresses in the Spec, e.g. if the Gateway automatically assigns an address from a reserved pool. \n These addresses should all be of type \"IPAddress\"." + items: + description: GatewayAddress describes an address that can be bound to a Gateway. + properties: + type: + default: IPAddress + description: "Type of the Address. This is either \"IPAddress\" or \"NamedAddress\". \n Support: Extended" + enum: + - IPAddress + - NamedAddress + type: string + value: + description: 'Value. Examples: "1.2.3.4", "128::1", "my-ip-address". Validity of the values will depend on `Type` and support by the controller.' + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: NotReconciled + status: "False" + type: Scheduled + description: "Conditions describe the current conditions of the Gateway. \n Implementations should prefer to express Gateway conditions using the `GatewayConditionType` and `GatewayConditionReason` constants so that operators and tools can converge on a common vocabulary to describe Gateway state. \n Known condition types are: \n * \"Scheduled\" * \"Ready\"" + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + conditions: + description: Conditions describe the current condition of this listener. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + hostname: + description: Hostname is the Listener hostname value for which this message is reporting the status. + maxLength: 253 + minLength: 1 + type: string + port: + description: Port is the unique Listener port value for which this message is reporting the status. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: Protocol is the Listener protocol value for which this message is reporting the status. + type: string + required: + - conditions + - port + - protocol + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - port + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/networking.x-k8s.io_httproutes.yaml b/docs/content/reference/dynamic-configuration/networking.x-k8s.io_httproutes.yaml new file mode 100644 index 000000000..c64e1f986 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/networking.x-k8s.io_httproutes.yaml @@ -0,0 +1,528 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: httproutes.networking.x-k8s.io +spec: + group: networking.x-k8s.io + names: + kind: HTTPRoute + listKind: HTTPRouteList + plural: httproutes + singular: httproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: HTTPRoute is the Schema for the HTTPRoute 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: HTTPRouteSpec defines the desired state of HTTPRoute + properties: + gateways: + default: + allow: SameNamespace + description: Gateways defines which Gateways can use this Route. + properties: + allow: + default: SameNamespace + description: 'Allow indicates which Gateways will be allowed to use this route. Possible values are: * All: Gateways in any namespace can use this route. * FromList: Only Gateways specified in GatewayRefs may use this route. * SameNamespace: Only Gateways in the same namespace may use this route.' + enum: + - All + - FromList + - SameNamespace + type: string + gatewayRefs: + description: GatewayRefs must be specified when Allow is set to "FromList". In that case, only Gateways referenced in this list will be allowed to use this route. This field is ignored for other values of "Allow". + items: + description: GatewayReference identifies a Gateway in a specified namespace. + properties: + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - name + - namespace + type: object + type: array + type: object + hostnames: + description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. Hostname is the fully qualified domain name of a network host, as defined by RFC 3986. Note the following deviations from the \"host\" part of the URI as defined in the RFC: \n 1. IPs are not allowed. 2. The `:` delimiter is not respected because ports are not allowed. \n Incoming requests are matched against the hostnames before the HTTPRoute rules. If no hostname is specified, traffic is routed based on the HTTPRouteRules. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. \"*.example.com\"). The wildcard character '*' must appear by itself as the first DNS label and matches only a single label. You cannot have a wildcard label by itself (e.g. Host == \"*\"). Requests will be matched against the Host field in the following order: 1. If Host is precise, the request matches this rule if the http host header is equal to Host. 2. If Host is a wildcard, then the request matches this rule if the http host header is to equal to the suffix (removing the first label) of the wildcard rule. \n Support: Core" + items: + description: Hostname is used to specify a hostname that should be matched. + maxLength: 253 + minLength: 1 + type: string + maxItems: 16 + type: array + rules: + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions, optionally executing additional processing steps, and forwarding the request to an API object. + properties: + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or custom conformance. \n Support: core" + items: + description: 'HTTPRouteFilter defines additional processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express additional processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. TODO(hbagdi): re-render CRDs once controller-tools supports union tags: - https://github.com/kubernetes-sigs/controller-tools/pull/298 - https://github.com/kubernetes-sigs/controller-tools/issues/461' + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.acme.io\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + additionalProperties: + type: string + description: "Add adds the given header (name, value) to the request before the action. \n Input: GET /foo HTTP/1.1 \n Config: add: {\"my-header\": \"foo\"} \n Output: GET /foo HTTP/1.1 my-header: foo \n Support: Extended" + type: object + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of RemoveHeader is a list of HTTP header names. Note that the header names are case-insensitive [RFC-2616 4.2]. \n Input: GET /foo HTTP/1.1 My-Header1: ABC My-Header2: DEF My-Header2: GHI \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 My-Header2: DEF \n Support: Extended" + items: + type: string + maxItems: 16 + type: array + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. \n Support: Extended" + properties: + backendRef: + description: "BackendRef is a local object reference to mirror matched requests to. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the rule is not included in the route. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DegradedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + port: + description: Port specifies the destination port number to use for the backend referenced by the ServiceName or BackendRef field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + serviceName: + description: "ServiceName refers to the name of the Service to mirror matched requests to. When specified, this takes the place of BackendRef. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the rule is not included in the route. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DegradedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Core" + maxLength: 253 + type: string + required: + - port + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Custom: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior." + enum: + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + maxItems: 16 + type: array + forwardTo: + description: ForwardTo defines the backend(s) where matching requests should be sent. If unspecified, the rule performs no forwarding. If unspecified and no filters are specified that would result in a response being sent, a 503 error code is returned. + items: + description: HTTPRouteForwardTo defines how a HTTPRoute should forward a request. + properties: + backendRef: + description: "BackendRef is a reference to a backend to forward matched requests to. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the route must be dropped from the Gateway. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DroppedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + filters: + description: "Filters defined at this-level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Custom (For broader support of filters, use the Filters field in HTTPRouteRule.)" + items: + description: 'HTTPRouteFilter defines additional processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express additional processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. TODO(hbagdi): re-render CRDs once controller-tools supports union tags: - https://github.com/kubernetes-sigs/controller-tools/pull/298 - https://github.com/kubernetes-sigs/controller-tools/issues/461' + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.acme.io\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + additionalProperties: + type: string + description: "Add adds the given header (name, value) to the request before the action. \n Input: GET /foo HTTP/1.1 \n Config: add: {\"my-header\": \"foo\"} \n Output: GET /foo HTTP/1.1 my-header: foo \n Support: Extended" + type: object + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of RemoveHeader is a list of HTTP header names. Note that the header names are case-insensitive [RFC-2616 4.2]. \n Input: GET /foo HTTP/1.1 My-Header1: ABC My-Header2: DEF My-Header2: GHI \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 My-Header2: DEF \n Support: Extended" + items: + type: string + maxItems: 16 + type: array + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. \n Support: Extended" + properties: + backendRef: + description: "BackendRef is a local object reference to mirror matched requests to. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the rule is not included in the route. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DegradedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + port: + description: Port specifies the destination port number to use for the backend referenced by the ServiceName or BackendRef field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + serviceName: + description: "ServiceName refers to the name of the Service to mirror matched requests to. When specified, this takes the place of BackendRef. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the rule is not included in the route. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DegradedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Core" + maxLength: 253 + type: string + required: + - port + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Custom: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior." + enum: + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + maxItems: 16 + type: array + port: + description: "Port specifies the destination port number to use for the backend referenced by the ServiceName or BackendRef field. \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + serviceName: + description: "ServiceName refers to the name of the Service to forward matched requests to. When specified, this takes the place of BackendRef. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the route must be dropped from the Gateway. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DroppedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n The protocol to use should be specified with the AppProtocol field on Service resources. This field was introduced in Kubernetes 1.18. If using an earlier version of Kubernetes, a `networking.x-k8s.io/app-protocol` annotation on the BackendPolicy resource may be used to define the protocol. If the AppProtocol field is available, this annotation should not be used. The AppProtocol field, when populated, takes precedence over the annotation in the BackendPolicy resource. For custom backends, it is encouraged to add a semantically-equivalent field in the Custom Resource Definition. \n Support: Core" + maxLength: 253 + type: string + weight: + default: 1 + description: "Weight specifies the proportion of HTTP requests forwarded to the backend referenced by the ServiceName or BackendRef field. This is computed as weight/(sum of all weights in this ForwardTo list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support: Core" + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - port + type: object + maxItems: 4 + type: array + matches: + default: + - path: + type: Prefix + value: / + description: "Matches define 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. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: values: version: \"2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request should satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: \"2\"` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n A client request may match multiple HTTP route rules. Matching precedence MUST be determined in order of the following criteria, continuing on ties: * The longest matching hostname. * The longest matching path. * The largest number of header matches * The oldest Route based on creation timestamp. For example, a Route with a creation timestamp of \"2020-09-08 01:02:03\" is given precedence over a Route with a creation timestamp of \"2020-09-08 01:02:04\". * The Route appearing first in alphabetical order (namespace/name) for example, foo/bar is given precedence over foo/baz." + items: + description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: \"1\"` header: \n ``` match: path: value: \"/foo\" headers: values: version: \"1\" ```" + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"match\" behavior. For example, resource \"myroutematcher\" in group \"networking.acme.io\". If the referent cannot be found, the rule is not included in the route. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DegradedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + headers: + description: Headers specifies a HTTP request header matcher. + properties: + type: + default: Exact + description: "Type specifies how to match against the value of the header. \n Support: core (Exact) Support: custom (RegularExpression, ImplementationSpecific) \n Since RegularExpression PathType has custom conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect. \n HTTP Header name matching MUST be case-insensitive (RFC 2616 - section 4.2)." + enum: + - Exact + - RegularExpression + - ImplementationSpecific + type: string + values: + additionalProperties: + type: string + description: "Values is a map of HTTP Headers to be matched. It MUST contain at least one entry. \n The HTTP header field name to match is the map key, and the value of the HTTP header is the map value. HTTP header field name matching MUST be case-insensitive. \n Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route." + type: object + required: + - values + type: object + path: + default: + type: Prefix + value: / + description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. + properties: + type: + default: Prefix + description: "Type specifies how to match against the path Value. \n Support: core (Exact, Prefix) Support: custom (RegularExpression, ImplementationSpecific) \n Since RegularExpression PathType has custom conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - Prefix + - RegularExpression + - ImplementationSpecific + type: string + value: + description: Value of the HTTP path to match against. + minLength: 1 + type: string + required: + - value + type: object + type: object + maxItems: 8 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + tls: + description: "TLS defines the TLS certificate to use for Hostnames defined in this Route. This configuration only takes effect if the AllowRouteOverride field is set to true in the associated Gateway resource. \n Collisions can happen if multiple HTTPRoutes define a TLS certificate for the same hostname. In such a case, conflict resolution guiding principles apply, specificallly, if hostnames are same and two different certificates are specified then the certificate in the oldest resource wins. \n Please note that HTTP Route-selection takes place after the TLS Handshake (ClientHello). Due to this, TLS certificate defined here will take precedence even if the request has the potential to match multiple routes (in case multiple HTTPRoutes share the same hostname). \n Support: Core" + properties: + certificateRef: + description: 'CertificateRef refers to a Kubernetes object that contains a TLS certificate and private key. This certificate MUST be used for TLS handshakes for the domain this RouteTLSConfig is associated with. If an entry in this list omits or specifies the empty string for both the group and kind, the resource defaults to "secrets". An implementation may support other resources (for example, resource "mycertificates" in group "networking.acme.io"). Support: Core (Kubernetes Secrets) Support: Implementation-specific (Other resource types)' + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + required: + - certificateRef + type: object + required: + - rules + type: object + status: + description: HTTPRouteStatus defines the observed state of HTTPRoute. + properties: + gateways: + description: "Gateways is a list of the Gateways that are associated with the route, and the status of the route with respect to each of these Gateways. When a Gateway selects this route, the controller that manages the Gateway should add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route is modified. \n A maximum of 100 Gateways will be represented in this list. If this list is full, there may be additional Gateways using this Route that are not included in the list." + items: + description: RouteGatewayStatus describes the status of a route with respect to an associated Gateway. + properties: + conditions: + description: Conditions describes the status of the route with respect to the Gateway. For example, the "Admitted" condition indicates whether the route has been admitted or rejected by the Gateway, and why. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + gatewayRef: + description: GatewayRef is a reference to a Gateway object that is associated with the route. + properties: + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - name + - namespace + type: object + required: + - gatewayRef + type: object + maxItems: 100 + type: array + required: + - gateways + type: object + type: object + served: true + storage: true + subresources: + status: {} +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 8624cd6b8..c01e9cbb4 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -174,6 +174,9 @@ plugin's GOPATH. `--experimental.devplugin.modulename`: plugin's module name. +`--experimental.kubernetesgateway`: +Allow the Kubernetes gateway api provider usage. (Default: ```false```) + `--experimental.plugins..modulename`: plugin's module name. @@ -555,9 +558,6 @@ Allow cross namespace resource reference. (Default: ```true```) `--providers.kubernetescrd.certauthfilepath`: Kubernetes certificate authority file path (not needed for in-cluster client). -`--providers.kubernetescrd.disablepasshostheaders`: -Kubernetes disable PassHost Headers. (Default: ```false```) - `--providers.kubernetescrd.endpoint`: Kubernetes server endpoint (required for external cluster client). @@ -576,15 +576,33 @@ Ingress refresh throttle duration (Default: ```0```) `--providers.kubernetescrd.token`: Kubernetes bearer token (not needed for in-cluster client). +`--providers.kubernetesgateway`: +Enable Kubernetes gateway api provider with default settings. (Default: ```false```) + +`--providers.kubernetesgateway.certauthfilepath`: +Kubernetes certificate authority file path (not needed for in-cluster client). + +`--providers.kubernetesgateway.endpoint`: +Kubernetes server endpoint (required for external cluster client). + +`--providers.kubernetesgateway.labelselector`: +Kubernetes label selector to select specific GatewayClasses. + +`--providers.kubernetesgateway.namespaces`: +Kubernetes namespaces. + +`--providers.kubernetesgateway.throttleduration`: +Kubernetes refresh throttle duration (Default: ```0```) + +`--providers.kubernetesgateway.token`: +Kubernetes bearer token (not needed for in-cluster client). + `--providers.kubernetesingress`: Enable Kubernetes backend with default settings. (Default: ```false```) `--providers.kubernetesingress.certauthfilepath`: Kubernetes certificate authority file path (not needed for in-cluster client). -`--providers.kubernetesingress.disablepasshostheaders`: -Kubernetes disable PassHost Headers. (Default: ```false```) - `--providers.kubernetesingress.endpoint`: Kubernetes server endpoint (required for external cluster client). diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index ec663c089..a0fa23028 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -174,6 +174,9 @@ plugin's GOPATH. `TRAEFIK_EXPERIMENTAL_DEVPLUGIN_MODULENAME`: plugin's module name. +`TRAEFIK_EXPERIMENTAL_KUBERNETESGATEWAY`: +Allow the Kubernetes gateway api provider usage. (Default: ```false```) + `TRAEFIK_EXPERIMENTAL_PLUGINS__MODULENAME`: plugin's module name. @@ -555,9 +558,6 @@ Allow cross namespace resource reference. (Default: ```true```) `TRAEFIK_PROVIDERS_KUBERNETESCRD_CERTAUTHFILEPATH`: Kubernetes certificate authority file path (not needed for in-cluster client). -`TRAEFIK_PROVIDERS_KUBERNETESCRD_DISABLEPASSHOSTHEADERS`: -Kubernetes disable PassHost Headers. (Default: ```false```) - `TRAEFIK_PROVIDERS_KUBERNETESCRD_ENDPOINT`: Kubernetes server endpoint (required for external cluster client). @@ -576,15 +576,33 @@ Ingress refresh throttle duration (Default: ```0```) `TRAEFIK_PROVIDERS_KUBERNETESCRD_TOKEN`: Kubernetes bearer token (not needed for in-cluster client). +`TRAEFIK_PROVIDERS_KUBERNETESGATEWAY`: +Enable Kubernetes gateway api provider with default settings. (Default: ```false```) + +`TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_CERTAUTHFILEPATH`: +Kubernetes certificate authority file path (not needed for in-cluster client). + +`TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_ENDPOINT`: +Kubernetes server endpoint (required for external cluster client). + +`TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_LABELSELECTOR`: +Kubernetes label selector to select specific GatewayClasses. + +`TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_NAMESPACES`: +Kubernetes namespaces. + +`TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_THROTTLEDURATION`: +Kubernetes refresh throttle duration (Default: ```0```) + +`TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_TOKEN`: +Kubernetes bearer token (not needed for in-cluster client). + `TRAEFIK_PROVIDERS_KUBERNETESINGRESS`: Enable Kubernetes backend with default settings. (Default: ```false```) `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_CERTAUTHFILEPATH`: Kubernetes certificate authority file path (not needed for in-cluster client). -`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_DISABLEPASSHOSTHEADERS`: -Kubernetes disable PassHost Headers. (Default: ```false```) - `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_ENDPOINT`: Kubernetes server endpoint (required for external cluster client). diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 3e7b69a4f..ceacd0198 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -99,7 +99,6 @@ endpoint = "foobar" token = "foobar" certAuthFilePath = "foobar" - disablePassHostHeaders = true namespaces = ["foobar", "foobar"] labelSelector = "foobar" ingressClass = "foobar" @@ -112,12 +111,18 @@ endpoint = "foobar" token = "foobar" certAuthFilePath = "foobar" - disablePassHostHeaders = true namespaces = ["foobar", "foobar"] allowCrossNamespace = true labelSelector = "foobar" ingressClass = "foobar" throttleDuration = 42 + [providers.kubernetesGateway] + endpoint = "foobar" + token = "foobar" + certAuthFilePath = "foobar" + namespaces = ["foobar", "foobar"] + labelSelector = "foobar" + throttleDuration = 42 [providers.rest] insecure = true [providers.rancher] @@ -385,3 +390,4 @@ [experimental.devPlugin] goPath = "foobar" moduleName = "foobar" + kubernetesGateway = true diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index cbbd83f6c..e2011bc1a 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -105,7 +105,6 @@ providers: endpoint: foobar token: foobar certAuthFilePath: foobar - disablePassHostHeaders: true namespaces: - foobar - foobar @@ -120,7 +119,6 @@ providers: endpoint: foobar token: foobar certAuthFilePath: foobar - disablePassHostHeaders: true namespaces: - foobar - foobar @@ -128,6 +126,15 @@ providers: labelSelector: foobar ingressClass: foobar throttleDuration: 42s + kubernetesGateway: + endpoint: foobar + token: foobar + certAuthFilePath: foobar + namespaces: + - foobar + - foobar + labelSelector: foobar + throttleDuration: 42s rest: insecure: true rancher: @@ -403,3 +410,5 @@ experimental: devPlugin: goPath: foobar moduleName: foobar + kubernetesGateway: true + diff --git a/docs/content/routing/providers/kubernetes-gateway.md b/docs/content/routing/providers/kubernetes-gateway.md new file mode 100644 index 000000000..03d326760 --- /dev/null +++ b/docs/content/routing/providers/kubernetes-gateway.md @@ -0,0 +1,161 @@ +# Traefik & Kubernetes + +The Kubernetes Gateway API, The Experimental Way. +{: .subtitle } + +## Configuration Examples + +??? example "Configuring Kubernetes Gateway provider and Deploying/Exposing Services" + + ```yaml tab="Gateway API" + --8<-- "content/reference/dynamic-configuration/kubernetes-gateway-simple-https.yml" + ``` + + ```yaml tab="Whoami Service" + --8<-- "content/reference/dynamic-configuration/kubernetes-whoami-svc.yml" + ``` + + ```yaml tab="Traefik Service" + --8<-- "content/reference/dynamic-configuration/kubernetes-gateway-traefik-lb-svc.yml" + ``` + + ```yaml tab="RBAC" + --8<-- "content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml" + ``` + +## Routing Configuration + +### Custom Resource Definition (CRD) + +* You can find an exhaustive list, of the custom resources and their attributes in +[the reference page](../../reference/dynamic-configuration/kubernetes-gateway.md) or in the Kubernetes Sigs `Service APIs` [repository](https://github.com/kubernetes-sigs/service-apis/). +* Validate that [the prerequisites](../../providers/kubernetes-gateway.md#configuration-requirements) are fulfilled before using the Traefik Kubernetes Gateway Provider. + +You can find an excerpt of the supported Kubernetes Gateway API resources in the table below: + +| Kind | Purpose | Concept Behind | +|------------------------------------|---------------------------------------------------------------------------|-------------------------------------------------------------------------------------------| +| [GatewayClass](#kind-gatewayclass) | Defines a set of Gateways that share a common configuration and behaviour | [GatewayClass](https://kubernetes-sigs.github.io/service-apis/api-overview/#gatewayclass) | +| [Gateway](#kind-gateway) | Describes how traffic can be translated to Services within the cluster | [Gateway](https://kubernetes-sigs.github.io/service-apis/api-overview/#gateway) | +| [HTTPRoute](#kind-httproute) | HTTP rules for mapping requests from a Gateway to Kubernetes Services | [Route](https://kubernetes-sigs.github.io/service-apis/api-overview/#httptcpfooroute) | + +### Kind: `GatewayClass` + +`GatewayClass` is cluster-scoped resource defined by the infrastructure provider. This resource represents a class of Gateways that can be instantiated. +More details on the GatewayClass [official documentation](https://kubernetes-sigs.github.io/service-apis/gatewayclass/). + +The `GatewayClass` should be declared by the infrastructure provider, otherwise please register the `GatewayClass` +[definition](../../reference/dynamic-configuration/kubernetes-gateway.md#definitions) in the Kubernetes cluster before +creating `GatewayClass` objects. + +!!! info "Declaring GatewayClass" + + ```yaml + kind: GatewayClass + apiVersion: networking.x-k8s.io/v1alpha1 + metadata: + name: my-gateway-class + spec: + # Controller is a domain/path string that indicates + # the controller that is managing Gateways of this class. + controller: traefik.io/gateway-controller + ``` + +### Kind: `Gateway` + +A `Gateway` is 1:1 with the life cycle of the configuration of infrastructure. When a user creates a Gateway, +some load balancing infrastructure is provisioned or configured by the GatewayClass controller. +More details on the Gateway [official documentation](https://kubernetes-sigs.github.io/service-apis/gateway/). + +Register the `Gateway` [definition](../../reference/dynamic-configuration/kubernetes-gateway.md#definitions) in the +Kubernetes cluster before creating `Gateway` objects. + +!!! info "Declaring Gateway" + + ``` + kind: Gateway + apiVersion: networking.x-k8s.io/v1alpha1 + metadata: + name: my-gateway + namespace: default + spec: + gatewayClassName: my-gateway-class # [1] + listeners: # [2] + - protocol: HTTPS # [3] + port: 443 # [4] + tls: # [5] + certificateRef: # [6] + group: "core" + kind: "Secret" + name: "mysecret" + routes: # [7] + kind: HTTPRoute # [8] + selector: # [9] + matchLabels: # [10] + app: foo + ``` + +| Ref | Attribute | Description | +|------|--------------------|-----------------------------------------------------------------------------------------------------------------------------| +| [1] | `gatewayClassName` | GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. | +| [2] | `listeners` | Logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. | +| [3] | `protocol` | The network protocol this listener expects to receive (only HTTP and HTTPS are implemented). | +| [4] | `port` | The network port. | +| [5] | `tls` | TLS configuration for the Listener. This field is required if the Protocol field is "HTTPS" or "TLS" and ignored otherwise. | +| [6] | `certificateRef` | The reference to Kubernetes object that contains a TLS certificate and private key. | +| [7] | `routes` | A schema for associating routes with the Listener using selectors. | +| [8] | `kind` | The kind of the referent. | +| [9] | `selector` | Routes in namespaces selected by the selector may be used by this Gateway routes to associate with the Gateway. | +| [10] | `matchLabels` | A set of route labels used for selecting routes to associate with the Gateway. | + +### Kind: `HTTPRoute` + +`HTTPRoute` defines HTTP rules for mapping requests from a `Gateway` to Kubernetes Services. + +Register the `HTTPRoute` [definition](../../reference/dynamic-configuration/kubernetes-gateway.md#definitions) in the +Kubernetes cluster before creating `HTTPRoute` objects. + +!!! info "Declaring HTTPRoute" + + ```yaml + kind: HTTPRoute + apiVersion: networking.x-k8s.io/v1alpha1 + metadata: + name: http-app-1 + namespace: default + labels: # [1] + app: foo + spec: + hostnames: # [2] + - "whoami" + 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] + ``` + +| 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. | diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 294170787..df5018fb8 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -81,6 +81,7 @@ nav: - 'Docker': 'providers/docker.md' - 'Kubernetes IngressRoute': 'providers/kubernetes-crd.md' - 'Kubernetes Ingress': 'providers/kubernetes-ingress.md' + - 'Kubernetes Gateway API': 'providers/kubernetes-gateway.md' - 'Consul Catalog': 'providers/consul-catalog.md' - 'ECS': 'providers/ecs.md' - 'Marathon': 'providers/marathon.md' @@ -100,6 +101,7 @@ nav: - 'Docker': 'routing/providers/docker.md' - 'Kubernetes IngressRoute': 'routing/providers/kubernetes-crd.md' - 'Kubernetes Ingress': 'routing/providers/kubernetes-ingress.md' + - 'Kubernetes Gateway API': 'routing/providers/kubernetes-gateway.md' - 'Consul Catalog': 'routing/providers/consul-catalog.md' - 'ECS': 'routing/providers/ecs.md' - 'Marathon': 'routing/providers/marathon.md' @@ -190,6 +192,7 @@ nav: - 'File': 'reference/dynamic-configuration/file.md' - 'Docker': 'reference/dynamic-configuration/docker.md' - 'Kubernetes CRD': 'reference/dynamic-configuration/kubernetes-crd.md' + - 'Kubernetes Gateway API': 'reference/dynamic-configuration/kubernetes-gateway.md' - 'Consul Catalog': 'reference/dynamic-configuration/consul-catalog.md' - 'ECS': 'reference/dynamic-configuration/ecs.md' - 'KV': 'reference/dynamic-configuration/kv.md' diff --git a/go.mod b/go.mod index f4459a2ae..00ae45762 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/traefik/traefik/v2 go 1.15 require ( - github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect 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 @@ -21,7 +20,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/docker/cli v0.0.0-20200221155518-740919cc7fc0 github.com/docker/distribution v2.7.1+incompatible // indirect - github.com/docker/docker v0.0.0-00010101000000-000000000000 + github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0 github.com/docker/docker-credential-helpers v0.6.3 // indirect github.com/docker/go-connections v0.4.0 github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82 // indirect @@ -41,6 +40,7 @@ require ( github.com/gorilla/mux v1.7.3 github.com/gorilla/websocket v1.4.2 github.com/hashicorp/consul/api v1.3.0 + github.com/hashicorp/go-multierror v1.0.0 github.com/hashicorp/go-version v1.2.0 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d github.com/instana/go-sensor v1.5.1 @@ -65,7 +65,7 @@ require ( github.com/pires/go-proxyproto v0.3.1 github.com/pmezard/go-difflib v1.0.0 github.com/prometheus/client_golang v1.3.0 - github.com/prometheus/client_model v0.1.0 + github.com/prometheus/client_model v0.2.0 github.com/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac github.com/sirupsen/logrus v1.7.0 github.com/stretchr/testify v1.6.1 @@ -83,7 +83,7 @@ require ( go.elastic.co/apm v1.7.0 go.elastic.co/apm/module/apmot v1.7.0 golang.org/x/mod v0.3.0 - golang.org/x/net v0.0.0-20200822124328-c89045814202 + golang.org/x/net v0.0.0-20200904194848-62affa334b73 golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e google.golang.org/grpc v1.27.1 gopkg.in/DataDog/dd-trace-go.v1 v1.19.0 @@ -95,6 +95,7 @@ require ( k8s.io/client-go v0.19.2 k8s.io/code-generator v0.19.2 mvdan.cc/xurls/v2 v2.1.0 + sigs.k8s.io/service-apis v0.1.0 ) // Docker v19.03.6 diff --git a/go.sum b/go.sum index 78510025f..1d09a784a 100644 --- a/go.sum +++ b/go.sum @@ -91,9 +91,11 @@ github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEY github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= @@ -109,6 +111,8 @@ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/ github.com/abronan/valkeyrie v0.0.0-20200127174252-ef4277a138cd h1:UlQRt3CZdeD+WfDamDtdDDOu84CYbGIh9/B28TgzCZk= github.com/abronan/valkeyrie v0.0.0-20200127174252-ef4277a138cd/go.mod h1:2RUNONRAQ8bS1QcVJF3dYO/faiEro6NAAIQ6CqBkpD0= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/ahmetb/gen-crd-api-reference-docs v0.2.0/go.mod h1:P/XzJ+c2+khJKNKABcm2biRwk2QAuwbLf8DlXuaL7WM= github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.18 h1:KyEv96ncdgOIJRTKMcWlIqM0umf8X3LQP6oOyg0hNsM= github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.18/go.mod h1:L+HB2uBoDgi3+r1pJEJcbGwyyHhd2QXaGsKLbDwtm8Q= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -117,9 +121,11 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v1.61.458 h1:UdFGeD4Eg6gZFQ7tLWdguNLpBTevJwBa97S0YunGy1k= github.com/aliyun/alibaba-cloud-sdk-go v1.61.458/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= 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= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= @@ -127,6 +133,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.16.23/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -139,6 +147,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.0+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/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= @@ -149,6 +158,8 @@ github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8 github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -188,29 +199,39 @@ github.com/containous/multibuf v0.0.0-20190809014333-8b6c9a7e6bba h1:PhR03pep+5e github.com/containous/multibuf v0.0.0-20190809014333-8b6c9a7e6bba/go.mod h1:zkWcASFUJEst6QwCrxLdkuw1gvaKqmflEipm+iecV5M= github.com/containous/mux v0.0.0-20181024131434-c33f32e26898 h1:1srn9voikJGofblBhWy3WuZWqo14Ou7NaswNG/I2yWc= github.com/containous/mux v0.0.0-20181024131434-c33f32e26898/go.mod h1:z8WW7n06n8/1xF9Jl9WmuDeZuHAhfL+bwarNjsciwwg= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY= github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +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-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= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpu/goacmedns v0.0.3 h1:QOeMpIEsIdm1LSASSswjaTf8CXmzcrgy5OeCfHjppA4= github.com/cpu/goacmedns v0.0.3/go.mod h1:4MipLkI+qScwqtVxcNO6okBhbgRrr7/tKXUSgSL0teQ= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnsimple/dnsimple-go v0.63.0 h1:0doY8VW/ckRIMTmOw4E1vwqo+bhtjDzvh1pU2ZteFGA= @@ -227,6 +248,7 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82 h1:X0fj836zx99zFu83v/M79DuBn84IL/Syx1SY6Y5ZEMA= github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libcompose v0.0.0-20190805081528-eac9fe1b8b03 h1:kodxuqd2aTzaoldemGNQLrnYGpYLvm1kRdzngQa6hPc= @@ -238,6 +260,7 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3 github.com/donovanhide/eventsource v0.0.0-20170630084216-b8f31a59085e h1:rMOGp6HPeMHbdLrZkX2nD+94uqDunc27tXVuS+ey4mQ= github.com/donovanhide/eventsource v0.0.0-20170630084216-b8f31a59085e/go.mod h1:56wL82FO0bfMU5RvfXoIwSOP2ggqqxT+tAfNEIyxuHw= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k= github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0= github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU= @@ -262,6 +285,8 @@ github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exoscale/egoscale v0.23.0 h1:hoUDzrO8yNoobNdnrRvlRFjfg3Ng0vQTrv6bXRJu6z0= @@ -275,7 +300,6 @@ 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/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 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -284,6 +308,8 @@ github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2/go.mod h1:GLy 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/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.1.3 h1:D8nnzrijQFUAqdNPwnbvm6tJ3AJAzQAlnROeecUNG/4= github.com/go-acme/lego/v4 v4.1.3/go.mod h1:pIFm5tWkXSgiAEfJ/XQCQIvX1cEvHFwbgLZyx8OVSUE= github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= @@ -297,34 +323,68 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea h1:CnEQOUv4ilElSwFB9g/lVmz206oLE4aNZDYngIY1Gvg= github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.2.1 h1:fV3MLmabKIZ383XifUjFSwcoGee0v9qgPp8wy5svibE= +github.com/go-logr/logr v0.2.1/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 h1:JVrqSeQfdhYRFk24TvhTZWU0q8lfCojxZQFi3Ou7+uY= github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 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/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= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -338,6 +398,7 @@ github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptG github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 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= @@ -346,13 +407,12 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb 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 h1:ocYkMQY5RrXTYgXl7ICpV0IXwlEQGwKIsery4gyXa1U= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +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= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -377,7 +437,6 @@ github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBE github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -394,8 +453,12 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ 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= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gophercloud/gophercloud v0.6.1-0.20191122030953-d8ac278c1c9d/go.mod h1:ozGNgr9KYOVATV5jsgHl/ceCDXGuguqOZAzoQ/2vcNM= github.com/gophercloud/gophercloud v0.7.0 h1:vhmQQEM2SbnGCg2/3EzQnQZ3V7+UCGy9s8exQCprNYg= github.com/gophercloud/gophercloud v0.7.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss= @@ -412,13 +475,13 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gravitational/trace v0.0.0-20190726142706-a535a178675f h1:68WxnfBzJRYktZ30fmIjGQ74RsXYLoeH2/NITPktTMY= github.com/gravitational/trace v0.0.0-20190726142706-a535a178675f/go.mod h1:RvdOUHE4SHqR3oXlFFKnGzms8a5dugHygGw1bqDstYI= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg= 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.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= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= @@ -460,8 +523,10 @@ github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+d github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= @@ -469,7 +534,6 @@ github.com/hashicorp/memberlist v0.1.4 h1:gkyML/r71w3FL8gUi74Vk76avkj/9lYAY9lvg0 github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -477,10 +541,10 @@ github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmK github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5uPK3wHh96wCsqe3hCMKh8E= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= -github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 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= @@ -489,7 +553,6 @@ github.com/instana/go-sensor v1.5.1/go.mod h1:5dEieTqu59XZr2/X53xF2Px4v83aSRRZa/ 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/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= @@ -500,7 +563,6 @@ github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22 github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -517,9 +579,7 @@ github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXH github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -546,6 +606,7 @@ github.com/liquidweb/liquidweb-go v1.6.1/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVL 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/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 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51 h1:Kg/NPZLLC3aAFr1YToMs98dbCdhootQ1hZIvZU28hAQ= @@ -553,13 +614,17 @@ github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51/go.mod h1:RYmqHb github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f h1:ZZYhg16XocqSKPGNQAe0aeweNtFxuedbwwb4fSlg7h4= github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f/go.mod h1:8heskWJ5c0v5J9WH89ADhyal1DOZcayll8fSbhB+/9A= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 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/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-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= @@ -582,7 +647,6 @@ github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9 github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -597,12 +661,14 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2 h1:i2Ly0B+1+rzNZHHWtD4ZwKi+OU5l+uQo1iDHZ2PmiIc= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= @@ -620,21 +686,29 @@ github.com/nrdcg/goinwx v0.8.1 h1:20EQ/JaGFnSKwiDH2JzjIpicffl3cPk6imJBDqVBVtU= github.com/nrdcg/goinwx v0.8.1/go.mod h1:tILVc10gieBp/5PMvbcYeXM6pVQ+c9jxDZnpaR1UW7c= github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg= github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= 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.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y= +github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= @@ -670,6 +744,7 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= @@ -681,7 +756,6 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi github.com/pires/go-proxyproto v0.3.1 h1:eWb52zeDUbSUDBV+8aVCfOy0pnEG6DrDW3cJ/WKdQsk= github.com/pires/go-proxyproto v0.3.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -689,27 +763,29 @@ github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6J github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= 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= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.3.0 h1:miYCvYqFXtl/J9FIy8eNpBfYthAEFg+Ys0XyUVEcDsc= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0 h1:ElTg5tNp4DqfV7UQjDqv2+RJlNzsDtvNAWccbItceIE= 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-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= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= @@ -717,12 +793,14 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R 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= github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= +github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA= github.com/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac h1:wBGhHdXKICZmvAPWS8gQoMyOWDH7QAi9bU4Z1nDWnFU= github.com/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac/go.mod h1:67sLWL17mVlO1HFROaTBmU71NB4R8UNCesFHhg0f6LQ= @@ -730,18 +808,20 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhD github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sacloud/libsacloud v1.36.2 h1:aosI7clbQ9IU0Hj+3rpk3SKJop5nLPpLThnWCivPqjI= github.com/sacloud/libsacloud v1.36.2/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg= -github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec h1:6ncX5ko6B9LntYM0YBRXkiSaZMmLYeZ/NWcmeB43mMY= github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCLY7gIfzeQaUJ+kppEO5WQG3cL8iE8tGHU= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis= github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= 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/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 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= @@ -758,14 +838,23 @@ 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/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= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 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/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -776,13 +865,13 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 h1:XGopsea1Dw7ecQ8JscCNQXDGYAKDiWjDeXnpN/+BY9g= github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +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= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -801,6 +890,8 @@ github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24sz github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/GfSYVCjK7dyaw= github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/unrolled/render v1.0.2 h1:dGS3EmChQP3yOi1YeFNO/Dx+MbWZhdvhQJTXochM5bs= github.com/unrolled/render v1.0.2/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM= github.com/unrolled/secure v1.0.7 h1:BcQHp3iKZyZCKj5gRqwQG+5urnGBF00wGgoPPwtheVQ= @@ -811,6 +902,7 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 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/vulcand/oxy v1.1.0 h1:DbBijGo1+6cFqR9jarkMxasdj0lgWwrrFtue6ijek4Q= github.com/vulcand/oxy v1.1.0/go.mod h1:ADiMYHi8gkGl2987yQIzDRoXZilANF4WtKaQ92OppKY= github.com/vulcand/predicate v1.1.0 h1:Gq/uWopa4rx/tnZu2opOSBqHK63Yqlou/SzrbwdJiNg= @@ -828,7 +920,9 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.elastic.co/apm v1.7.0 h1:vd4ncfZ/Y2GIsWW7aFR4uQdqmfUbuHfUhglqOqEwrUI= go.elastic.co/apm v1.7.0/go.mod h1:IYfi/330rWC5Kfns1rM+kY+RPkIdgUziRF6Cbm9qlxQ= go.elastic.co/apm/module/apmhttp v1.7.0 h1:dwUkUHlGR6W7FSAxdsZvO3tz+IaLxlXSnwH7ABahJdc= @@ -837,13 +931,16 @@ go.elastic.co/apm/module/apmot v1.7.0 h1:BkdjCYAJY1vcnb/kwfIZJU6XTNgtbN6H3GPwuMy go.elastic.co/apm/module/apmot v1.7.0/go.mod h1:d2HlJ5Wr8ZfSUvRobRVK5vCihOkk/K+rDUEA9ONMQL0= go.elastic.co/fastjson v1.0.0 h1:ooXV/ABvf+tBul26jcVViPT3sBir0PvXgibYB1IQQzg= go.elastic.co/fastjson v1.0.0/go.mod h1:PmeUOMMtLHQr9ZS9J9owrAVg0FkaZDRZJEFTTGHtchs= -go.etcd.io/bbolt v1.3.1-etcd.8 h1:6J7QAKqfFBGnU80KRnuQxfjjeE5xAGE/qB810I3FQHQ= go.etcd.io/bbolt v1.3.1-etcd.8/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd v3.3.13+incompatible h1:jCejD5EMnlGxFvcGRyEV4VGlENZc7oPQX6o0t7n3xbw= go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= +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.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= @@ -852,11 +949,9 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= @@ -864,24 +959,27 @@ go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277 h1:d9qaMM+ODpCq+9We41// go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 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-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-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= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6 h1:TjszyFsQsyZNHwdVdZ5m7bjmreu0znc2kRYsEml9/Ww= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/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 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= @@ -895,7 +993,6 @@ golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= 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= @@ -916,13 +1013,14 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -931,30 +1029,33 @@ 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-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= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= 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 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/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= @@ -966,8 +1067,10 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -977,8 +1080,12 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h 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= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-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= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -991,39 +1098,40 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e h1:9vRrk9YW2BTzLP0VCB9ZDjU4cPqkg+IDWL7XgxA1yxQ= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1033,6 +1141,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/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= +golang.org/x/tools v0.0.0-20190213192042-740235f6c0d8/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -1043,10 +1153,12 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1067,14 +1179,18 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb h1:iKlO7ROJc6SttHKlxzwGytRtBUqX4VARrNTgP2YLX5M= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200616133436-c1934b75d054 h1:HHeAlu5H9b71C+Fx0K+1dGgVFN1DM1/wz4aoGOA5qS8= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200616195046-dc31b401abb5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200904185747-39188db58858 h1:xLt+iB5ksWcZVxqc+g9K41ZHy+6MKWfXCDsjSThnsPA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= 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= @@ -1112,7 +1228,6 @@ google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171 h1:xes2Q2k+d/+YNXVw0FpZkIDJiaux4OVrRKXRAzH6A0U= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= @@ -1144,12 +1259,10 @@ gopkg.in/DataDog/dd-trace-go.v1 v1.19.0 h1:aFSFd6oDMdvPYiToGqTv7/ERA6QrPhGaXSuue gopkg.in/DataDog/dd-trace-go.v1 v1.19.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= @@ -1159,7 +1272,6 @@ gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdOD gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.51.1 h1:GyboHr4UqMiLUybYjd22ZjQIKEJEpgtLXtuGbR21Oho= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -1173,12 +1285,13 @@ gopkg.in/jcmturner/gokrb5.v7 v7.2.3 h1:hHMV/yKPwMnJhPuPx7pH2Uw/3Qyf+thJYlisUc440 gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU= gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/ns1/ns1-go.v2 v2.4.2 h1:H6VnvLez0GjxXsXat6MUFmKuiMFuDaMBdGF9qtkmODo= gopkg.in/ns1/ns1-go.v2 v2.4.2/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk= gopkg.in/redis.v5 v5.2.9 h1:MNZYOLPomQzZMfpN3ZtD1uyJ2IDonTTlxYiV/pEApiw= gopkg.in/redis.v5 v5.2.9/go.mod h1:6gtv0/+A4iM08kdRfocWYB3bLX2tebpNtfKlFT6H4mY= -gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= @@ -1189,10 +1302,10 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -1208,23 +1321,51 @@ honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M= howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= +k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78= +k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms= k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= +k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY= +k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M= +k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= +k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc= k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw= +k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg= +k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU= +k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q= k8s.io/client-go v0.19.2 h1:gMJuU3xJZs86L1oQ99R4EViAADUPMHHtS9jFshasHSc= k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA= +k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= +k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= k8s.io/code-generator v0.19.2 h1:7uaWJll6fyCPj2j3sfNN1AiY2gZU1VFN2dFR2uoxGWI= k8s.io/code-generator v0.19.2/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= +k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM= +k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14 h1:t4L10Qfx/p7ASH3gXCdIUtPbbIuegCoUJf3TMSFekjw= k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200728071708-7794989d0000 h1:XgICMZutMLbopSVIJJrhUun6Hbuh1NTZBv2sd0lvypU= +k8s.io/gengo v0.0.0-20200728071708-7794989d0000/go.mod h1:aG2eeomYfcUw8sE3fa7YdkjgnGtyY56TjZlaJJ0ZoWo= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ= +k8s.io/klog/v2 v2.3.0 h1:WmkrnW7fdrm0/DMClc+HIxtftvxVIPAhlVwMQo5yLco= +k8s.io/klog/v2 v2.3.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +k8s.io/kube-openapi v0.0.0-20200831175022-64514a1d5d59 h1:hlbT1c/UQK1Zf9lsxemrM7C/WnIPwGHgFUgpkVraHcs= +k8s.io/kube-openapi v0.0.0-20200831175022-64514a1d5d59/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg= k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= @@ -1233,9 +1374,15 @@ mvdan.cc/xurls/v2 v2.1.0/go.mod h1:5GrSd9rOnKOpZaji1OZLYL/yeAAtGDlo/cFe+8K5n8E= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 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/controller-runtime v0.6.2/go.mod h1:vhcq/rlnENJ09SIRp3EveTaZ0yqH526hjf9iJdbUJ/E= +sigs.k8s.io/controller-tools v0.4.0/go.mod h1:G9rHdZMVlBDocIxGkK3jHLWqcTMNvveypYJwrvYKjWU= +sigs.k8s.io/service-apis v0.1.0 h1:yImgpgLrxSD5tMdLqpIDEzroFaUzqwZbrg6/H3VpkYM= +sigs.k8s.io/service-apis v0.1.0/go.mod h1:QkiV/PnK7YbN5zqYqXnh5wByTTT1LYJ5scwdIs62qWs= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= 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= diff --git a/integration/fixtures/k8s/01-gateway-api.yml b/integration/fixtures/k8s/01-gateway-api.yml new file mode 100644 index 000000000..352d76eaa --- /dev/null +++ b/integration/fixtures/k8s/01-gateway-api.yml @@ -0,0 +1,1239 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: backendpolicies.networking.x-k8s.io +spec: + group: networking.x-k8s.io + names: + kind: BackendPolicy + listKind: BackendPolicyList + plural: backendpolicies + singular: backendpolicy + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: BackendPolicy defines policies associated with backends. For the purpose of this API, a backend is defined as any resource that a route can forward traffic to. A common example of a backend is a Service. Configuration that is implementation specific may be represented with similar implementation specific custom resources. + 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: BackendPolicySpec defines desired policy for a backend. + properties: + backendRefs: + description: "BackendRefs define which backends this policy should be applied to. This policy can only apply to backends within the same namespace. If more than one BackendPolicy targets the same backend, precedence must be given to the oldest BackendPolicy. \n Support: Core" + items: + description: BackendRef identifies an API object within a known namespace that defaults group to core and resource to services if unspecified. + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + type: string + kind: + description: Kind is the kind of the referent. + maxLength: 253 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + type: string + port: + description: Port is the port of the referent. If unspecified, this policy applies to all ports on the backend. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - group + - name + type: object + maxItems: 16 + type: array + tls: + description: "TLS is the TLS configuration for these backends. \n Support: Extended" + properties: + certificateAuthorityRef: + description: "CertificateAuthorityRef is a reference to a resource that includes trusted CA certificates for the associated backends. If an entry in this list omits or specifies the empty string for both the group and the resource, the resource defaults to \"secrets\". An implementation may support other resources (for example, resource \"mycertificates\" in group \"networking.acme.io\"). \n When stored in a Secret, certificates must be PEM encoded and specified within the \"ca.crt\" data field of the Secret. Multiple certificates can be specified, concatenated by new lines. \n Support: Extended" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + options: + additionalProperties: + type: string + description: "Options are a list of key/value pairs to give extended options to the provider. \n Support: Implementation-specific." + type: object + type: object + required: + - backendRefs + type: object + status: + description: BackendPolicyStatus defines the observed state of BackendPolicy. Conditions that are related to a specific Route or Gateway should be placed on the Route(s) using backends configured by this BackendPolicy. + properties: + conditions: + description: Conditions describe the current conditions of the BackendPolicy. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} +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: gatewayclasses.networking.x-k8s.io +spec: + group: networking.x-k8s.io + names: + kind: GatewayClass + listKind: GatewayClassList + plural: gatewayclasses + singular: gatewayclass + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.controller + name: Controller + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: "GatewayClass describes a class of Gateways available to the user for creating Gateway resources. \n GatewayClass is a Cluster level resource. \n Support: Core." + 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: Spec for this GatewayClass. + properties: + controller: + description: "Controller is a domain/path string that indicates the controller that is managing Gateways of this class. \n Example: \"acme.io/gateway-controller\". \n This field is not mutable and cannot be empty. \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Support: Core" + maxLength: 253 + type: string + parametersRef: + description: "ParametersRef is a controller-specific resource containing the configuration parameters corresponding to this class. This is optional if the controller does not require any additional configuration. \n Parameters resources are implementation specific custom resources. These resources must be cluster-scoped. \n If the referent cannot be found, the GatewayClass's \"InvalidParameters\" status condition will be true. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + required: + - controller + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: InvalidParameters + description: Status of the GatewayClass. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: InvalidParameters + description: Conditions is the current status from the controller for this GatewayClass. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} +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: gateways.networking.x-k8s.io +spec: + group: networking.x-k8s.io + names: + kind: Gateway + listKind: GatewayList + plural: gateways + singular: gateway + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: "Gateway represents an instantiation of a service-traffic handling infrastructure by binding Listeners to a set of IP addresses. \n Implementations should add the `gateway-exists-finalizer.networking.x-k8s.io` finalizer on the associated GatewayClass whenever Gateway(s) is running. This ensures that a GatewayClass associated with a Gateway(s) is not deleted while in use." + 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: "GatewaySpec defines the desired state of Gateway. \n Not all possible combinations of options specified in the Spec are valid. Some invalid configurations can be caught synchronously via a webhook, but there are many cases that will require asynchronous signaling via the GatewayStatus block." + properties: + addresses: + description: "Addresses requested for this gateway. This is optional and behavior can depend on the GatewayClass. If a value is set in the spec and the requested address is invalid, the GatewayClass MUST indicate this in the associated entry in GatewayStatus.Addresses. \n If no Addresses are specified, the GatewayClass may schedule the Gateway in an implementation-defined manner, assigning an appropriate set of Addresses. \n The GatewayClass MUST bind all Listeners to every GatewayAddress that it assigns to the Gateway. \n Support: Core" + items: + description: GatewayAddress describes an address that can be bound to a Gateway. + properties: + type: + default: IPAddress + description: "Type of the Address. This is either \"IPAddress\" or \"NamedAddress\". \n Support: Extended" + enum: + - IPAddress + - NamedAddress + type: string + value: + description: 'Value. Examples: "1.2.3.4", "128::1", "my-ip-address". Validity of the values will depend on `Type` and support by the controller.' + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + gatewayClassName: + description: GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + listeners: + description: "Listeners associated with this Gateway. Listeners define logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. \n An implementation MAY group Listeners by Port and then collapse each group of Listeners into a single Listener if the implementation determines that the Listeners in the group are \"compatible\". An implementation MAY also group together and collapse compatible Listeners belonging to different Gateways. \n For example, an implementation might consider Listeners to be compatible with each other if all of the following conditions are met: \n 1. Either each Listener within the group specifies the \"HTTP\" Protocol or each Listener within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. \n 2. Each Listener within the group specifies a Hostname that is unique within the group. \n 3. As a special case, one Listener within a group may omit Hostname, in which case this Listener matches when no other Listener matches. \n If the implementation does collapse compatible Listeners, the hostname provided in the incoming client request MUST be matched to a Listener to find the correct set of Routes. The incoming hostname MUST be matched using the Hostname field for each Listener in order of most to least specific. That is, exact matches must be processed before wildcard matches. \n If this field specifies multiple Listeners that have the same Port value but are not compatible, the implementation must raise a \"Conflicted\" condition in the Listener status. \n Support: Core" + items: + description: Listener embodies the concept of a logical endpoint where a Gateway can accept network connections. Each listener in a Gateway must have a unique combination of Hostname, Port, and Protocol. This will be enforced by a validating webhook. + properties: + hostname: + description: "Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified or \"*\", all hostnames are matched. This field can be omitted for protocols that don't require hostname based matching. \n Hostname is the fully qualified domain name of a network host, as defined by RFC 3986. Note the following deviations from the \"host\" part of the URI as defined in the RFC: \n 1. IP literals are not allowed. 2. The `:` delimiter is not respected because ports are not allowed. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. \"*.example.com\"). The wildcard character '*' must appear by itself as the first DNS label and matches only a single label. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + port: + description: "Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: "Protocol specifies the network protocol this listener expects to receive. The GatewayClass MUST apply the Hostname match appropriately for each protocol: \n * For the \"TLS\" protocol, the Hostname match MUST be applied to the [SNI](https://tools.ietf.org/html/rfc6066#section-3) server name offered by the client. * For the \"HTTP\" protocol, the Hostname match MUST be applied to the host portion of the [effective request URI](https://tools.ietf.org/html/rfc7230#section-5.5) or the [:authority pseudo-header](https://tools.ietf.org/html/rfc7540#section-8.1.2.3) * For the \"HTTPS\" protocol, the Hostname match MUST be applied at both the TLS and HTTP protocol layers. \n Support: Core" + type: string + routes: + description: "Routes specifies a schema for associating routes with the Listener using selectors. A Route is a resource capable of servicing a request and allows a cluster operator to expose a cluster resource (i.e. Service) by externally-reachable URL, load-balance traffic and terminate SSL/TLS. Typically, a route is a \"HTTPRoute\" or \"TCPRoute\" in group \"networking.x-k8s.io\", however, an implementation may support other types of resources. \n The Routes selector MUST select a set of objects that are compatible with the application protocol specified in the Protocol field. \n Although a client request may technically match multiple route rules, only one rule may ultimately receive the request. Matching precedence MUST be determined in order of the following criteria: \n * The most specific match. For example, the most specific HTTPRoute match is determined by the longest matching combination of hostname and path. * The oldest Route based on creation timestamp. For example, a Route with a creation timestamp of \"2020-09-08 01:02:03\" is given precedence over a Route with a creation timestamp of \"2020-09-08 01:02:04\". * If everything else is equivalent, the Route appearing first in alphabetical order (namespace/name) should be given precedence. For example, foo/bar is given precedence over foo/baz. \n All valid portions of a Route selected by this field should be supported. Invalid portions of a Route can be ignored (sometimes that will mean the full Route). If a portion of a Route transitions from valid to invalid, support for that portion of the Route should be dropped to ensure consistency. For example, even if a filter specified by a Route is invalid, the rest of the Route should still be supported. \n Support: Core" + properties: + group: + default: networking.x-k8s.io + description: "Group is the group of the route resource to select. Omitting the value or specifying the empty string indicates the networking.x-k8s.io API group. For example, use the following to select an HTTPRoute: \n routes: kind: HTTPRoute \n Otherwise, if an alternative API group is desired, specify the desired group: \n routes: group: acme.io kind: FooRoute \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + kind: + description: "Kind is the kind of the route resource to select. \n Kind MUST correspond to kinds of routes that are compatible with the application protocol specified in the Listener's Protocol field. \n If an implementation does not support or recognize this resource type, it SHOULD raise a \"ConditionInvalidRoutes\" condition for the affected Listener. \n Support: Core" + type: string + namespaces: + default: + from: Same + description: "Namespaces indicates in which namespaces Routes should be selected for this Gateway. This is restricted to the namespace of this Gateway by default. \n Support: Core" + properties: + from: + description: "From indicates where Routes will be selected for this Gateway. Possible values are: * All: Routes in all namespaces may be used by this Gateway. * Selector: Routes in namespaces selected by the selector may be used by this Gateway. * Same: Only Routes in the same namespace may be used by this Gateway. \n Support: Core" + enum: + - All + - Selector + - Same + type: string + selector: + description: "Selector must be specified when From is set to \"Selector\". In that case, only Routes in Namespaces matching this Selector will be selected by this Gateway. This field is ignored for other values of \"From\". \n Support: Core" + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + type: object + selector: + description: "Selector specifies a set of route labels used for selecting routes to associate with the Gateway. If RouteSelector is defined, only routes matching the RouteSelector are associated with the Gateway. An empty RouteSelector matches all routes. \n Support: Core" + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + required: + - kind + type: object + tls: + description: "TLS is the TLS configuration for the Listener. This field is required if the Protocol field is \"HTTPS\" or \"TLS\" and ignored otherwise. \n The association of SNIs to Certificate defined in GatewayTLSConfig is defined based on the Hostname field for this listener. \n The GatewayClass MUST use the longest matching SNI out of all available certificates for any TLS handshake. \n Support: Core" + properties: + certificateRef: + description: 'CertificateRef is the reference to Kubernetes object that contain a TLS certificate and private key. This certificate MUST be used for TLS handshakes for the domain this GatewayTLSConfig is associated with. If an entry in this list omits or specifies the empty string for both the group and the resource, the resource defaults to "secrets". An implementation may support other resources (for example, resource "mycertificates" in group "networking.acme.io"). Support: Core (Kubernetes Secrets) Support: Implementation-specific (Other resource types)' + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + mode: + description: 'Mode defines the TLS behavior for the TLS session initiated by the client. There are two possible modes: - Terminate: The TLS session between the downstream client and the Gateway is terminated at the Gateway. - Passthrough: The TLS session is NOT terminated by the Gateway. This implies that the Gateway can''t decipher the TLS stream except for the ClientHello message of the TLS protocol. CertificateRef field is ignored in this mode.' + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + type: string + description: "Options are a list of key/value pairs to give extended options to the provider. \n There variation among providers as to how ciphersuites are expressed. If there is a common subset for expressing ciphers then it will make sense to loft that as a core API construct. \n Support: Implementation-specific." + type: object + routeOverride: + default: + certificate: Deny + description: "RouteOverride dictates if TLS settings can be configured via Routes or not. \n CertificateRef must be defined even if `routeOverride.certificate` is set to 'Allow' as it will be used as the default certificate for the listener." + properties: + certificate: + default: Deny + description: "Certificate dictates if TLS certificates can be configured via Routes. If set to 'Allow', a TLS certificate for a hostname defined in a Route takes precedence over the certificate defined in Gateway. \n Support: Core" + enum: + - Allow + - Deny + type: string + required: + - certificate + type: object + type: object + required: + - port + - protocol + - routes + type: object + maxItems: 64 + minItems: 1 + type: array + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: NotReconciled + status: "False" + type: Scheduled + description: GatewayStatus defines the observed state of Gateway. + properties: + addresses: + description: "Addresses lists the IP addresses that have actually been bound to the Gateway. These addresses may differ from the addresses in the Spec, e.g. if the Gateway automatically assigns an address from a reserved pool. \n These addresses should all be of type \"IPAddress\"." + items: + description: GatewayAddress describes an address that can be bound to a Gateway. + properties: + type: + default: IPAddress + description: "Type of the Address. This is either \"IPAddress\" or \"NamedAddress\". \n Support: Extended" + enum: + - IPAddress + - NamedAddress + type: string + value: + description: 'Value. Examples: "1.2.3.4", "128::1", "my-ip-address". Validity of the values will depend on `Type` and support by the controller.' + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: NotReconciled + status: "False" + type: Scheduled + description: "Conditions describe the current conditions of the Gateway. \n Implementations should prefer to express Gateway conditions using the `GatewayConditionType` and `GatewayConditionReason` constants so that operators and tools can converge on a common vocabulary to describe Gateway state. \n Known condition types are: \n * \"Scheduled\" * \"Ready\"" + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener port. + properties: + conditions: + description: Conditions describe the current condition of this listener. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + port: + description: Port is the unique Listener port value for which this message is reporting the status. If more than one Gateway Listener shares the same port value, this message reports the combined status of all such Listeners. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - conditions + - port + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - port + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} +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: httproutes.networking.x-k8s.io +spec: + group: networking.x-k8s.io + names: + kind: HTTPRoute + listKind: HTTPRouteList + plural: httproutes + singular: httproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: HTTPRoute is the Schema for the HTTPRoute 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: HTTPRouteSpec defines the desired state of HTTPRoute + properties: + gateways: + default: + allow: SameNamespace + description: Gateways defines which Gateways can use this Route. + properties: + allow: + default: SameNamespace + description: 'Allow indicates which Gateways will be allowed to use this route. Possible values are: * All: Gateways in any namespace can use this route. * FromList: Only Gateways specified in GatewayRefs may use this route. * SameNamespace: Only Gateways in the same namespace may use this route.' + enum: + - All + - FromList + - SameNamespace + type: string + gatewayRefs: + description: GatewayRefs must be specified when Allow is set to "FromList". In that case, only Gateways referenced in this list will be allowed to use this route. This field is ignored for other values of "Allow". + items: + description: GatewayReference identifies a Gateway in a specified namespace. + properties: + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - name + - namespace + type: object + type: array + type: object + hostnames: + description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. Hostname is the fully qualified domain name of a network host, as defined by RFC 3986. Note the following deviations from the \"host\" part of the URI as defined in the RFC: \n 1. IPs are not allowed. 2. The `:` delimiter is not respected because ports are not allowed. \n Incoming requests are matched against the hostnames before the HTTPRoute rules. If no hostname is specified, traffic is routed based on the HTTPRouteRules. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. \"*.example.com\"). The wildcard character '*' must appear by itself as the first DNS label and matches only a single label. You cannot have a wildcard label by itself (e.g. Host == \"*\"). Requests will be matched against the Host field in the following order: 1. If Host is precise, the request matches this rule if the http host header is equal to Host. 2. If Host is a wildcard, then the request matches this rule if the http host header is to equal to the suffix (removing the first label) of the wildcard rule. \n Support: Core" + items: + description: Hostname is used to specify a hostname that should be matched. + maxLength: 253 + minLength: 1 + type: string + maxItems: 16 + type: array + rules: + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: HTTPRouteRule defines semantics for matching an incoming HTTP request against a set of matching rules and executing an action (and optionally filters) on the request. + properties: + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. Specifying a core filter multiple times has unspecified or custom conformance. \n Support: core" + items: + description: 'HTTPRouteFilter defines additional processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express additional processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. TODO(hbagdi): re-render CRDs once controller-tools supports union tags: - https://github.com/kubernetes-sigs/controller-tools/pull/298 - https://github.com/kubernetes-sigs/controller-tools/issues/461' + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.acme.io\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + additionalProperties: + type: string + description: "Add adds the given header (name, value) to the request before the action. \n Input: GET /foo HTTP/1.1 \n Config: add: {\"my-header\": \"foo\"} \n Output: GET /foo HTTP/1.1 my-header: foo \n Support: Extended" + type: object + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of RemoveHeader is a list of HTTP header names. Note that the header names are case-insensitive [RFC-2616 4.2]. \n Input: GET /foo HTTP/1.1 My-Header1: ABC My-Header2: DEF My-Header2: GHI \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 My-Header2: DEF \n Support: Extended" + items: + type: string + maxItems: 16 + type: array + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. \n Support: Extended" + properties: + backendRef: + description: "BackendRef is a local object reference to mirror matched requests to. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the route must be dropped from the Gateway. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DroppedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + port: + description: Port specifies the destination port number to use for the backend referenced by the ServiceName or BackendRef field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + serviceName: + description: "ServiceName refers to the name of the Service to mirror matched requests to. When specified, this takes the place of BackendRef. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the route must be dropped from the Gateway. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DroppedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Core" + maxLength: 253 + type: string + required: + - port + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Custom: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior." + enum: + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + maxItems: 16 + type: array + forwardTo: + description: ForwardTo defines the backend(s) where matching requests should be sent. + items: + description: HTTPRouteForwardTo defines how a HTTPRoute should forward a request. + properties: + backendRef: + description: "BackendRef is a reference to a backend to forward matched requests to. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the route must be dropped from the Gateway. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DroppedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + filters: + description: "Filters defined at this-level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Custom (For broader support of filters, use the Filters field in HTTPRouteRule.)" + items: + description: 'HTTPRouteFilter defines additional processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express additional processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. TODO(hbagdi): re-render CRDs once controller-tools supports union tags: - https://github.com/kubernetes-sigs/controller-tools/pull/298 - https://github.com/kubernetes-sigs/controller-tools/issues/461' + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.acme.io\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + additionalProperties: + type: string + description: "Add adds the given header (name, value) to the request before the action. \n Input: GET /foo HTTP/1.1 \n Config: add: {\"my-header\": \"foo\"} \n Output: GET /foo HTTP/1.1 my-header: foo \n Support: Extended" + type: object + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of RemoveHeader is a list of HTTP header names. Note that the header names are case-insensitive [RFC-2616 4.2]. \n Input: GET /foo HTTP/1.1 My-Header1: ABC My-Header2: DEF My-Header2: GHI \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 My-Header2: DEF \n Support: Extended" + items: + type: string + maxItems: 16 + type: array + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. \n Support: Extended" + properties: + backendRef: + description: "BackendRef is a local object reference to mirror matched requests to. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the route must be dropped from the Gateway. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DroppedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + port: + description: Port specifies the destination port number to use for the backend referenced by the ServiceName or BackendRef field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + serviceName: + description: "ServiceName refers to the name of the Service to mirror matched requests to. When specified, this takes the place of BackendRef. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the route must be dropped from the Gateway. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DroppedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Core" + maxLength: 253 + type: string + required: + - port + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Custom: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior." + enum: + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + maxItems: 16 + type: array + port: + description: "Port specifies the destination port number to use for the backend referenced by the ServiceName or BackendRef field. \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + serviceName: + description: "ServiceName refers to the name of the Service to forward matched requests to. When specified, this takes the place of BackendRef. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. \n If the referent cannot be found, the route must be dropped from the Gateway. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DroppedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: Core" + maxLength: 253 + type: string + weight: + default: 1 + description: "Weight specifies the proportion of HTTP requests forwarded to the backend referenced by the ServiceName or BackendRef field. This is computed as weight/(sum of all weights in this ForwardTo list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support: Core" + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - port + type: object + maxItems: 4 + type: array + matches: + default: + - path: + type: Prefix + value: / + description: "Matches define 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. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: values: version: \"2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request should satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: \"2\"` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request." + items: + description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: \"1\"` header: \n ``` match: path: value: \"/foo\" headers: values: version: \"1\" ```" + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"match\" behavior. For example, resource \"myroutematcher\" in group \"networking.acme.io\". If the referent cannot be found, the route must be dropped from the Gateway. The controller should raise the \"ResolvedRefs\" condition on the Gateway with the \"DroppedRoutes\" reason. The gateway status for this route should be updated with a condition that describes the error more specifically. \n Support: custom" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + headers: + description: Headers specifies a HTTP request header matcher. + properties: + type: + default: Exact + description: "Type specifies how to match against the value of the header. \n Support: core (Exact) Support: custom (RegularExpression, ImplementationSpecific) \n Since RegularExpression PathType has custom conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + - ImplementationSpecific + type: string + values: + additionalProperties: + type: string + description: "Values is a map of HTTP Headers to be matched. It MUST contain at least one entry. \n The HTTP header field name to match is the map key, and the value of the HTTP header is the map value. HTTP header field names MUST be matched case-insensitively. \n Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route." + type: object + required: + - values + type: object + path: + default: + type: Prefix + value: / + description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. + properties: + type: + default: Prefix + description: "Type specifies how to match against the path Value. \n Support: core (Exact, Prefix) Support: custom (RegularExpression, ImplementationSpecific) \n Since RegularExpression PathType has custom conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - Prefix + - RegularExpression + - ImplementationSpecific + type: string + value: + description: Value of the HTTP path to match against. + minLength: 1 + type: string + required: + - value + type: object + type: object + maxItems: 8 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + tls: + description: "TLS defines the TLS certificate to use for Hostnames defined in this Route. This configuration only takes effect if the AllowRouteOverride field is set to true in the associated Gateway resource. \n Collisions can happen if multiple HTTPRoutes define a TLS certificate for the same hostname. In such a case, conflict resolution guiding principles apply, specificallly, if hostnames are same and two different certificates are specified then the certificate in the oldest resource wins. \n Please note that HTTP Route-selection takes place after the TLS Handshake (ClientHello). Due to this, TLS certificate defined here will take precedence even if the request has the potential to match multiple routes (in case multiple HTTPRoutes share the same hostname). \n Support: Core" + properties: + certificateRef: + description: 'CertificateRef refers to a Kubernetes object that contains a TLS certificate and private key. This certificate MUST be used for TLS handshakes for the domain this RouteTLSConfig is associated with. If an entry in this list omits or specifies the empty string for both the group and the resource, the resource defaults to "secrets". An implementation may support other resources (for example, resource "mycertificates" in group "networking.acme.io"). Support: Core (Kubernetes Secrets) Support: Implementation-specific (Other resource types)' + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the referent. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + required: + - certificateRef + type: object + required: + - rules + type: object + status: + description: HTTPRouteStatus defines the observed state of HTTPRoute. + properties: + gateways: + description: "Gateways is a list of the Gateways that are associated with the route, and the status of the route with respect to each of these Gateways. When a Gateway selects this route, the controller that manages the Gateway should add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route is modified. \n A maximum of 100 Gateways will be represented in this list. If this list is full, there may be additional Gateways using this Route that are not included in the list." + items: + description: RouteGatewayStatus describes the status of a route with respect to an associated Gateway. + properties: + conditions: + description: Conditions describes the status of the route with respect to the Gateway. For example, the "Admitted" condition indicates whether the route has been admitted or rejected by the Gateway, and why. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + gatewayRef: + description: GatewayRef is a reference to a Gateway object that is associated with the route. + properties: + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - name + - namespace + type: object + required: + - gatewayRef + type: object + maxItems: 100 + type: array + required: + - gateways + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/integration/fixtures/k8s/03-gateway.yml b/integration/fixtures/k8s/03-gateway.yml new file mode 100644 index 000000000..45820232c --- /dev/null +++ b/integration/fixtures/k8s/03-gateway.yml @@ -0,0 +1,42 @@ +--- +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: + - protocol: HTTP + port: 8180 + routes: + kind: HTTPRoute + +--- +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: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/integration/fixtures/k8s_gateway.toml b/integration/fixtures/k8s_gateway.toml new file mode 100644 index 000000000..93a0b391d --- /dev/null +++ b/integration/fixtures/k8s_gateway.toml @@ -0,0 +1,22 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + +[api] + insecure = true + +[experimental] + kubernetesGateway = true + +[entryPoints] + [entryPoints.footcp] + address = ":8193" + [entryPoints.fooudp] + address = ":8190/udp" + [entryPoints.web] + address = ":8180" + +[providers.kubernetesGateway] diff --git a/integration/k8s_test.go b/integration/k8s_test.go index 728e326e0..b5f2b2093 100644 --- a/integration/k8s_test.go +++ b/integration/k8s_test.go @@ -108,6 +108,17 @@ func (s *K8sSuite) TestCRDLabelSelector(c *check.C) { testConfiguration(c, "testdata/rawdata-crd-label-selector.json", "8000") } +func (s *K8sSuite) TestGatewayConfiguration(c *check.C) { + cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_gateway.toml")) + defer display(c) + + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer s.killCmd(cmd) + + testConfiguration(c, "testdata/rawdata-gateway.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/testdata/rawdata-gateway.json b/integration/testdata/rawdata-gateway.json new file mode 100644 index 000000000..7615f12fb --- /dev/null +++ b/integration/testdata/rawdata-gateway.json @@ -0,0 +1,117 @@ +{ + "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" + ] + }, + "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06@kubernetesgateway": { + "entryPoints": [ + "web" + ], + "service": "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr", + "rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)", + "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-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr@kubernetesgateway": { + "weighted": { + "services": [ + { + "name": "default-whoami-80", + "weight": 1 + } + ] + }, + "status": "enabled", + "usedBy": [ + "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06@kubernetesgateway" + ] + }, + "default-whoami-80@kubernetesgateway": { + "loadBalancer": { + "servers": [ + { + "url": "http://10.42.0.3:80" + }, + { + "url": "http://10.42.0.7:80" + } + ], + "passHostHeader": true + }, + "status": "enabled", + "serverStatus": { + "http://10.42.0.3:80": "UP", + "http://10.42.0.7:80": "UP" + } + }, + "noop@internal": { + "status": "enabled" + } + } +} \ No newline at end of file diff --git a/pkg/anonymize/anonymize_config_test.go b/pkg/anonymize/anonymize_config_test.go index 98877ac2d..4c3b67d4c 100644 --- a/pkg/anonymize/anonymize_config_test.go +++ b/pkg/anonymize/anonymize_config_test.go @@ -23,6 +23,7 @@ import ( "github.com/traefik/traefik/v2/pkg/provider/file" "github.com/traefik/traefik/v2/pkg/provider/http" "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd" + "github.com/traefik/traefik/v2/pkg/provider/kubernetes/gateway" "github.com/traefik/traefik/v2/pkg/provider/kubernetes/ingress" "github.com/traefik/traefik/v2/pkg/provider/kv" "github.com/traefik/traefik/v2/pkg/provider/kv/consul" @@ -607,13 +608,12 @@ func TestDo_staticConfiguration(t *testing.T) { } config.Providers.KubernetesIngress = &ingress.Provider{ - Endpoint: "MyEndpoint", - Token: "MyToken", - CertAuthFilePath: "MyCertAuthPath", - DisablePassHostHeaders: true, - Namespaces: []string{"a", "b"}, - LabelSelector: "myLabelSelector", - IngressClass: "MyIngressClass", + Endpoint: "MyEndpoint", + Token: "MyToken", + CertAuthFilePath: "MyCertAuthPath", + Namespaces: []string{"a", "b"}, + LabelSelector: "myLabelSelector", + IngressClass: "MyIngressClass", IngressEndpoint: &ingress.EndpointIngress{ IP: "IP", Hostname: "Hostname", @@ -623,14 +623,22 @@ func TestDo_staticConfiguration(t *testing.T) { } config.Providers.KubernetesCRD = &crd.Provider{ - Endpoint: "MyEndpoint", - Token: "MyToken", - CertAuthFilePath: "MyCertAuthPath", - DisablePassHostHeaders: true, - Namespaces: []string{"a", "b"}, - LabelSelector: "myLabelSelector", - IngressClass: "MyIngressClass", - ThrottleDuration: ptypes.Duration(111 * time.Second), + Endpoint: "MyEndpoint", + Token: "MyToken", + CertAuthFilePath: "MyCertAuthPath", + Namespaces: []string{"a", "b"}, + LabelSelector: "myLabelSelector", + IngressClass: "MyIngressClass", + ThrottleDuration: ptypes.Duration(111 * time.Second), + } + + config.Providers.KubernetesGateway = &gateway.Provider{ + Endpoint: "MyEndpoint", + Token: "MyToken", + CertAuthFilePath: "MyCertAuthPath", + Namespaces: []string{"a", "b"}, + LabelSelector: "myLabelSelector", + ThrottleDuration: ptypes.Duration(111 * time.Second), } config.Providers.Rest = &rest.Provider{ diff --git a/pkg/anonymize/testdata/anonymized-static-config.json b/pkg/anonymize/testdata/anonymized-static-config.json index adc729042..a6ccb1727 100644 --- a/pkg/anonymize/testdata/anonymized-static-config.json +++ b/pkg/anonymize/testdata/anonymized-static-config.json @@ -131,7 +131,6 @@ "endpoint": "xxxx", "token": "xxxx", "certAuthFilePath": "xxxx", - "disablePassHostHeaders": true, "namespaces": [ "a", "b" @@ -149,7 +148,6 @@ "endpoint": "xxxx", "token": "xxxx", "certAuthFilePath": "xxxx", - "disablePassHostHeaders": true, "namespaces": [ "a", "b" @@ -158,6 +156,17 @@ "ingressClass": "MyIngressClass", "throttleDuration": 111000000000 }, + "kubernetesGateway": { + "endpoint": "xxxx", + "token": "xxxx", + "certAuthFilePath": "xxxx", + "namespaces": [ + "a", + "b" + ], + "labelSelector": "myLabelSelector", + "throttleDuration": 111000000000 + }, "rest": { "insecure": true }, diff --git a/pkg/config/dynamic/fixtures/sample.toml b/pkg/config/dynamic/fixtures/sample.toml index ff3f7735e..c5f22514a 100644 --- a/pkg/config/dynamic/fixtures/sample.toml +++ b/pkg/config/dynamic/fixtures/sample.toml @@ -80,7 +80,6 @@ endpoint = "foobar" token = "foobar" certAuthFilePath = "foobar" - disablePassHostHeaders = true namespaces = ["foobar", "foobar"] labelSelector = "foobar" ingressClass = "foobar" @@ -92,7 +91,6 @@ endpoint = "foobar" token = "foobar" certAuthFilePath = "foobar" - disablePassHostHeaders = true namespaces = ["foobar", "foobar"] labelSelector = "foobar" ingressClass = "foobar" diff --git a/pkg/config/static/experimental.go b/pkg/config/static/experimental.go index 5715f1e91..fbb91fa9f 100644 --- a/pkg/config/static/experimental.go +++ b/pkg/config/static/experimental.go @@ -4,6 +4,7 @@ import "github.com/traefik/traefik/v2/pkg/plugins" // Experimental experimental Traefik features. 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"` + 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"` } diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index 66bf4e684..048b87ec7 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -19,6 +19,7 @@ import ( "github.com/traefik/traefik/v2/pkg/provider/file" "github.com/traefik/traefik/v2/pkg/provider/http" "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd" + "github.com/traefik/traefik/v2/pkg/provider/kubernetes/gateway" "github.com/traefik/traefik/v2/pkg/provider/kubernetes/ingress" "github.com/traefik/traefik/v2/pkg/provider/kv/consul" "github.com/traefik/traefik/v2/pkg/provider/kv/etcd" @@ -174,6 +175,7 @@ type Providers struct { Marathon *marathon.Provider `description:"Enable Marathon backend with default settings." json:"marathon,omitempty" toml:"marathon,omitempty" yaml:"marathon,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` KubernetesIngress *ingress.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + KubernetesGateway *gateway.Provider `description:"Enable Kubernetes gateway api provider with default settings." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` Rest *rest.Provider `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` Rancher *rancher.Provider `description:"Enable Rancher backend with default settings." json:"rancher,omitempty" toml:"rancher,omitempty" yaml:"rancher,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` ConsulCatalog *consulcatalog.Provider `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` @@ -229,6 +231,22 @@ func (c *Configuration) SetEffectiveConfiguration() { c.Global.SendAnonymousUsage = true } + // Disable Gateway API provider if not enabled in experimental + if c.Experimental == nil || !c.Experimental.KubernetesGateway { + c.Providers.KubernetesGateway = nil + } + + // Configure Gateway API provider + if c.Providers.KubernetesGateway != nil { + log.WithoutContext().Debugf("Experimental Kubernetes Gateway provider has been activated") + entryPoints := make(map[string]gateway.Entrypoint) + for epName, entryPoint := range c.EntryPoints { + entryPoints[epName] = gateway.Entrypoint{Address: entryPoint.GetAddress(), HasHTTPTLSConf: entryPoint.HTTP.TLS != nil} + } + + c.Providers.KubernetesGateway.EntryPoints = entryPoints + } + c.initACMEProvider() } diff --git a/pkg/provider/aggregator/aggregator.go b/pkg/provider/aggregator/aggregator.go index c74e880f6..9632af47f 100644 --- a/pkg/provider/aggregator/aggregator.go +++ b/pkg/provider/aggregator/aggregator.go @@ -45,6 +45,10 @@ func NewProviderAggregator(conf static.Providers) ProviderAggregator { p.quietAddProvider(conf.KubernetesCRD) } + if conf.KubernetesGateway != nil { + p.quietAddProvider(conf.KubernetesGateway) + } + if conf.Rancher != nil { p.quietAddProvider(conf.Rancher) } diff --git a/pkg/provider/configuration.go b/pkg/provider/configuration.go index 32cfd9b1b..972111f91 100644 --- a/pkg/provider/configuration.go +++ b/pkg/provider/configuration.go @@ -169,10 +169,30 @@ func AddServiceTCP(configuration *dynamic.TCPConfiguration, serviceName string, return false } - configuration.Services[serviceName].LoadBalancer.Servers = append(configuration.Services[serviceName].LoadBalancer.Servers, service.LoadBalancer.Servers...) + uniq := map[string]struct{}{} + for _, server := range configuration.Services[serviceName].LoadBalancer.Servers { + uniq[server.Address] = struct{}{} + } + + for _, server := range service.LoadBalancer.Servers { + if _, ok := uniq[server.Address]; !ok { + configuration.Services[serviceName].LoadBalancer.Servers = append(configuration.Services[serviceName].LoadBalancer.Servers, server) + } + } + return true } +// AddRouterTCP Adds a router to a configurations. +func AddRouterTCP(configuration *dynamic.TCPConfiguration, routerName string, router *dynamic.TCPRouter) bool { + if _, ok := configuration.Routers[routerName]; !ok { + configuration.Routers[routerName] = router + return true + } + + return reflect.DeepEqual(configuration.Routers[routerName], router) +} + // AddServiceUDP adds a service to a configuration. func AddServiceUDP(configuration *dynamic.UDPConfiguration, serviceName string, service *dynamic.UDPService) bool { if _, ok := configuration.Services[serviceName]; !ok { @@ -184,18 +204,18 @@ func AddServiceUDP(configuration *dynamic.UDPConfiguration, serviceName string, return false } - configuration.Services[serviceName].LoadBalancer.Servers = append(configuration.Services[serviceName].LoadBalancer.Servers, service.LoadBalancer.Servers...) - return true -} - -// AddRouterTCP Adds a router to a configurations. -func AddRouterTCP(configuration *dynamic.TCPConfiguration, routerName string, router *dynamic.TCPRouter) bool { - if _, ok := configuration.Routers[routerName]; !ok { - configuration.Routers[routerName] = router - return true + uniq := map[string]struct{}{} + for _, server := range configuration.Services[serviceName].LoadBalancer.Servers { + uniq[server.Address] = struct{}{} } - return reflect.DeepEqual(configuration.Routers[routerName], router) + for _, server := range service.LoadBalancer.Servers { + if _, ok := uniq[server.Address]; !ok { + configuration.Services[serviceName].LoadBalancer.Servers = append(configuration.Services[serviceName].LoadBalancer.Servers, server) + } + } + + return true } // AddRouterUDP adds a router to a configuration. @@ -219,7 +239,17 @@ func AddService(configuration *dynamic.HTTPConfiguration, serviceName string, se return false } - configuration.Services[serviceName].LoadBalancer.Servers = append(configuration.Services[serviceName].LoadBalancer.Servers, service.LoadBalancer.Servers...) + uniq := map[string]struct{}{} + for _, server := range configuration.Services[serviceName].LoadBalancer.Servers { + uniq[server.URL] = struct{}{} + } + + for _, server := range service.LoadBalancer.Servers { + if _, ok := uniq[server.URL]; !ok { + configuration.Services[serviceName].LoadBalancer.Servers = append(configuration.Services[serviceName].LoadBalancer.Servers, server) + } + } + return true } diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 6082d7fe9..ccda4a173 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -38,16 +38,15 @@ const ( // Provider holds configurations of the provider. type Provider struct { - Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` - Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"` - CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"` - DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers." json:"disablePassHostHeaders,omitempty" toml:"disablePassHostHeaders,omitempty" yaml:"disablePassHostHeaders,omitempty" export:"true"` - Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"` - AllowCrossNamespace *bool `description:"Allow cross namespace resource reference." json:"allowCrossNamespace,omitempty" toml:"allowCrossNamespace,omitempty" yaml:"allowCrossNamespace,omitempty" export:"true"` - LabelSelector string `description:"Kubernetes 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"` - ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` - lastConfiguration safe.Safe + Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` + Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"` + 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"` + AllowCrossNamespace *bool `description:"Allow cross namespace resource reference." json:"allowCrossNamespace,omitempty" toml:"allowCrossNamespace,omitempty" yaml:"allowCrossNamespace,omitempty" export:"true"` + LabelSelector string `description:"Kubernetes 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"` + ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` + lastConfiguration safe.Safe } // SetDefaults sets the default values. diff --git a/pkg/provider/kubernetes/gateway/client.go b/pkg/provider/kubernetes/gateway/client.go new file mode 100644 index 000000000..1e42194c1 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/client.go @@ -0,0 +1,443 @@ +package gateway + +import ( + "context" + "errors" + "fmt" + "io/ioutil" + "time" + + "github.com/traefik/traefik/v2/pkg/log" + corev1 "k8s.io/api/core/v1" + kubeerror "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "sigs.k8s.io/service-apis/apis/v1alpha1" + "sigs.k8s.io/service-apis/pkg/client/clientset/versioned" + "sigs.k8s.io/service-apis/pkg/client/informers/externalversions" +) + +const resyncPeriod = 10 * time.Minute + +type resourceEventHandler struct { + ev chan<- interface{} +} + +func (reh *resourceEventHandler) OnAdd(obj interface{}) { + eventHandlerFunc(reh.ev, obj) +} + +func (reh *resourceEventHandler) OnUpdate(oldObj, newObj interface{}) { + switch oldObj.(type) { + case *v1alpha1.GatewayClass: + // Skip update for gateway classes. We only manage addition or deletion for this cluster-wide resource. + return + default: + eventHandlerFunc(reh.ev, newObj) + } +} + +func (reh *resourceEventHandler) OnDelete(obj interface{}) { + eventHandlerFunc(reh.ev, obj) +} + +// Client is a client for the Provider master. +// WatchAll starts the watch of the Provider resources and updates the stores. +// The stores can then be accessed via the Get* functions. +type Client interface { + WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) + + GetGatewayClasses() ([]*v1alpha1.GatewayClass, error) + UpdateGatewayStatus(gateway *v1alpha1.Gateway, gatewayStatus v1alpha1.GatewayStatus) error + UpdateGatewayClassStatus(gatewayClass *v1alpha1.GatewayClass, condition metav1.Condition) error + GetGateways() []*v1alpha1.Gateway + GetHTTPRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.HTTPRoute, 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) +} + +type clientWrapper struct { + csGateway versioned.Interface + csKube kubernetes.Interface + + factoryGatewayClass externalversions.SharedInformerFactory + factoriesGateway map[string]externalversions.SharedInformerFactory + factoriesKube map[string]informers.SharedInformerFactory + factoriesSecret map[string]informers.SharedInformerFactory + + isNamespaceAll bool + watchedNamespaces []string + + labelSelector string +} + +func createClientFromConfig(c *rest.Config) (*clientWrapper, error) { + csGateway, err := versioned.NewForConfig(c) + if err != nil { + return nil, err + } + + csKube, err := kubernetes.NewForConfig(c) + if err != nil { + return nil, err + } + + return newClientImpl(csKube, csGateway), nil +} + +func newClientImpl(csKube kubernetes.Interface, csGateway versioned.Interface) *clientWrapper { + return &clientWrapper{ + csGateway: csGateway, + csKube: csKube, + factoriesGateway: make(map[string]externalversions.SharedInformerFactory), + factoriesKube: make(map[string]informers.SharedInformerFactory), + factoriesSecret: make(map[string]informers.SharedInformerFactory), + } +} + +// newInClusterClient returns a new Provider client that is expected to run +// inside the cluster. +func newInClusterClient(endpoint string) (*clientWrapper, error) { + config, err := rest.InClusterConfig() + if err != nil { + return nil, fmt.Errorf("failed to create in-cluster configuration: %w", err) + } + + if endpoint != "" { + config.Host = endpoint + } + + return createClientFromConfig(config) +} + +func newExternalClusterClientFromFile(file string) (*clientWrapper, error) { + configFromFlags, err := clientcmd.BuildConfigFromFlags("", file) + if err != nil { + return nil, err + } + return createClientFromConfig(configFromFlags) +} + +// newExternalClusterClient returns a new Provider client that may run outside +// of the cluster. +// The endpoint parameter must not be empty. +func newExternalClusterClient(endpoint, token, caFilePath string) (*clientWrapper, error) { + if endpoint == "" { + return nil, errors.New("endpoint missing for external cluster client") + } + + config := &rest.Config{ + Host: endpoint, + BearerToken: token, + } + + if caFilePath != "" { + caData, err := ioutil.ReadFile(caFilePath) + if err != nil { + return nil, fmt.Errorf("failed to read CA file %s: %w", caFilePath, err) + } + + config.TLSClientConfig = rest.TLSClientConfig{CAData: caData} + } + + return createClientFromConfig(config) +} + +// WatchAll starts namespace-specific controllers for all relevant kinds. +func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) { + eventCh := make(chan interface{}, 1) + eventHandler := &resourceEventHandler{ev: eventCh} + + if len(namespaces) == 0 { + namespaces = []string{metav1.NamespaceAll} + c.isNamespaceAll = true + } + + c.watchedNamespaces = namespaces + + notOwnedByHelm := func(opts *metav1.ListOptions) { + opts.LabelSelector = "owner!=helm" + } + + labelSelectorOptions := func(options *metav1.ListOptions) { + options.LabelSelector = c.labelSelector + } + + c.factoryGatewayClass = externalversions.NewSharedInformerFactoryWithOptions(c.csGateway, resyncPeriod, externalversions.WithTweakListOptions(labelSelectorOptions)) + c.factoryGatewayClass.Networking().V1alpha1().GatewayClasses().Informer().AddEventHandler(eventHandler) + + for _, ns := range namespaces { + factoryGateway := externalversions.NewSharedInformerFactoryWithOptions(c.csGateway, resyncPeriod, externalversions.WithNamespace(ns)) + factoryGateway.Networking().V1alpha1().Gateways().Informer().AddEventHandler(eventHandler) + factoryGateway.Networking().V1alpha1().HTTPRoutes().Informer().AddEventHandler(eventHandler) + + factoryKube := informers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, informers.WithNamespace(ns)) + factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler) + factoryKube.Core().V1().Endpoints().Informer().AddEventHandler(eventHandler) + + factorySecret := informers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, informers.WithNamespace(ns), informers.WithTweakListOptions(notOwnedByHelm)) + factorySecret.Core().V1().Secrets().Informer().AddEventHandler(eventHandler) + + c.factoriesGateway[ns] = factoryGateway + c.factoriesKube[ns] = factoryKube + c.factoriesSecret[ns] = factorySecret + } + + c.factoryGatewayClass.Start(stopCh) + + for _, ns := range namespaces { + c.factoriesGateway[ns].Start(stopCh) + c.factoriesKube[ns].Start(stopCh) + c.factoriesSecret[ns].Start(stopCh) + } + + for t, ok := range c.factoryGatewayClass.WaitForCacheSync(stopCh) { + if !ok { + return nil, fmt.Errorf("timed out waiting for controller caches to sync %s", t.String()) + } + } + + for _, ns := range namespaces { + for t, ok := range c.factoriesGateway[ns].WaitForCacheSync(stopCh) { + if !ok { + return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", t.String(), ns) + } + } + + for t, ok := range c.factoriesKube[ns].WaitForCacheSync(stopCh) { + if !ok { + return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", t.String(), ns) + } + } + + for t, ok := range c.factoriesSecret[ns].WaitForCacheSync(stopCh) { + if !ok { + return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", t.String(), ns) + } + } + } + + return eventCh, nil +} + +func (c *clientWrapper) GetHTTPRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.HTTPRoute, error) { + if !c.isWatchedNamespace(namespace) { + return nil, fmt.Errorf("failed to get HTTPRoute %s with labels selector %s: namespace is not within watched namespaces", namespace, selector) + } + httpRoutes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Networking().V1alpha1().HTTPRoutes().Lister().HTTPRoutes(namespace).List(selector) + if err != nil { + return nil, err + } + + if len(httpRoutes) == 0 { + return nil, fmt.Errorf("failed to get HTTPRoute %s with labels selector %s: namespace is not within watched namespaces", namespace, selector) + } + + return httpRoutes, nil +} + +func (c *clientWrapper) GetGateways() []*v1alpha1.Gateway { + var result []*v1alpha1.Gateway + + for ns, factory := range c.factoriesGateway { + gateways, err := factory.Networking().V1alpha1().Gateways().Lister().List(labels.Everything()) + if err != nil { + log.WithoutContext().Errorf("Failed to list Gateways in namespace %s: %v", ns, err) + continue + } + result = append(result, gateways...) + } + + return result +} + +func (c *clientWrapper) GetGatewayClasses() ([]*v1alpha1.GatewayClass, error) { + return c.factoryGatewayClass.Networking().V1alpha1().GatewayClasses().Lister().List(labels.Everything()) +} + +func (c *clientWrapper) UpdateGatewayClassStatus(gatewayClass *v1alpha1.GatewayClass, condition metav1.Condition) error { + gc := gatewayClass.DeepCopy() + + var newConditions []metav1.Condition + for _, cond := range gc.Status.Conditions { + // No update for identical condition. + if cond.Type == condition.Type && cond.Status == condition.Status { + return nil + } + + // Keep other condition types. + if cond.Type != condition.Type { + newConditions = append(newConditions, cond) + } + } + + // Append the condition to update. + newConditions = append(newConditions, condition) + gc.Status.Conditions = newConditions + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + _, err := c.csGateway.NetworkingV1alpha1().GatewayClasses().UpdateStatus(ctx, gc, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("failed to update GatewayClass %q status: %w", gatewayClass.Name, err) + } + + return nil +} + +func (c *clientWrapper) UpdateGatewayStatus(gateway *v1alpha1.Gateway, gatewayStatus v1alpha1.GatewayStatus) error { + if !c.isWatchedNamespace(gateway.Namespace) { + return fmt.Errorf("cannot update Gateway status %s/%s: namespace is not within watched namespaces", gateway.Namespace, gateway.Name) + } + + if statusEquals(gateway.Status, gatewayStatus) { + return nil + } + + g := gateway.DeepCopy() + g.Status = gatewayStatus + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + _, err := c.csGateway.NetworkingV1alpha1().Gateways(gateway.Namespace).UpdateStatus(ctx, g, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("failed to update Gateway %q status: %w", gateway.Name, err) + } + + return nil +} + +func statusEquals(oldStatus, newStatus v1alpha1.GatewayStatus) bool { + if len(oldStatus.Listeners) != len(newStatus.Listeners) { + return false + } + + if !conditionsEquals(oldStatus.Conditions, newStatus.Conditions) { + return false + } + + listenerMatches := 0 + for _, newListener := range newStatus.Listeners { + for _, oldListener := range oldStatus.Listeners { + if newListener.Port == oldListener.Port { + if !conditionsEquals(newListener.Conditions, oldListener.Conditions) { + return false + } + + listenerMatches++ + } + } + } + + return listenerMatches == len(oldStatus.Listeners) +} + +func conditionsEquals(conditionsA, conditionsB []metav1.Condition) bool { + if len(conditionsA) != len(conditionsB) { + return false + } + + conditionMatches := 0 + for _, conditionA := range conditionsA { + for _, conditionB := range conditionsB { + if conditionA.Type == conditionB.Type { + if conditionA.Reason != conditionB.Reason || conditionA.Status != conditionB.Status || conditionA.Message != conditionB.Message { + return false + } + conditionMatches++ + } + } + } + + return conditionMatches == len(conditionsA) +} + +// GetService returns the named service from the given namespace. +func (c *clientWrapper) GetService(namespace, name string) (*corev1.Service, bool, error) { + if !c.isWatchedNamespace(namespace) { + return nil, false, fmt.Errorf("failed to get service %s/%s: namespace is not within watched namespaces", namespace, name) + } + + service, err := c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().Services().Lister().Services(namespace).Get(name) + exist, err := translateNotFoundError(err) + + return service, exist, err +} + +// GetEndpoints returns the named endpoints from the given namespace. +func (c *clientWrapper) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) { + if !c.isWatchedNamespace(namespace) { + return nil, false, fmt.Errorf("failed to get endpoints %s/%s: namespace is not within watched namespaces", namespace, name) + } + + endpoint, err := c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().Endpoints().Lister().Endpoints(namespace).Get(name) + exist, err := translateNotFoundError(err) + + return endpoint, exist, err +} + +// GetSecret returns the named secret from the given namespace. +func (c *clientWrapper) GetSecret(namespace, name string) (*corev1.Secret, bool, error) { + if !c.isWatchedNamespace(namespace) { + return nil, false, fmt.Errorf("failed to get secret %s/%s: namespace is not within watched namespaces", namespace, name) + } + + secret, err := c.factoriesSecret[c.lookupNamespace(namespace)].Core().V1().Secrets().Lister().Secrets(namespace).Get(name) + exist, err := translateNotFoundError(err) + + return secret, exist, err +} + +// lookupNamespace returns the lookup namespace key for the given namespace. +// When listening on all namespaces, it returns the client-go identifier ("") +// for all-namespaces. Otherwise, it returns the given namespace. +// The distinction is necessary because we index all informers on the special +// identifier iff all-namespaces are requested but receive specific namespace +// identifiers from the Kubernetes API, so we have to bridge this gap. +func (c *clientWrapper) lookupNamespace(ns string) string { + if c.isNamespaceAll { + return metav1.NamespaceAll + } + return ns +} + +// eventHandlerFunc will pass the obj on to the events channel or drop it. +// This is so passing the events along won't block in the case of high volume. +// The events are only used for signaling anyway so dropping a few is ok. +func eventHandlerFunc(events chan<- interface{}, obj interface{}) { + select { + case events <- obj: + default: + } +} + +// translateNotFoundError will translate a "not found" error to a boolean return +// value which indicates if the resource exists and a nil error. +func translateNotFoundError(err error) (bool, error) { + if kubeerror.IsNotFound(err) { + return false, nil + } + return err == nil, err +} + +// isWatchedNamespace checks to ensure that the namespace is being watched before we request +// it to ensure we don't panic by requesting an out-of-watch object. +func (c *clientWrapper) isWatchedNamespace(ns string) bool { + if c.isNamespaceAll { + return true + } + for _, watchedNamespace := range c.watchedNamespaces { + if watchedNamespace == ns { + return true + } + } + return false +} diff --git a/pkg/provider/kubernetes/gateway/client_mock_test.go b/pkg/provider/kubernetes/gateway/client_mock_test.go new file mode 100644 index 000000000..73b7d1eb2 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/client_mock_test.go @@ -0,0 +1,181 @@ +package gateway + +import ( + "fmt" + "io/ioutil" + "path/filepath" + + "github.com/traefik/traefik/v2/pkg/provider/kubernetes/k8s" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/service-apis/apis/v1alpha1" +) + +var _ Client = (*clientMock)(nil) + +func init() { + // required by k8s.MustParseYaml + err := v1alpha1.AddToScheme(scheme.Scheme) + if err != nil { + panic(err) + } +} + +type clientMock struct { + services []*corev1.Service + secrets []*corev1.Secret + endpoints []*corev1.Endpoints + + apiServiceError error + apiSecretError error + apiEndpointsError error + + gatewayClasses []*v1alpha1.GatewayClass + gateways []*v1alpha1.Gateway + httpRoutes []*v1alpha1.HTTPRoute + + watchChan chan interface{} +} + +func newClientMock(paths ...string) clientMock { + var c clientMock + + for _, path := range paths { + yamlContent, err := ioutil.ReadFile(filepath.FromSlash("./fixtures/" + path)) + if err != nil { + panic(err) + } + + k8sObjects := k8s.MustParseYaml(yamlContent) + for _, obj := range k8sObjects { + switch o := obj.(type) { + case *corev1.Service: + c.services = append(c.services, o) + case *corev1.Secret: + c.secrets = append(c.secrets, o) + case *corev1.Endpoints: + c.endpoints = append(c.endpoints, o) + case *v1alpha1.GatewayClass: + c.gatewayClasses = append(c.gatewayClasses, o) + case *v1alpha1.Gateway: + c.gateways = append(c.gateways, o) + case *v1alpha1.HTTPRoute: + c.httpRoutes = append(c.httpRoutes, o) + default: + panic(fmt.Sprintf("Unknown runtime object %+v %T", o, o)) + } + } + } + + return c +} + +func (c clientMock) UpdateGatewayStatus(gateway *v1alpha1.Gateway, gatewayStatus v1alpha1.GatewayStatus) error { + for _, g := range c.gateways { + if g.Name == gateway.Name { + if !statusEquals(g.Status, gatewayStatus) { + g.Status = gatewayStatus + return nil + } + return fmt.Errorf("cannot update gateway %v", gateway.Name) + } + } + return nil +} + +func (c clientMock) UpdateGatewayClassStatus(gatewayClass *v1alpha1.GatewayClass, condition metav1.Condition) error { + for _, gc := range c.gatewayClasses { + if gc.Name == gatewayClass.Name { + for _, c := range gc.Status.Conditions { + if c.Type == condition.Type && c.Status != condition.Status { + c.Status = condition.Status + c.LastTransitionTime = condition.LastTransitionTime + c.Message = condition.Message + c.Reason = condition.Reason + } + } + } + } + return nil +} + +func (c clientMock) UpdateGatewayStatusConditions(gateway *v1alpha1.Gateway, condition metav1.Condition) error { + for _, g := range c.gatewayClasses { + if g.Name == gateway.Name { + for _, c := range g.Status.Conditions { + if c.Type == condition.Type && (c.Status != condition.Status || c.Reason != condition.Reason) { + c.Status = condition.Status + c.LastTransitionTime = condition.LastTransitionTime + c.Message = condition.Message + c.Reason = condition.Reason + } + } + } + } + return nil +} + +func (c clientMock) GetGatewayClasses() ([]*v1alpha1.GatewayClass, error) { + return c.gatewayClasses, nil +} + +func (c clientMock) GetGateways() []*v1alpha1.Gateway { + return c.gateways +} + +func (c clientMock) GetHTTPRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.HTTPRoute, error) { + httpRoutes := make([]*v1alpha1.HTTPRoute, len(c.httpRoutes)) + + for _, httpRoute := range c.httpRoutes { + if httpRoute.Namespace == namespace && selector.Matches(labels.Set(httpRoute.Labels)) { + httpRoutes = append(httpRoutes, httpRoute) + } + } + return httpRoutes, nil +} + +func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, error) { + if c.apiServiceError != nil { + return nil, false, c.apiServiceError + } + + for _, service := range c.services { + if service.Namespace == namespace && service.Name == name { + return service, true, nil + } + } + return nil, false, c.apiServiceError +} + +func (c clientMock) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) { + if c.apiEndpointsError != nil { + return nil, false, c.apiEndpointsError + } + + for _, endpoints := range c.endpoints { + if endpoints.Namespace == namespace && endpoints.Name == name { + return endpoints, true, nil + } + } + + return &corev1.Endpoints{}, false, nil +} + +func (c clientMock) GetSecret(namespace, name string) (*corev1.Secret, bool, error) { + if c.apiSecretError != nil { + return nil, false, c.apiSecretError + } + + for _, secret := range c.secrets { + if secret.Namespace == namespace && secret.Name == name { + return secret, true, nil + } + } + return nil, false, nil +} + +func (c clientMock) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) { + return c.watchChan, nil +} diff --git a/pkg/provider/kubernetes/gateway/client_test.go b/pkg/provider/kubernetes/gateway/client_test.go new file mode 100644 index 000000000..eb10e5f2b --- /dev/null +++ b/pkg/provider/kubernetes/gateway/client_test.go @@ -0,0 +1,246 @@ +package gateway + +import ( + "testing" + + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/service-apis/apis/v1alpha1" +) + +func TestStatusEquals(t *testing.T) { + testCases := []struct { + desc string + statusA v1alpha1.GatewayStatus + statusB v1alpha1.GatewayStatus + expected bool + }{ + { + desc: "Empty", + statusA: v1alpha1.GatewayStatus{}, + statusB: v1alpha1.GatewayStatus{}, + expected: true, + }, + { + desc: "Same status", + statusA: v1alpha1.GatewayStatus{ + Conditions: []metav1.Condition{ + { + Type: "foobar", + Reason: "foobar", + }, + }, + Listeners: []v1alpha1.ListenerStatus{ + { + Port: 80, + Conditions: []metav1.Condition{ + { + Type: "foobar", + Reason: "foobar", + }, + }, + }, + }, + }, + statusB: v1alpha1.GatewayStatus{ + Conditions: []metav1.Condition{ + { + Type: "foobar", + Reason: "foobar", + }, + }, + Listeners: []v1alpha1.ListenerStatus{ + { + Port: 80, + Conditions: []metav1.Condition{ + { + Type: "foobar", + Reason: "foobar", + }, + }, + }, + }, + }, + expected: true, + }, + { + desc: "Listeners length not equal", + statusA: v1alpha1.GatewayStatus{ + Listeners: []v1alpha1.ListenerStatus{}, + }, + statusB: v1alpha1.GatewayStatus{ + Listeners: []v1alpha1.ListenerStatus{ + {}, + }, + }, + expected: false, + }, + { + desc: "Gateway conditions length not equal", + statusA: v1alpha1.GatewayStatus{ + Conditions: []metav1.Condition{}, + }, + statusB: v1alpha1.GatewayStatus{ + Conditions: []metav1.Condition{ + {}, + }, + }, + expected: false, + }, + { + desc: "Gateway conditions different types", + statusA: v1alpha1.GatewayStatus{ + Conditions: []metav1.Condition{ + { + Type: "foobar", + }, + }, + }, + statusB: v1alpha1.GatewayStatus{ + Conditions: []metav1.Condition{ + { + Type: "foobir", + }, + }, + }, + expected: false, + }, + { + desc: "Gateway conditions same types but different reason", + statusA: v1alpha1.GatewayStatus{ + Conditions: []metav1.Condition{ + { + Type: "foobar", + }, + }, + }, + statusB: v1alpha1.GatewayStatus{ + Conditions: []metav1.Condition{ + { + Type: "foobar", + Reason: "Another reason", + }, + }, + }, + expected: false, + }, + { + desc: "Gateway listeners conditions length", + statusA: v1alpha1.GatewayStatus{ + Listeners: []v1alpha1.ListenerStatus{ + { + Port: 80, + Conditions: []metav1.Condition{}, + }, + }, + }, + statusB: v1alpha1.GatewayStatus{ + Listeners: []v1alpha1.ListenerStatus{ + { + Port: 80, + Conditions: []metav1.Condition{ + {}, + }, + }, + }, + }, + expected: false, + }, + { + desc: "Gateway listeners conditions same types but different status", + statusA: v1alpha1.GatewayStatus{ + Listeners: []v1alpha1.ListenerStatus{ + { + Conditions: []metav1.Condition{ + { + Type: "foobar", + }, + }, + }, + }, + }, + statusB: v1alpha1.GatewayStatus{ + Listeners: []v1alpha1.ListenerStatus{ + { + Conditions: []metav1.Condition{ + { + Type: "foobar", + Status: "Another status", + }, + }, + }, + }, + }, + expected: false, + }, + { + desc: "Gateway listeners conditions same types but different message", + statusA: v1alpha1.GatewayStatus{ + Listeners: []v1alpha1.ListenerStatus{ + { + Conditions: []metav1.Condition{ + { + Type: "foobar", + }, + }, + }, + }, + }, + statusB: v1alpha1.GatewayStatus{ + Listeners: []v1alpha1.ListenerStatus{ + { + Conditions: []metav1.Condition{ + { + Type: "foobar", + Message: "Another status", + }, + }, + }, + }, + }, + expected: false, + }, + { + desc: "Gateway listeners conditions same types/reason but different ports", + statusA: v1alpha1.GatewayStatus{ + Listeners: []v1alpha1.ListenerStatus{ + { + Port: 80, + Conditions: []metav1.Condition{ + { + Type: "foobar", + Reason: "foobar", + }, + }, + }, + }, + }, + statusB: v1alpha1.GatewayStatus{ + Listeners: []v1alpha1.ListenerStatus{ + { + Port: 443, + Conditions: []metav1.Condition{ + { + Type: "foobar", + Reason: "foobar", + }, + }, + }, + }, + }, + expected: false, + }, + } + + for _, test := range testCases { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + result := statusEquals(test.statusA, test.statusB) + + assert.Equal(t, test.expected, result) + }) + } +} diff --git a/pkg/provider/kubernetes/gateway/fixtures/gatewayclass_with_unknown_controller.yml b/pkg/provider/kubernetes/gateway/fixtures/gatewayclass_with_unknown_controller.yml new file mode 100644 index 000000000..5dbeae273 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/gatewayclass_with_unknown_controller.yml @@ -0,0 +1,46 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: unkown.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: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/one_rule_two_targets.yml b/pkg/provider/kubernetes/gateway/fixtures/one_rule_two_targets.yml new file mode 100644 index 000000000..d88b90889 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/one_rule_two_targets.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: + - serviceName: whoami + port: 80 + weight: 1 + - serviceName: whoami2 + port: 8080 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/services.yml b/pkg/provider/kubernetes/gateway/fixtures/services.yml new file mode 100644 index 000000000..9c75a59c3 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/services.yml @@ -0,0 +1,166 @@ +apiVersion: v1 +kind: Service +metadata: + name: whoami + namespace: default + +spec: + ports: + - name: web2 + port: 8000 + targetPort: web2 + - name: web + port: 80 + targetPort: web + selector: + app: containous + task: whoami + +--- +kind: Endpoints +apiVersion: v1 +metadata: + name: whoami + namespace: default + +subsets: + - addresses: + - ip: 10.10.0.1 + - ip: 10.10.0.2 + ports: + - name: web + port: 80 + - name: web2 + port: 8000 + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami2 + namespace: default + +spec: + ports: + - name: web + port: 8080 + targetPort: web + selector: + app: containous + task: whoami2 + +--- +kind: Endpoints +apiVersion: v1 +metadata: + name: whoami2 + namespace: default + +subsets: + - addresses: + - ip: 10.10.0.3 + - ip: 10.10.0.4 + ports: + - name: web + port: 8080 + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoamitls + namespace: default + +spec: + ports: + - name: websecure + port: 443 + targetPort: websecure + selector: + app: containous + task: whoami2 + +--- +kind: Endpoints +apiVersion: v1 +metadata: + name: whoamitls + namespace: default + +subsets: + - addresses: + - ip: 10.10.0.5 + - ip: 10.10.0.6 + ports: + - name: websecure + port: 8443 + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami3 + namespace: default + +spec: + ports: + - name: websecure2 + port: 8443 + targetPort: websecure2 + scheme: https + selector: + app: containous + task: whoami3 + +--- +kind: Endpoints +apiVersion: v1 +metadata: + name: whoami3 + namespace: default + +subsets: + - addresses: + - ip: 10.10.0.7 + - ip: 10.10.0.8 + ports: + - name: websecure2 + port: 8443 + +--- +apiVersion: v1 +kind: Service +metadata: + name: external-svc + namespace: default +spec: + externalName: external.domain + type: ExternalName + +--- +apiVersion: v1 +kind: Service +metadata: + name: external-svc-with-http + namespace: default +spec: + externalName: external.domain + type: ExternalName + ports: + - name: http + protocol: TCP + port: 80 + +--- +apiVersion: v1 +kind: Service +metadata: + name: external-svc-with-https + namespace: default +spec: + externalName: external.domain + type: ExternalName + ports: + - name: https + protocol: TCP + port: 443 diff --git a/pkg/provider/kubernetes/gateway/fixtures/simple.yml b/pkg/provider/kubernetes/gateway/fixtures/simple.yml new file mode 100644 index 000000000..821a8fccc --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/simple.yml @@ -0,0 +1,46 @@ +--- +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: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/two_rules.yml b/pkg/provider/kubernetes/gateway/fixtures/two_rules.yml new file mode 100644 index 000000000..6ac21dc0d --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/two_rules.yml @@ -0,0 +1,54 @@ +--- +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: + - serviceName: whoami + port: 80 + weight: 1 + - matches: + - path: + type: Exact + value: /bir + forwardTo: + - serviceName: whoami2 + port: 8080 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/with_multiple_host.yml b/pkg/provider/kubernetes/gateway/fixtures/with_multiple_host.yml new file mode 100644 index 000000000..9333258fa --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/with_multiple_host.yml @@ -0,0 +1,43 @@ +--- +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" + - "bar.com" + rules: + - forwardTo: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/with_protocol_https.yml b/pkg/provider/kubernetes/gateway/fixtures/with_protocol_https.yml new file mode 100644 index 000000000..1e76a834a --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/with_protocol_https.yml @@ -0,0 +1,62 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +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: HTTPS + port: 443 + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + 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: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/with_protocol_https_without_tls.yml b/pkg/provider/kubernetes/gateway/fixtures/with_protocol_https_without_tls.yml new file mode 100644 index 000000000..525557dab --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/with_protocol_https_without_tls.yml @@ -0,0 +1,46 @@ +--- +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: HTTPS + port: 443 + 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: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/with_several_rules.yml b/pkg/provider/kubernetes/gateway/fixtures/with_several_rules.yml new file mode 100644 index 000000000..05c0e8675 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/with_several_rules.yml @@ -0,0 +1,64 @@ +--- +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: Prefix + value: /bar + headers: + type: Exact + values: + my-header: foo + my-header2: bar + forwardTo: + - serviceName: whoami + port: 80 + weight: 1 + + - matches: + - path: + type: Exact + value: /bar + headers: + type: Exact + values: + my-header: bar + forwardTo: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/with_two_gateways_one_httproute.yml b/pkg/provider/kubernetes/gateway/fixtures/with_two_gateways_one_httproute.yml new file mode 100644 index 000000000..09c65d346 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/with_two_gateways_one_httproute.yml @@ -0,0 +1,80 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +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-https + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTPS + port: 443 + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + app: foo + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-http + 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: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/with_two_listeners_one_httproute.yml b/pkg/provider/kubernetes/gateway/fixtures/with_two_listeners_one_httproute.yml new file mode 100644 index 000000000..ba86b19cb --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/with_two_listeners_one_httproute.yml @@ -0,0 +1,70 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +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: HTTPS + port: 443 + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + app: foo + - 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: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/with_wrong_service_port.yml b/pkg/provider/kubernetes/gateway/fixtures/with_wrong_service_port.yml new file mode 100644 index 000000000..2ffa7cb44 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/with_wrong_service_port.yml @@ -0,0 +1,46 @@ +--- +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: + - serviceName: whoami + weight: 1 + port: 9000 diff --git a/pkg/provider/kubernetes/gateway/fixtures/without_gatewayclass.yml b/pkg/provider/kubernetes/gateway/fixtures/without_gatewayclass.yml new file mode 100644 index 000000000..13c851195 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/without_gatewayclass.yml @@ -0,0 +1,38 @@ +--- +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: + - serviceName: whoami + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/without_httproute.yml b/pkg/provider/kubernetes/gateway/fixtures/without_httproute.yml new file mode 100644 index 000000000..4694217dc --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/without_httproute.yml @@ -0,0 +1,25 @@ +--- +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 diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go new file mode 100644 index 000000000..50490cde7 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/kubernetes.go @@ -0,0 +1,906 @@ +package gateway + +import ( + "context" + "crypto/sha256" + "errors" + "fmt" + "net" + "os" + "sort" + "strconv" + "strings" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/hashicorp/go-multierror" + "github.com/mitchellh/hashstructure" + ptypes "github.com/traefik/paerser/types" + "github.com/traefik/traefik/v2/pkg/config/dynamic" + "github.com/traefik/traefik/v2/pkg/job" + "github.com/traefik/traefik/v2/pkg/log" + "github.com/traefik/traefik/v2/pkg/provider" + "github.com/traefik/traefik/v2/pkg/safe" + "github.com/traefik/traefik/v2/pkg/tls" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/service-apis/apis/v1alpha1" +) + +const providerName = "kubernetesgateway" + +// Provider holds configurations of the provider. +type Provider struct { + Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` + Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"` + 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 label selector to select specific GatewayClasses." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"` + ThrottleDuration ptypes.Duration `description:"Kubernetes refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` + EntryPoints map[string]Entrypoint `json:"-" toml:"-" yaml:"-" label:"-" file:"-"` + + lastConfiguration safe.Safe +} + +// Entrypoint defines the available entry points. +type Entrypoint struct { + Address string + HasHTTPTLSConf bool +} + +func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) { + // Label selector validation + _, err := labels.Parse(p.LabelSelector) + if err != nil { + return nil, fmt.Errorf("invalid label selector: %q", p.LabelSelector) + } + log.FromContext(ctx).Infof("label selector is: %q", p.LabelSelector) + + withEndpoint := "" + if p.Endpoint != "" { + withEndpoint = fmt.Sprintf(" with endpoint %s", p.Endpoint) + } + + var client *clientWrapper + switch { + case os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "": + log.FromContext(ctx).Infof("Creating in-cluster Provider client%s", withEndpoint) + client, err = newInClusterClient(p.Endpoint) + case os.Getenv("KUBECONFIG") != "": + log.FromContext(ctx).Infof("Creating cluster-external Provider client from KUBECONFIG %s", os.Getenv("KUBECONFIG")) + client, err = newExternalClusterClientFromFile(os.Getenv("KUBECONFIG")) + default: + log.FromContext(ctx).Infof("Creating cluster-external Provider client%s", withEndpoint) + client, err = newExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath) + } + + if err != nil { + return nil, err + } + client.labelSelector = p.LabelSelector + + return client, nil +} + +// Init the provider. +func (p *Provider) Init() error { + return nil +} + +// Provide allows the k8s provider to provide configurations to traefik +// using the given configuration channel. +func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error { + ctxLog := log.With(context.Background(), log.Str(log.ProviderName, providerName)) + logger := log.FromContext(ctxLog) + + k8sClient, err := p.newK8sClient(ctxLog) + if err != nil { + return err + } + + pool.GoCtx(func(ctxPool context.Context) { + operation := func() error { + eventsChan, err := k8sClient.WatchAll(p.Namespaces, ctxPool.Done()) + if err != nil { + logger.Errorf("Error watching kubernetes events: %v", err) + timer := time.NewTimer(1 * time.Second) + select { + case <-timer.C: + return err + case <-ctxPool.Done(): + return nil + } + } + + throttleDuration := time.Duration(p.ThrottleDuration) + throttledChan := throttleEvents(ctxLog, throttleDuration, pool, eventsChan) + if throttledChan != nil { + eventsChan = throttledChan + } + + for { + select { + case <-ctxPool.Done(): + return nil + case event := <-eventsChan: + // Note that event is the *first* event that came in during this throttling interval -- if we're hitting our throttle, we may have dropped events. + // This is fine, because we don't treat different event types differently. + // But if we do in the future, we'll need to track more information about the dropped events. + conf := p.loadConfigurationFromGateway(ctxLog, k8sClient) + + confHash, err := hashstructure.Hash(conf, nil) + switch { + case err != nil: + logger.Error("Unable to hash the configuration") + case p.lastConfiguration.Get() == confHash: + logger.Debugf("Skipping Kubernetes event kind %T", event) + default: + p.lastConfiguration.Set(confHash) + configurationChan <- dynamic.Message{ + ProviderName: providerName, + Configuration: conf, + } + } + + // If we're throttling, + // we sleep here for the throttle duration to enforce that we don't refresh faster than our throttle. + // time.Sleep returns immediately if p.ThrottleDuration is 0 (no throttle). + time.Sleep(throttleDuration) + } + } + } + + notify := func(err error, time time.Duration) { + logger.Errorf("Provider connection error: %v; retrying in %s", err, time) + } + err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxPool), notify) + if err != nil { + logger.Errorf("Cannot connect to Provider: %v", err) + } + }) + + return nil +} + +// TODO Handle errors and update resources statuses (gatewayClass, gateway). +func (p *Provider) loadConfigurationFromGateway(ctx context.Context, client Client) *dynamic.Configuration { + logger := log.FromContext(ctx) + + gatewayClassNames := map[string]struct{}{} + + gatewayClasses, err := client.GetGatewayClasses() + if err != nil { + logger.Errorf("Cannot find GatewayClasses: %v", err) + return &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{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + } + } + + for _, gatewayClass := range gatewayClasses { + if gatewayClass.Spec.Controller == "traefik.io/gateway-controller" { + gatewayClassNames[gatewayClass.Name] = struct{}{} + + err := client.UpdateGatewayClassStatus(gatewayClass, metav1.Condition{ + Type: string(v1alpha1.GatewayClassConditionStatusAdmitted), + Status: metav1.ConditionTrue, + Reason: "Handled", + Message: "Handled by Traefik controller", + LastTransitionTime: metav1.Now(), + }) + if err != nil { + logger.Errorf("Failed to update %s condition: %v", v1alpha1.GatewayClassConditionStatusAdmitted, err) + } + } + } + + cfgs := map[string]*dynamic.Configuration{} + + // TODO check if we can only use the default filtering mechanism + for _, gateway := range client.GetGateways() { + ctxLog := log.With(ctx, log.Str("gateway", gateway.Name), log.Str("namespace", gateway.Namespace)) + logger := log.FromContext(ctxLog) + + if _, ok := gatewayClassNames[gateway.Spec.GatewayClassName]; !ok { + continue + } + + cfg, err := p.createGatewayConf(client, gateway) + if err != nil { + logger.Error(err) + continue + } + + cfgs[gateway.Name+gateway.Namespace] = cfg + } + + conf := provider.Merge(ctx, cfgs) + + conf.TLS = &dynamic.TLSConfiguration{} + + for _, cfg := range cfgs { + if conf.TLS == nil { + conf.TLS = &dynamic.TLSConfiguration{} + } + + conf.TLS.Certificates = append(conf.TLS.Certificates, cfg.TLS.Certificates...) + + for name, options := range cfg.TLS.Options { + if conf.TLS.Options == nil { + conf.TLS.Options = map[string]tls.Options{} + } + + conf.TLS.Options[name] = options + } + + for name, store := range cfg.TLS.Stores { + if conf.TLS.Stores == nil { + conf.TLS.Stores = map[string]tls.Store{} + } + + conf.TLS.Stores[name] = store + } + } + + return conf +} + +func (p *Provider) createGatewayConf(client Client, gateway *v1alpha1.Gateway) (*dynamic.Configuration, error) { + conf := &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{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + } + + tlsConfigs := make(map[string]*tls.CertAndStores) + + // GatewayReasonListenersNotValid is used when one or more + // Listeners have an invalid or unsupported configuration + // and cannot be configured on the Gateway. + listenerStatuses := p.fillGatewayConf(client, gateway, conf, tlsConfigs) + + gatewayStatus, errG := p.makeGatewayStatus(listenerStatuses) + + err := client.UpdateGatewayStatus(gateway, gatewayStatus) + if err != nil { + return nil, fmt.Errorf("an error occurred while updating gateway status: %w", err) + } + + if errG != nil { + return nil, fmt.Errorf("an error occurred while creating gateway status: %w", errG) + } + + if len(tlsConfigs) > 0 { + conf.TLS.Certificates = append(conf.TLS.Certificates, getTLSConfig(tlsConfigs)...) + } + + return conf, nil +} + +func (p *Provider) fillGatewayConf(client Client, gateway *v1alpha1.Gateway, conf *dynamic.Configuration, tlsConfigs map[string]*tls.CertAndStores) []v1alpha1.ListenerStatus { + listenerStatuses := make([]v1alpha1.ListenerStatus, len(gateway.Spec.Listeners)) + + for i, listener := range gateway.Spec.Listeners { + listenerStatuses[i] = v1alpha1.ListenerStatus{ + Port: listener.Port, + Conditions: []metav1.Condition{}, + } + + // Supported Protocol + if listener.Protocol != v1alpha1.HTTPProtocolType && listener.Protocol != v1alpha1.HTTPSProtocolType { + // update "Detached" status true with "UnsupportedProtocol" reason + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionDetached), + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonUnsupportedProtocol), + Message: fmt.Sprintf("Unsupported listener protocol %q", listener.Protocol), + }) + + continue + } + + ep, err := p.entryPointName(listener.Port, listener.Protocol) + if err != nil { + // update "Detached" status with "PortUnavailable" reason + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionDetached), + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonPortUnavailable), + Message: fmt.Sprintf("Cannot find entryPoint for Gateway: %v", err), + }) + + continue + } + + if listener.Protocol == v1alpha1.HTTPSProtocolType { + if listener.TLS == nil { + // update "Detached" status with "UnsupportedProtocol" reason + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionDetached), + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonUnsupportedProtocol), + Message: fmt.Sprintf("No TLS configuration for Gateway Listener port %d and protocol %q", listener.Port, listener.Protocol), + }) + + continue + } + + if listener.TLS.CertificateRef.Kind != "Secret" || listener.TLS.CertificateRef.Group != "core" { + // update "ResolvedRefs" status true with "InvalidCertificateRef" reason + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonInvalidCertificateRef), + Message: fmt.Sprintf("Unsupported TLS CertificateRef group/kind : %v/%v", listener.TLS.CertificateRef.Group, listener.TLS.CertificateRef.Kind), + }) + + continue + } + + configKey := gateway.Namespace + "/" + listener.TLS.CertificateRef.Name + if _, tlsExists := tlsConfigs[configKey]; !tlsExists { + tlsConf, err := getTLS(client, listener.TLS.CertificateRef.Name, gateway.Namespace) + if err != nil { + // update "ResolvedRefs" status true with "InvalidCertificateRef" reason + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonInvalidCertificateRef), + Message: fmt.Sprintf("Error while retrieving certificate: %v", err), + }) + + continue + } + + tlsConfigs[configKey] = tlsConf + } + } + + // Supported Route types + if listener.Routes.Kind != "HTTPRoute" { + // update "ResolvedRefs" status true with "InvalidRoutesRef" reason + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonInvalidRoutesRef), + Message: fmt.Sprintf("Unsupported Route Kind %q", listener.Routes.Kind), + }) + + continue + } + + // TODO: support RouteNamespaces + httpRoutes, err := client.GetHTTPRoutes(gateway.Namespace, labels.SelectorFromSet(listener.Routes.Selector.MatchLabels)) + if err != nil { + // update "ResolvedRefs" status true with "InvalidRoutesRef" reason + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonInvalidRoutesRef), + Message: fmt.Sprintf("Cannot fetch HTTPRoutes for namespace %q and matchLabels %v", gateway.Namespace, listener.Routes.Selector.MatchLabels), + }) + continue + } + + for _, httpRoute := range httpRoutes { + // Should never happen + if httpRoute == nil { + continue + } + + hostRule := hostRule(httpRoute.Spec) + + for _, routeRule := range httpRoute.Spec.Rules { + rule, err := extractRule(routeRule, hostRule) + 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("Skipping HTTPRoute %s: cannot generate rule: %v", httpRoute.Name, err), + }) + continue + } + + router := dynamic.Router{ + Rule: rule, + EntryPoints: []string{ep}, + } + + if listener.TLS != nil { + // TODO support let's encrypt + router.TLS = &dynamic.RouterTLSConfig{} + } + + // Adding the gateway name and the entryPoint name prevents overlapping of routers build from the same routes. + routerName := httpRoute.Name + "-" + gateway.Name + "-" + ep + routerKey, err := makeRouterKey(router.Rule, makeID(httpRoute.Namespace, routerName)) + 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("Skipping HTTPRoute %s: cannot make router's key with rule %s: %v", httpRoute.Name, router.Rule, err), + }) + + // TODO update the RouteStatus condition / deduplicate conditions on listener + continue + } + + 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), + }) + + // 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 + } + + if router.Service != "" { + routerKey = provider.Normalize(routerKey) + + conf.HTTP.Routers[routerKey] = &router + } + } + } + } + + return listenerStatuses +} + +func (p *Provider) makeGatewayStatus(listenerStatuses []v1alpha1.ListenerStatus) (v1alpha1.GatewayStatus, error) { + // As Status.Addresses are not implemented yet, we initialize an empty array to follow the API expectations. + gatewayStatus := v1alpha1.GatewayStatus{ + Addresses: []v1alpha1.GatewayAddress{}, + } + + var result error + for i, listener := range listenerStatuses { + if len(listener.Conditions) == 0 { + // GatewayConditionReady "Ready", GatewayConditionReason "ListenerReady" + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionReady), + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Reason: "ListenerReady", + Message: "No error found", + }) + + continue + } + + for _, condition := range listener.Conditions { + result = multierror.Append(result, errors.New(condition.Message)) + } + } + + if result != nil { + // GatewayConditionReady "Ready", GatewayConditionReason "ListenersNotValid" + gatewayStatus.Conditions = append(gatewayStatus.Conditions, metav1.Condition{ + Type: string(v1alpha1.GatewayConditionReady), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.GatewayReasonListenersNotValid), + Message: "All Listeners must be valid", + }) + + return gatewayStatus, result + } + + gatewayStatus.Listeners = listenerStatuses + + // update "Scheduled" status with "ResourcesAvailable" reason + gatewayStatus.Conditions = append(gatewayStatus.Conditions, metav1.Condition{ + Type: string(v1alpha1.GatewayConditionScheduled), + Status: metav1.ConditionTrue, + Reason: "ResourcesAvailable", + Message: "Resources available", + LastTransitionTime: metav1.Now(), + }) + + // update "Ready" status with "ListenersValid" reason + gatewayStatus.Conditions = append(gatewayStatus.Conditions, metav1.Condition{ + Type: string(v1alpha1.GatewayConditionReady), + Status: metav1.ConditionTrue, + Reason: "ListenersValid", + Message: "Listeners valid", + LastTransitionTime: metav1.Now(), + }) + + return gatewayStatus, nil +} + +func hostRule(httpRouteSpec v1alpha1.HTTPRouteSpec) string { + hostRule := "" + for i, hostname := range httpRouteSpec.Hostnames { + if i > 0 && len(hostname) > 0 { + hostRule += "`, `" + } + hostRule += string(hostname) + } + + if hostRule != "" { + return "Host(`" + hostRule + "`)" + } + + return "" +} + +func extractRule(routeRule v1alpha1.HTTPRouteRule, hostRule string) (string, error) { + var rule string + var matchesRules []string + + for _, match := range routeRule.Matches { + if len(match.Path.Type) == 0 && match.Headers == nil { + continue + } + + var matchRules []string + // TODO handle other path types + if len(match.Path.Type) > 0 { + switch match.Path.Type { + case v1alpha1.PathMatchExact: + matchRules = append(matchRules, "Path(`"+match.Path.Value+"`)") + case v1alpha1.PathMatchPrefix: + matchRules = append(matchRules, "PathPrefix(`"+match.Path.Value+"`)") + default: + return "", fmt.Errorf("unsupported path match %s", match.Path.Type) + } + } + + // TODO handle other headers types + if match.Headers != nil { + switch match.Headers.Type { + case v1alpha1.HeaderMatchExact: + var headerRules []string + + for headerName, headerValue := range match.Headers.Values { + headerRules = append(headerRules, "Headers(`"+headerName+"`,`"+headerValue+"`)") + } + // to have a consistent order + sort.Strings(headerRules) + matchRules = append(matchRules, headerRules...) + default: + return "", fmt.Errorf("unsupported header match type %s", match.Headers.Type) + } + } + + matchesRules = append(matchesRules, strings.Join(matchRules, " && ")) + } + + // If no matches are specified, the default is a prefix + // path match on "/", which has the effect of matching every + // HTTP request. + if len(routeRule.Matches) == 0 { + matchesRules = append(matchesRules, "PathPrefix(`/`)") + } + + if hostRule != "" { + if len(matchesRules) == 0 { + return hostRule, nil + } + rule += hostRule + " && " + } + + if len(matchesRules) == 1 { + return rule + matchesRules[0], nil + } + + if len(rule) == 0 { + return strings.Join(matchesRules, " || "), nil + } + + return rule + "(" + strings.Join(matchesRules, " || ") + ")", nil +} + +func (p *Provider) entryPointName(port v1alpha1.PortNumber, protocol v1alpha1.ProtocolType) (string, error) { + portStr := strconv.FormatInt(int64(port), 10) + + for name, entryPoint := range p.EntryPoints { + if strings.HasSuffix(entryPoint.Address, ":"+portStr) { + // if the protocol is HTTP the entryPoint must have no TLS conf + if protocol == v1alpha1.HTTPProtocolType && entryPoint.HasHTTPTLSConf { + continue + } + + return name, nil + } + } + + return "", fmt.Errorf("no matching entryPoint for port %d and protocol %q", port, protocol) +} + +func makeRouterKey(rule, name string) (string, error) { + h := sha256.New() + if _, err := h.Write([]byte(rule)); err != nil { + return "", err + } + + key := fmt.Sprintf("%s-%.10x", name, h.Sum(nil)) + + return key, nil +} + +func makeID(namespace, name string) string { + if namespace == "" { + return name + } + + return namespace + "-" + name +} + +func getTLS(k8sClient Client, secretName, namespace string) (*tls.CertAndStores, error) { + secret, exists, err := k8sClient.GetSecret(namespace, secretName) + if err != nil { + return nil, fmt.Errorf("failed to fetch secret %s/%s: %w", namespace, secretName, err) + } + if !exists { + return nil, fmt.Errorf("secret %s/%s does not exist", namespace, secretName) + } + + cert, key, err := getCertificateBlocks(secret, namespace, secretName) + if err != nil { + return nil, err + } + + return &tls.CertAndStores{ + Certificate: tls.Certificate{ + CertFile: tls.FileOrContent(cert), + KeyFile: tls.FileOrContent(key), + }, + }, nil +} + +func getTLSConfig(tlsConfigs map[string]*tls.CertAndStores) []*tls.CertAndStores { + var secretNames []string + for secretName := range tlsConfigs { + secretNames = append(secretNames, secretName) + } + sort.Strings(secretNames) + + var configs []*tls.CertAndStores + for _, secretName := range secretNames { + configs = append(configs, tlsConfigs[secretName]) + } + + return configs +} + +func getCertificateBlocks(secret *corev1.Secret, namespace, secretName string) (string, string, error) { + var missingEntries []string + + tlsCrtData, tlsCrtExists := secret.Data["tls.crt"] + if !tlsCrtExists { + missingEntries = append(missingEntries, "tls.crt") + } + + tlsKeyData, tlsKeyExists := secret.Data["tls.key"] + if !tlsKeyExists { + missingEntries = append(missingEntries, "tls.key") + } + + if len(missingEntries) > 0 { + return "", "", fmt.Errorf("secret %s/%s is missing the following TLS data entries: %s", + namespace, secretName, strings.Join(missingEntries, ", ")) + } + + cert := string(tlsCrtData) + if cert == "" { + missingEntries = append(missingEntries, "tls.crt") + } + + key := string(tlsKeyData) + if key == "" { + missingEntries = append(missingEntries, "tls.key") + } + + if len(missingEntries) > 0 { + return "", "", fmt.Errorf("secret %s/%s contains the following empty TLS data entries: %s", + namespace, secretName, strings.Join(missingEntries, ", ")) + } + + return cert, key, nil +} + +// loadServices is generating a WRR service, even when there is only one target. +func loadServices(client Client, namespace string, targets []v1alpha1.HTTPRouteForwardTo) (*dynamic.Service, map[string]*dynamic.Service, error) { + services := map[string]*dynamic.Service{} + + wrrSvc := &dynamic.Service{ + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{}, + }, + } + + for _, forwardTo := range targets { + if forwardTo.ServiceName == nil { + continue + } + + svc := dynamic.Service{ + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: func(v bool) *bool { return &v }(true), + }, + } + + // TODO Handle BackendRefs + + service, exists, err := client.GetService(namespace, *forwardTo.ServiceName) + if err != nil { + return nil, nil, err + } + + if !exists { + return nil, nil, errors.New("service not found") + } + + if len(service.Spec.Ports) > 1 && forwardTo.Port == 0 { + // If the port is unspecified and the backend is a Service + // object consisting of multiple port definitions, the route + // must be dropped from the Gateway. The controller should + // raise the "ResolvedRefs" condition on the Gateway with the + // "DroppedRoutes" reason. The gateway status for this route + // should be updated with a condition that describes the error + // more specifically. + log.WithoutContext().Errorf("A multiple ports Kubernetes Service cannot be used if unspecified forwardTo.Port") + continue + } + + var portName string + var portSpec corev1.ServicePort + var match bool + + for _, p := range service.Spec.Ports { + if forwardTo.Port == 0 || p.Port == int32(forwardTo.Port) { + portName = p.Name + portSpec = p + match = true + break + } + } + + if !match { + return nil, nil, errors.New("service port not found") + } + + endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, *forwardTo.ServiceName) + if endpointsErr != nil { + return nil, nil, endpointsErr + } + + if !endpointsExists { + return nil, nil, errors.New("endpoints not found") + } + + if len(endpoints.Subsets) == 0 { + return nil, nil, errors.New("subset not found") + } + + var port int32 + var portStr string + for _, subset := range endpoints.Subsets { + for _, p := range subset.Ports { + if portName == p.Name { + port = p.Port + break + } + } + + if port == 0 { + return nil, nil, errors.New("cannot define a port") + } + + protocol := getProtocol(portSpec, portName) + + portStr = strconv.FormatInt(int64(port), 10) + for _, addr := range subset.Addresses { + svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.Server{ + URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(addr.IP, portStr)), + }) + } + } + + 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 { + return nil, nil, errors.New("no service has been created") + } + + return wrrSvc, services, nil +} + +func getProtocol(portSpec corev1.ServicePort, portName string) string { + protocol := "http" + if portSpec.Port == 443 || strings.HasPrefix(portName, "https") { + protocol = "https" + } + + return protocol +} + +func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *safe.Pool, eventsChan <-chan interface{}) chan interface{} { + if throttleDuration == 0 { + return nil + } + // Create a buffered channel to hold the pending event (if we're delaying processing the event due to throttling) + eventsChanBuffered := make(chan interface{}, 1) + + // Run a goroutine that reads events from eventChan and does a non-blocking write to pendingEvent. + // This guarantees that writing to eventChan will never block, + // and that pendingEvent will have something in it if there's been an event since we read from that channel. + pool.GoCtx(func(ctxPool context.Context) { + for { + select { + case <-ctxPool.Done(): + return + case nextEvent := <-eventsChan: + select { + case eventsChanBuffered <- nextEvent: + default: + // We already have an event in eventsChanBuffered, so we'll do a refresh as soon as our throttle allows us to. + // It's fine to drop the event and keep whatever's in the buffer -- we don't do different things for different events + log.FromContext(ctx).Debugf("Dropping event kind %T due to throttling", nextEvent) + } + } + } + }) + + return eventsChanBuffered +} diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go new file mode 100644 index 000000000..25a6300c5 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go @@ -0,0 +1,990 @@ +package gateway + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v2/pkg/config/dynamic" + "github.com/traefik/traefik/v2/pkg/provider" + "github.com/traefik/traefik/v2/pkg/tls" + "sigs.k8s.io/service-apis/apis/v1alpha1" +) + +var _ provider.Provider = (*Provider)(nil) + +func Bool(v bool) *bool { return &v } + +func TestLoadHTTPRoutes(t *testing.T) { + testCases := []struct { + desc string + ingressClass string + paths []string + expected *dynamic.Configuration + entryPoints map[string]Entrypoint + }{ + { + desc: "Empty", + 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{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty because missing entry point", + paths: []string{"services.yml", "simple.yml"}, + entryPoints: map[string]Entrypoint{"web": { + Address: ":443", + }}, + 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{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty because no http route defined", + paths: []string{"services.yml", "without_httproute.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{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by missing GatewayClass", + entryPoints: map[string]Entrypoint{"web": { + Address: ":80", + }}, + paths: []string{"services.yml", "without_gatewayclass.yml"}, + 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{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by unknown GatewayClass controller name", + entryPoints: map[string]Entrypoint{"web": { + Address: ":80", + }}, + paths: []string{"services.yml", "gatewayclass_with_unknown_controller.yml"}, + 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{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by multiport service with wrong TargetPort", + entryPoints: map[string]Entrypoint{"web": { + Address: ":80", + }}, + paths: []string{"services.yml", "with_wrong_service_port.yml"}, + 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{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Empty caused by HTTPS without TLS", + entryPoints: map[string]Entrypoint{"websecure": { + Address: ":443", + }}, + paths: []string{"services.yml", "with_protocol_https_without_tls.yml"}, + 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{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Simple HTTPRoute, with foo entrypoint", + paths: []string{"services.yml", "simple.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: "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"}, + entryPoints: map[string]Entrypoint{"websecure": { + Address: ":443", + }}, + 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-websecure-1c0cf64bde37d9d0df06": { + EntryPoints: []string{"websecure"}, + Service: "default-http-app-1-my-gateway-websecure-1c0cf64bde37d9d0df06-wrr", + Rule: "Host(`foo.com`) && Path(`/bar`)", + TLS: &dynamic.RouterTLSConfig{}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-http-app-1-my-gateway-websecure-1c0cf64bde37d9d0df06-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + 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{ + Certificates: []*tls.CertAndStores{ + { + Certificate: tls.Certificate{ + CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + }, + }, + }, + }, + }, + }, + { + desc: "Simple HTTPRoute, with multiple hosts", + paths: []string{"services.yml", "with_multiple_host.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-75dd1ad561e42725558a": { + EntryPoints: []string{"web"}, + Service: "default-http-app-1-my-gateway-web-75dd1ad561e42725558a-wrr", + Rule: "Host(`foo.com`, `bar.com`) && PathPrefix(`/`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-http-app-1-my-gateway-web-75dd1ad561e42725558a-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + 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: "One HTTPRoute with two different rules", + paths: []string{"services.yml", "two_rules.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"}, + Rule: "Host(`foo.com`) && Path(`/bar`)", + Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr", + }, + "default-http-app-1-my-gateway-web-d737b4933fa88e68ab8a": { + EntryPoints: []string{"web"}, + Rule: "Host(`foo.com`) && Path(`/bir`)", + Service: "default-http-app-1-my-gateway-web-d737b4933fa88e68ab8a-wrr", + }, + }, + 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: "default-whoami-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-http-app-1-my-gateway-web-d737b4933fa88e68ab8a-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-whoami2-8080", + 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), + }, + }, + "default-whoami2-8080": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.3:8080", + }, + { + URL: "http://10.10.0.4:8080", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "One HTTPRoute with one rule two targets", + paths: []string{"services.yml", "one_rule_two_targets.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"}, + Rule: "Host(`foo.com`) && Path(`/bar`)", + Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr", + }, + }, + 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: "default-whoami-80", + Weight: func(i int) *int { return &i }(1), + }, + { + Name: "default-whoami2-8080", + 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), + }, + }, + "default-whoami2-8080": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.3:8080", + }, + { + URL: "http://10.10.0.4:8080", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Two Gateways and one HTTPRoute", + paths: []string{"services.yml", "with_two_gateways_one_httproute.yml"}, + entryPoints: map[string]Entrypoint{ + "web": { + Address: ":80", + }, + "websecure": { + Address: ":443", + }, + }, + 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-http-web-1c0cf64bde37d9d0df06": { + EntryPoints: []string{"web"}, + Service: "default-http-app-1-my-gateway-http-web-1c0cf64bde37d9d0df06-wrr", + Rule: "Host(`foo.com`) && Path(`/bar`)", + }, + "default-http-app-1-my-gateway-https-websecure-1c0cf64bde37d9d0df06": { + EntryPoints: []string{"websecure"}, + Service: "default-http-app-1-my-gateway-https-websecure-1c0cf64bde37d9d0df06-wrr", + Rule: "Host(`foo.com`) && Path(`/bar`)", + TLS: &dynamic.RouterTLSConfig{}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-http-app-1-my-gateway-http-web-1c0cf64bde37d9d0df06-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-whoami-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-http-app-1-my-gateway-https-websecure-1c0cf64bde37d9d0df06-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + 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{ + Certificates: []*tls.CertAndStores{ + { + Certificate: tls.Certificate{ + CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + }, + }, + }, + }, + }, + }, + { + desc: "Gateway with two listeners and one HTTPRoute", + paths: []string{"services.yml", "with_two_listeners_one_httproute.yml"}, + entryPoints: map[string]Entrypoint{ + "web": { + Address: ":80", + }, + "websecure": { + Address: ":443", + }, + }, + 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`)", + }, + "default-http-app-1-my-gateway-websecure-1c0cf64bde37d9d0df06": { + EntryPoints: []string{"websecure"}, + Service: "default-http-app-1-my-gateway-websecure-1c0cf64bde37d9d0df06-wrr", + Rule: "Host(`foo.com`) && Path(`/bar`)", + TLS: &dynamic.RouterTLSConfig{}, + }, + }, + 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: "default-whoami-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-http-app-1-my-gateway-websecure-1c0cf64bde37d9d0df06-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + 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{ + Certificates: []*tls.CertAndStores{ + { + Certificate: tls.Certificate{ + CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + }, + }, + }, + }, + }, + }, + { + desc: "Simple HTTPRoute, with several rules", + paths: []string{"services.yml", "with_several_rules.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-6211a6376ce8f78494a8": { + EntryPoints: []string{"web"}, + Service: "default-http-app-1-my-gateway-web-6211a6376ce8f78494a8-wrr", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`) && Headers(`my-header2`,`bar`) && Headers(`my-header`,`foo`)", + }, + "default-http-app-1-my-gateway-web-fe80e69a38713941ea22": { + EntryPoints: []string{"web"}, + Service: "default-http-app-1-my-gateway-web-fe80e69a38713941ea22-wrr", + Rule: "Host(`foo.com`) && Path(`/bar`) && Headers(`my-header`,`bar`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-http-app-1-my-gateway-web-6211a6376ce8f78494a8-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-whoami-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-http-app-1-my-gateway-web-fe80e69a38713941ea22-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + 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{}, + }, + }, + } + + for _, test := range testCases { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + if test.expected == nil { + return + } + + p := Provider{EntryPoints: test.entryPoints} + conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) + assert.Equal(t, test.expected, conf) + }) + } +} + +func TestHostRule(t *testing.T) { + testCases := []struct { + desc string + routeSpec v1alpha1.HTTPRouteSpec + expectedRule string + }{ + { + desc: "Empty rule and matches", + expectedRule: "", + }, + { + desc: "One Host", + routeSpec: v1alpha1.HTTPRouteSpec{ + Hostnames: []v1alpha1.Hostname{ + "Foo", + }, + }, + expectedRule: "Host(`Foo`)", + }, + { + desc: "Multiple Hosts", + routeSpec: v1alpha1.HTTPRouteSpec{ + Hostnames: []v1alpha1.Hostname{ + "Foo", + "Bar", + "Bir", + }, + }, + expectedRule: "Host(`Foo`, `Bar`, `Bir`)", + }, + { + desc: "Multiple Hosts with empty one", + routeSpec: v1alpha1.HTTPRouteSpec{ + Hostnames: []v1alpha1.Hostname{ + "Foo", + "", + "Bir", + }, + }, + expectedRule: "Host(`Foo`, `Bir`)", + }, + { + desc: "Multiple empty hosts", + routeSpec: v1alpha1.HTTPRouteSpec{ + Hostnames: []v1alpha1.Hostname{ + "", + "", + "", + }, + }, + expectedRule: "", + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + assert.Equal(t, test.expectedRule, hostRule(test.routeSpec)) + }) + } +} + +func TestExtractRule(t *testing.T) { + testCases := []struct { + desc string + routeRule v1alpha1.HTTPRouteRule + hostRule string + expectedRule string + expectedError bool + }{ + { + desc: "Empty rule and matches", + expectedRule: "PathPrefix(`/`)", + }, + { + desc: "One Host rule without matches", + hostRule: "Host(`foo.com`)", + expectedRule: "Host(`foo.com`) && PathPrefix(`/`)", + }, + { + desc: "One Path in matches", + routeRule: v1alpha1.HTTPRouteRule{ + Matches: []v1alpha1.HTTPRouteMatch{ + { + Path: v1alpha1.HTTPPathMatch{ + Type: v1alpha1.PathMatchExact, + Value: "/foo/", + }, + }, + }, + }, + expectedRule: "Path(`/foo/`)", + }, + { + desc: "One Path in matches and another unknown", + routeRule: v1alpha1.HTTPRouteRule{ + Matches: []v1alpha1.HTTPRouteMatch{ + { + Path: v1alpha1.HTTPPathMatch{ + Type: v1alpha1.PathMatchExact, + Value: "/foo/", + }, + }, + { + Path: v1alpha1.HTTPPathMatch{ + Type: "unknown", + Value: "/foo/", + }, + }, + }, + }, + expectedError: true, + }, + { + desc: "One Path in matches and another empty", + routeRule: v1alpha1.HTTPRouteRule{ + Matches: []v1alpha1.HTTPRouteMatch{ + { + Path: v1alpha1.HTTPPathMatch{ + Type: v1alpha1.PathMatchExact, + Value: "/foo/", + }, + }, + {}, + }, + }, + expectedRule: "Path(`/foo/`)", + }, + { + desc: "Path OR Header rules", + routeRule: v1alpha1.HTTPRouteRule{ + Matches: []v1alpha1.HTTPRouteMatch{ + { + Path: v1alpha1.HTTPPathMatch{ + Type: v1alpha1.PathMatchExact, + Value: "/foo/", + }, + }, + { + Headers: &v1alpha1.HTTPHeaderMatch{ + Type: v1alpha1.HeaderMatchExact, + Values: map[string]string{ + "my-header": "foo", + }, + }, + }, + }, + }, + expectedRule: "Path(`/foo/`) || Headers(`my-header`,`foo`)", + }, + { + desc: "Path && Header rules", + routeRule: v1alpha1.HTTPRouteRule{ + Matches: []v1alpha1.HTTPRouteMatch{ + { + Path: v1alpha1.HTTPPathMatch{ + Type: v1alpha1.PathMatchExact, + Value: "/foo/", + }, + Headers: &v1alpha1.HTTPHeaderMatch{ + Type: v1alpha1.HeaderMatchExact, + Values: map[string]string{ + "my-header": "foo", + }, + }, + }, + }, + }, + expectedRule: "Path(`/foo/`) && Headers(`my-header`,`foo`)", + }, + { + desc: "Host && Path && Header rules", + hostRule: "Host(`foo.com`)", + routeRule: v1alpha1.HTTPRouteRule{ + Matches: []v1alpha1.HTTPRouteMatch{ + { + Path: v1alpha1.HTTPPathMatch{ + Type: v1alpha1.PathMatchExact, + Value: "/foo/", + }, + Headers: &v1alpha1.HTTPHeaderMatch{ + Type: v1alpha1.HeaderMatchExact, + Values: map[string]string{ + "my-header": "foo", + }, + }, + }, + }, + }, + expectedRule: "Host(`foo.com`) && Path(`/foo/`) && Headers(`my-header`,`foo`)", + }, + { + desc: "Host && (Path || Header) rules", + hostRule: "Host(`foo.com`)", + routeRule: v1alpha1.HTTPRouteRule{ + Matches: []v1alpha1.HTTPRouteMatch{ + { + Path: v1alpha1.HTTPPathMatch{ + Type: v1alpha1.PathMatchExact, + Value: "/foo/", + }, + }, + { + Headers: &v1alpha1.HTTPHeaderMatch{ + Type: v1alpha1.HeaderMatchExact, + Values: map[string]string{ + "my-header": "foo", + }, + }, + }, + }, + }, + expectedRule: "Host(`foo.com`) && (Path(`/foo/`) || Headers(`my-header`,`foo`))", + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + rule, err := extractRule(test.routeRule, test.hostRule) + if test.expectedError { + assert.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, test.expectedRule, rule) + }) + } +} diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index 0480fddaf..e9f37b7d7 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -36,16 +36,15 @@ const ( // Provider holds configurations of the provider. type Provider struct { - Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` - Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"` - CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"` - DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers." json:"disablePassHostHeaders,omitempty" toml:"disablePassHostHeaders,omitempty" yaml:"disablePassHostHeaders,omitempty" export:"true"` - 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"` - 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 + Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` + Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"` + 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"` + 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 } // EndpointIngress holds the endpoint information for the Kubernetes provider. diff --git a/pkg/provider/kubernetes/k8s/parser.go b/pkg/provider/kubernetes/k8s/parser.go index ea6c60202..c424ca303 100644 --- a/pkg/provider/kubernetes/k8s/parser.go +++ b/pkg/provider/kubernetes/k8s/parser.go @@ -12,7 +12,7 @@ import ( // MustParseYaml parses a YAML to objects. func MustParseYaml(content []byte) []runtime.Object { - acceptedK8sTypes := regexp.MustCompile(`^(Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport)$`) + acceptedK8sTypes := regexp.MustCompile(`^(Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|GatewayClass|Gateway|HTTPRoute)$`) files := strings.Split(string(content), "---") retVal := make([]runtime.Object, 0, len(files)) diff --git a/pkg/provider/marathon/config_test.go b/pkg/provider/marathon/config_test.go index 6f4dac9b7..f69b47530 100644 --- a/pkg/provider/marathon/config_test.go +++ b/pkg/provider/marathon/config_test.go @@ -868,9 +868,6 @@ func TestBuildConfiguration(t *testing.T) { { URL: "http://localhost:80", }, - { - URL: "http://localhost:80", - }, }, PassHostHeader: Bool(true), }, diff --git a/webui/src/statics/providers/kubernetesgateway.svg b/webui/src/statics/providers/kubernetesgateway.svg new file mode 100644 index 000000000..b6670fe5d --- /dev/null +++ b/webui/src/statics/providers/kubernetesgateway.svg @@ -0,0 +1,6 @@ + + + + + +