diff --git a/pkg/apis/projectcontour/v1/detailedconditions.go b/pkg/apis/projectcontour/v1/detailedconditions.go new file mode 100644 index 000000000..5f5a27ad3 --- /dev/null +++ b/pkg/apis/projectcontour/v1/detailedconditions.go @@ -0,0 +1,190 @@ +// Copyright Project Contour Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +k8s:deepcopy-gen=package + +// Package v1 is the v1 version of the API. +// +groupName=projectcontour.io +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ConditionStatus is a type alias for the k8s.io/apimachinery/pkg/apis/meta/v1 +// ConditionStatus type to maintain API compatibility. +// +k8s:deepcopy-gen=false +type ConditionStatus = metav1.ConditionStatus + +// These are valid condition statuses. "ConditionTrue" means a resource is in the condition. +// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes +// can't decide if a resource is in the condition or not. In the future, we could add other +// intermediate conditions, e.g. ConditionDegraded. These are retained here for API compatibility. +const ( + ConditionTrue ConditionStatus = metav1.ConditionTrue + ConditionFalse ConditionStatus = metav1.ConditionFalse + ConditionUnknown ConditionStatus = metav1.ConditionUnknown +) + +// Condition is a type alias for the k8s.io/apimachinery/pkg/apis/meta/v1 +// Condition type to maintain API compatibility. +// +k8s:deepcopy-gen=false +type Condition = metav1.Condition + +// SubCondition is a Condition-like type intended for use as a subcondition inside a DetailedCondition. +// +// It contains a subset of the Condition fields. +// +// It is intended for warnings and errors, so `type` names should use abnormal-true polarity, +// that is, they should be of the form "ErrorPresent: true". +// +// The expected lifecycle for these errors is that they should only be present when the error or warning is, +// and should be removed when they are not relevant. +type SubCondition struct { + // Type of condition in `CamelCase` or in `foo.example.com/CamelCase`. + // + // This must be in abnormal-true polarity, that is, `ErrorFound` or `controller.io/ErrorFound`. + // + // The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation: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])$` + // +kubebuilder:validation:MaxLength=316 + Type string `json:"type" protobuf:"bytes,1,opt,name=type"` + // Status of the condition, one of True, False, Unknown. + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:Enum=True;False;Unknown + Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status"` + // 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. + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:MaxLength=1024 + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:Pattern=`^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$` + Reason string `json:"reason" protobuf:"bytes,3,opt,name=reason"` + // Message is a human readable message indicating details about the transition. + // + // This may be an empty string. + // + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:MaxLength=32768 + Message string `json:"message" protobuf:"bytes,4,opt,name=message"` +} + +// DetailedCondition is an extension of the normal Kubernetes conditions, with two extra +// fields to hold sub-conditions, which provide more detailed reasons for the state (True or False) +// of the condition. +// +// `errors` holds information about sub-conditions which are fatal to that condition and render its state False. +// +// `warnings` holds information about sub-conditions which are not fatal to that condition and do not force the state to be False. +// +// Remember that Conditions have a type, a status, and a reason. +// +// The type is the type of the condition, the most important one in this CRD set is `Valid`. +// `Valid` is a positive-polarity condition: when it is `status: true` there are no problems. +// +// In more detail, `status: true` means that the object is has been ingested into Contour with no errors. +// `warnings` may still be present, and will be indicated in the Reason field. There must be zero entries in the `errors` +// slice in this case. +// +// `Valid`, `status: false` means that the object has had one or more fatal errors during processing into Contour. +// The details of the errors will be present under the `errors` field. There must be at least one error in the `errors` +// slice if `status` is `false`. +// +// For DetailedConditions of types other than `Valid`, the Condition must be in the negative polarity. +// When they have `status` `true`, there is an error. There must be at least one entry in the `errors` Subcondition slice. +// When they have `status` `false`, there are no serious errors, and there must be zero entries in the `errors` slice. +// In either case, there may be entries in the `warnings` slice. +// +// Regardless of the polarity, the `reason` and `message` fields must be updated with either the detail of the reason +// (if there is one and only one entry in total across both the `errors` and `warnings` slices), or +// `MultipleReasons` if there is more than one entry. +type DetailedCondition struct { + Condition `json:",inline"` + // Errors contains a slice of relevant error subconditions for this object. + // + // Subconditions are expected to appear when relevant (when there is a error), and disappear when not relevant. + // An empty slice here indicates no errors. + // +optional + Errors []SubCondition `json:"errors,omitempty"` + // Warnings contains a slice of relevant warning subconditions for this object. + // + // Subconditions are expected to appear when relevant (when there is a warning), and disappear when not relevant. + // An empty slice here indicates no warnings. + // +optional + Warnings []SubCondition `json:"warnings,omitempty"` +} + +const ( + // ValidConditionType describes an valid condition. + ValidConditionType = "Valid" + + // ConditionTypeAuthError describes an error condition related to Auth. + ConditionTypeAuthError = "AuthError" + + // ConditionTypeCORSError describes an error condition related to CORS. + ConditionTypeCORSError = "CORSError" + + // ConditionTypeIncludeError describes an error condition with + // inclusion of another HTTPProxy resource. + ConditionTypeIncludeError = "IncludeError" + + // ConditionTypeOrphanedError describes an error condition + // with an HTTPProxy resource which is not part of a delegation chain. + ConditionTypeOrphanedError = "Orphaned" + + // ConditionTypePrefixReplaceError describes an error condition with + // an HTTPProxy path prefix replacement issue. + ConditionTypePrefixReplaceError = "PrefixReplaceError" + + // ConditionTypeRootNamespaceError describes an error condition + // with an HTTPProxy resource created in non-root namespace. + ConditionTypeRootNamespaceError = "RootNamespaceError" + + // ConditionTypeRouteError describes an error condition that + // relates to Routes within an HTTPProxy. + ConditionTypeRouteError = "RouteError" + + // ConditionTypeServiceError describes an error condition that + // relates to a Service error within an HTTPProxy. + ConditionTypeServiceError = "ServiceError" + + // ConditionTypeSpecError describes an error condition that + // relates to the Spec of an HTTPProxy resource. + ConditionTypeSpecError = "SpecError" + + // ConditionTypeTCPProxyIncludeError describes an error condition + // with inclusion of another HTTPProxy TCP Proxy resource. + ConditionTypeTCPProxyIncludeError = "TCPProxyIncludeError" + + // ConditionTypeTCPProxyError describes an error condition relating + // to a TCP Proxy HTTPProxy resource. + ConditionTypeTCPProxyError = "TCPProxyError" + + // ConditionTypeTLSError describes an error condition relating + // to TLS configuration. + ConditionTypeTLSError = "TLSError" + + // ConditionTypeVirtualHostError describes an error condition relating + // to the VirtualHost configuration section of an HTTPProxy resource. + ConditionTypeVirtualHostError = "VirtualHostError" +) diff --git a/pkg/apis/projectcontour/v1/httpproxy.go b/pkg/apis/projectcontour/v1/httpproxy.go index 59ad2e682..4eefd53a7 100644 --- a/pkg/apis/projectcontour/v1/httpproxy.go +++ b/pkg/apis/projectcontour/v1/httpproxy.go @@ -1,4 +1,4 @@ -// Copyright © 2019 VMware +// Copyright Project Contour Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -14,13 +14,14 @@ package v1 import ( + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // HTTPProxySpec defines the spec of the CRD. type HTTPProxySpec struct { // Virtualhost appears at most once. If it is present, the object is considered - // to be a "root". + // to be a "root" HTTPProxy. // +optional VirtualHost *VirtualHost `json:"virtualhost,omitempty"` // Routes are the ingress routes. If TCPProxy is present, Routes is ignored. @@ -29,9 +30,16 @@ type HTTPProxySpec struct { // TCPProxy holds TCP proxy information. // +optional TCPProxy *TCPProxy `json:"tcpproxy,omitempty"` - // Includes allow for specific routing configuration to be appended to another HTTPProxy in another namespace. + // Includes allow for specific routing configuration to be included from another HTTPProxy, + // possibly in another namespace. // +optional Includes []Include `json:"includes,omitempty"` + // IngressClassName optionally specifies the ingress class to use for this + // HTTPProxy. This replaces the deprecated `kubernetes.io/ingress.class` + // annotation. For backwards compatibility, when that annotation is set, it + // is given precedence over this field. + // +optional + IngressClassName string `json:"ingressClassName,omitempty"` } // Include describes a set of policies that can be applied to an HTTPProxy in a namespace. @@ -41,92 +49,285 @@ type Include struct { // Namespace of the HTTPProxy to include. Defaults to the current namespace if not supplied. // +optional Namespace string `json:"namespace,omitempty"` - // Conditions are a set of routing properties that is applied to an HTTPProxy in a namespace. + // Conditions are a set of rules that are applied to included HTTPProxies. + // In effect, they are added onto the Conditions of included HTTPProxy Route + // structs. + // When applied, they are merged using AND, with one exception: + // There can be only one Prefix MatchCondition per Conditions slice. + // More than one Prefix, or contradictory Conditions, will make the + // include invalid. // +optional - Conditions []Condition `json:"conditions,omitempty"` + Conditions []MatchCondition `json:"conditions,omitempty"` } -// Condition are policies that are applied on top of HTTPProxies. +// MatchCondition are a general holder for matching rules for HTTPProxies. // One of Prefix or Header must be provided. -type Condition struct { +type MatchCondition struct { // Prefix defines a prefix match for a request. // +optional Prefix string `json:"prefix,omitempty"` // Header specifies the header condition to match. // +optional - Header *HeaderCondition `json:"header,omitempty"` + Header *HeaderMatchCondition `json:"header,omitempty"` } -// HeaderCondition specifies the header condition to match. -// Name is required. Only one of Present or Contains must -// be provided. -type HeaderCondition struct { - - // Name is the name of the header to match on. Name is required. +// HeaderMatchCondition specifies how to conditionally match against HTTP +// headers. The Name field is required, but only one of the remaining +// fields should be be provided. +type HeaderMatchCondition struct { + // Name is the name of the header to match against. Name is required. // Header names are case insensitive. Name string `json:"name"` - // Present is true if the Header is present in the request. + // Present specifies that condition is true when the named header + // is present, regardless of its value. Note that setting Present + // to false does not make the condition true if the named header + // is absent. // +optional Present bool `json:"present,omitempty"` - // Contains is true if the Header containing this string is present - // in the request. + // NotPresent specifies that condition is true when the named header + // is not present. Note that setting NotPresent to false does not + // make the condition true if the named header is present. + // +optional + NotPresent bool `json:"notpresent,omitempty"` + + // Contains specifies a substring that must be present in + // the header value. // +optional Contains string `json:"contains,omitempty"` - // NotContains is true if the Header containing this string is not present - // in the request. + // NotContains specifies a substring that must not be present + // in the header value. // +optional NotContains string `json:"notcontains,omitempty"` - // Exact is true if the Header containing this string matches exactly - // in the request. + // Exact specifies a string that the header value must be equal to. // +optional Exact string `json:"exact,omitempty"` - // NotExact is true if the Header containing this string doesn't match exactly - // in the request. + // NoExact specifies a string that the header value must not be + // equal to. The condition is true if the header has any other value. // +optional NotExact string `json:"notexact,omitempty"` } +// ExtensionServiceReference names an ExtensionService resource. +type ExtensionServiceReference struct { + // API version of the referent. + // If this field is not specified, the default "projectcontour.io/v1alpha1" will be used + // + // +optional + // +kubebuilder:validation:MinLength=1 + APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,5,opt,name=apiVersion"` + + // Namespace of the referent. + // If this field is not specifies, the namespace of the resource that targets the referent will be used. + // + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + // + // +optional + // +kubebuilder:validation:MinLength=1 + Namespace string `json:"namespace,omitempty" protobuf:"bytes,2,opt,name=namespace"` + + // Name of the referent. + // + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + // + // +required + // +kubebuilder:validation:MinLength=1 + Name string `json:"name,omitempty" protobuf:"bytes,3,opt,name=name"` +} + +// AuthorizationServer configures an external server to authenticate +// client requests. The external server must implement the v3 Envoy +// external authorization GRPC protocol (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto). +type AuthorizationServer struct { + // ExtensionServiceRef specifies the extension resource that will authorize client requests. + // + // +required + ExtensionServiceRef ExtensionServiceReference `json:"extensionRef"` + + // AuthPolicy sets a default authorization policy for client requests. + // This policy will be used unless overridden by individual routes. + // + // +optional + AuthPolicy *AuthorizationPolicy `json:"authPolicy,omitempty"` + + // ResponseTimeout configures maximum time to wait for a check response from the authorization server. + // Timeout durations are expressed in the Go [Duration format](https://godoc.org/time#ParseDuration). + // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + // The string "infinity" is also a valid input and specifies no timeout. + // + // +optional + // +kubebuilder:validation:Pattern=`^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$` + ResponseTimeout string `json:"responseTimeout,omitempty"` + + // If FailOpen is true, the client request is forwarded to the upstream service + // even if the authorization server fails to respond. This field should not be + // set in most cases. It is intended for use only while migrating applications + // from internal authorization to Contour external authorization. + // + // +optional + FailOpen bool `json:"failOpen,omitempty"` + + // WithRequestBody specifies configuration for sending the client request's body to authorization server. + // +optional + WithRequestBody *AuthorizationServerBufferSettings `json:"withRequestBody,omitempty"` +} + +// AuthorizationServerBufferSettings enables ExtAuthz filter to buffer client request data and send it as part of authorization request +type AuthorizationServerBufferSettings struct { + // MaxRequestBytes sets the maximum size of message body ExtAuthz filter will hold in-memory. + // +optional + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:default=1024 + MaxRequestBytes uint32 `json:"maxRequestBytes,omitempty"` + + // If AllowPartialMessage is true, then Envoy will buffer the body until MaxRequestBytes are reached. + // +optional + AllowPartialMessage bool `json:"allowPartialMessage,omitempty"` + + // If PackAsBytes is true, the body sent to Authorization Server is in raw bytes. + // +optional + PackAsBytes bool `json:"packAsBytes,omitempty"` +} + +// AuthorizationPolicy modifies how client requests are authenticated. +type AuthorizationPolicy struct { + // When true, this field disables client request authentication + // for the scope of the policy. + // + // +optional + Disabled bool `json:"disabled,omitempty"` + + // Context is a set of key/value pairs that are sent to the + // authentication server in the check request. If a context + // is provided at an enclosing scope, the entries are merged + // such that the inner scope overrides matching keys from the + // outer scope. + // + // +optional + Context map[string]string `json:"context,omitempty"` +} + // VirtualHost appears at most once. If it is present, the object is considered // to be a "root". type VirtualHost struct { // The fully qualified domain name of the root of the ingress tree - // all leaves of the DAG rooted at this object relate to the fqdn + // all leaves of the DAG rooted at this object relate to the fqdn. + // + // +kubebuilder:validation:Pattern="^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$" Fqdn string `json:"fqdn"` - // If present describes tls properties. The CNI names that will be matched on - // are described in fqdn, the tls.secretName secret must contain a - // matching certificate + + // If present the fields describes TLS properties of the virtual + // host. The SNI names that will be matched on are described in fqdn, + // the tls.secretName secret must contain a certificate that itself + // contains a name that matches the FQDN. + // // +optional TLS *TLS `json:"tls,omitempty"` + + // This field configures an extension service to perform + // authorization for this virtual host. Authorization can + // only be configured on virtual hosts that have TLS enabled. + // If the TLS configuration requires client certificate + // validation, the client certificate is always included in the + // authentication check request. + // + // +optional + Authorization *AuthorizationServer `json:"authorization,omitempty"` + // Specifies the cross-origin policy to apply to the VirtualHost. + // +optional + CORSPolicy *CORSPolicy `json:"corsPolicy,omitempty"` + // The policy for rate limiting on the virtual host. + // +optional + RateLimitPolicy *RateLimitPolicy `json:"rateLimitPolicy,omitempty"` } -// TLS describes tls properties. The CNI names that will be matched on -// are described in fqdn, the tls.secretName secret must contain a -// matching certificate unless tls.passthrough is set to true. +// TLS describes tls properties. The SNI names that will be matched on +// are described in the HTTPProxy's Spec.VirtualHost.Fqdn field. type TLS struct { - // required, the name of a secret in the current namespace + // SecretName is the name of a TLS secret in the current namespace. + // Either SecretName or Passthrough must be specified, but not both. + // If specified, the named secret must contain a matching certificate + // for the virtual host's FQDN. SecretName string `json:"secretName,omitempty"` - // Minimum TLS version this vhost should negotiate + // MinimumProtocolVersion is the minimum TLS version this vhost should + // negotiate. Valid options are `1.2` (default) and `1.3`. Any other value + // defaults to TLS 1.2. // +optional MinimumProtocolVersion string `json:"minimumProtocolVersion,omitempty"` - // If Passthrough is set to true, the SecretName will be ignored - // and the encrypted handshake will be passed through to the - // backing cluster. + // Passthrough defines whether the encrypted TLS handshake will be + // passed through to the backing cluster. Either Passthrough or + // SecretName must be specified, but not both. // +optional Passthrough bool `json:"passthrough,omitempty"` + // ClientValidation defines how to verify the client certificate + // when an external client establishes a TLS connection to Envoy. + // + // This setting: + // + // 1. Enables TLS client certificate validation. + // 2. Specifies how the client certificate will be validated (i.e. + // validation required or skipped). + // + // Note: Setting client certificate validation to be skipped should + // be only used in conjunction with an external authorization server that + // performs client validation as Contour will ensure client certificates + // are passed along. + // + // +optional + ClientValidation *DownstreamValidation `json:"clientValidation,omitempty"` + + // EnableFallbackCertificate defines if the vhost should allow a default certificate to + // be applied which handles all requests which don't match the SNI defined in this vhost. + EnableFallbackCertificate bool `json:"enableFallbackCertificate,omitempty"` +} + +// CORSHeaderValue specifies the value of the string headers returned by a cross-domain request. +// +kubebuilder:validation:Pattern="^[a-zA-Z0-9!#$%&'*+.^_`|~-]+$" +type CORSHeaderValue string + +// CORSPolicy allows setting the CORS policy +type CORSPolicy struct { + // Specifies whether the resource allows credentials. + // +optional + AllowCredentials bool `json:"allowCredentials,omitempty"` + // AllowOrigin specifies the origins that will be allowed to do CORS requests. "*" means + // allow any origin. + // +kubebuilder:validation:Required + AllowOrigin []string `json:"allowOrigin"` + // AllowMethods specifies the content for the *access-control-allow-methods* header. + // +kubebuilder:validation:Required + AllowMethods []CORSHeaderValue `json:"allowMethods"` + // AllowHeaders specifies the content for the *access-control-allow-headers* header. + // +optional + AllowHeaders []CORSHeaderValue `json:"allowHeaders,omitempty"` + // ExposeHeaders Specifies the content for the *access-control-expose-headers* header. + // +optional + ExposeHeaders []CORSHeaderValue `json:"exposeHeaders,omitempty"` + // MaxAge indicates for how long the results of a preflight request can be cached. + // MaxAge durations are expressed in the Go [Duration format](https://godoc.org/time#ParseDuration). + // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + // Only positive values are allowed while 0 disables the cache requiring a preflight OPTIONS + // check for all cross-origin requests. + // +optional + MaxAge string `json:"maxAge,omitempty"` } // Route contains the set of routes for a virtual host. type Route struct { - // Conditions are a set of routing properties that is applied to an HTTPProxy in a namespace. + // Conditions are a set of rules that are applied to a Route. + // When applied, they are merged using AND, with one exception: + // There can be only one Prefix MatchCondition per Conditions slice. + // More than one Prefix, or contradictory Conditions, will make the + // route invalid. // +optional - Conditions []Condition `json:"conditions,omitempty"` + Conditions []MatchCondition `json:"conditions,omitempty"` // Services are the services to proxy traffic. + // +optional Services []Service `json:"services,omitempty"` // Enables websocket support for the route. // +optional @@ -135,6 +336,11 @@ type Route struct { // not permitted when a `virtualhost.tls` block is present. // +optional PermitInsecure bool `json:"permitInsecure,omitempty"` + // AuthPolicy updates the authorization policy that was set + // on the root HTTPProxy object for client requests that + // match this route. + // +optional + AuthPolicy *AuthorizationPolicy `json:"authPolicy,omitempty"` // The timeout policy for this route. // +optional TimeoutPolicy *TimeoutPolicy `json:"timeoutPolicy,omitempty"` @@ -152,32 +358,300 @@ type Route struct { // // +optional PathRewritePolicy *PathRewritePolicy `json:"pathRewritePolicy,omitempty"` - // The policy for managing request headers during proxying + // The policy for managing request headers during proxying. // +optional RequestHeadersPolicy *HeadersPolicy `json:"requestHeadersPolicy,omitempty"` - // The policy for managing response headers during proxying + // The policy for managing response headers during proxying. + // Rewriting the 'Host' header is not supported. // +optional ResponseHeadersPolicy *HeadersPolicy `json:"responseHeadersPolicy,omitempty"` + // The policies for rewriting Set-Cookie header attributes. Note that + // rewritten cookie names must be unique in this list. Order rewrite + // policies are specified in does not matter. + // +optional + CookieRewritePolicies []CookieRewritePolicy `json:"cookieRewritePolicies,omitempty"` + // The policy for rate limiting on the route. + // +optional + RateLimitPolicy *RateLimitPolicy `json:"rateLimitPolicy,omitempty"` + + // RequestRedirectPolicy defines an HTTP redirection. + // +optional + RequestRedirectPolicy *HTTPRequestRedirectPolicy `json:"requestRedirectPolicy,omitempty"` +} + +// HTTPRequestRedirectPolicy defines configuration for redirecting a request. +type HTTPRequestRedirectPolicy struct { + // Scheme is the scheme to be used in the value of the `Location` + // header in the response. + // When empty, the scheme of the request is used. + // +optional + // +kubebuilder:validation:Enum=http;https + Scheme *string `json:"scheme,omitempty"` + + // Hostname is the precise hostname to be used in the value of the `Location` + // header in the response. + // When empty, the hostname of the request is used. + // No wildcards are allowed. + // +optional + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` + Hostname *string `json:"hostname,omitempty"` + + // Port is the port to be used in the value of the `Location` + // header in the response. + // When empty, port (if specified) of the request is used. + // +optional + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + Port *int32 `json:"port,omitempty"` + + // StatusCode is the HTTP status code to be used in response. + // +optional + // +kubebuilder:default=302 + // +kubebuilder:validation:Enum=301;302 + StatusCode *int `json:"statusCode,omitempty"` + + // Path allows for redirection to a different path from the + // original on the request. The path must start with a + // leading slash. + // + // Note: Only one of Path or Prefix can be defined. + // + // +optional + // +kubebuilder:validation:Pattern=`^\/.*$` + Path *string `json:"path,omitempty"` + + // Prefix defines the value to swap the matched prefix or path with. + // The prefix must start with a leading slash. + // + // Note: Only one of Path or Prefix can be defined. + // + // +optional + // +kubebuilder:validation:Pattern=`^\/.*$` + Prefix *string `json:"prefix,omitempty"` +} + +type CookieRewritePolicy struct { + // Name is the name of the cookie for which attributes will be rewritten. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=4096 + // +kubebuilder:validation:Pattern=`^[^()<>@,;:\\"\/[\]?={} \t\x7f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]+$` + Name string `json:"name"` + + // PathRewrite enables rewriting the Set-Cookie Path element. + // If not set, Path will not be rewritten. + // +optional + PathRewrite *CookiePathRewrite `json:"pathRewrite,omitempty"` + + // DomainRewrite enables rewriting the Set-Cookie Domain element. + // If not set, Domain will not be rewritten. + // +optional + DomainRewrite *CookieDomainRewrite `json:"domainRewrite,omitempty"` + + // Secure enables rewriting the Set-Cookie Secure element. + // If not set, Secure attribute will not be rewritten. + // +optional + Secure *bool `json:"secure,omitempty"` + + // SameSite enables rewriting the Set-Cookie SameSite element. + // If not set, SameSite attribute will not be rewritten. + // +optional + // +kubebuilder:validation:Enum=Strict;Lax;None + SameSite *string `json:"sameSite,omitempty"` } -func (r *Route) GetPrefixReplacements() []ReplacePrefix { - if r.PathRewritePolicy != nil { - return r.PathRewritePolicy.ReplacePrefix - } - return nil +type CookiePathRewrite struct { + // Value is the value to rewrite the Path attribute to. + // For now this is required. + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=4096 + // +kubebuilder:validation:Pattern=`^[^;\x7f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]+$` + Value string `json:"value"` +} + +type CookieDomainRewrite struct { + // Value is the value to rewrite the Domain attribute to. + // For now this is required. + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=4096 + // +kubebuilder:validation:Pattern="^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$" + Value string `json:"value"` +} + +// RateLimitPolicy defines rate limiting parameters. +type RateLimitPolicy struct { + // Local defines local rate limiting parameters, i.e. parameters + // for rate limiting that occurs within each Envoy pod as requests + // are handled. + // +optional + Local *LocalRateLimitPolicy `json:"local,omitempty"` + + // Global defines global rate limiting parameters, i.e. parameters + // defining descriptors that are sent to an external rate limit + // service (RLS) for a rate limit decision on each request. + // +optional + Global *GlobalRateLimitPolicy `json:"global,omitempty"` } +// LocalRateLimitPolicy defines local rate limiting parameters. +type LocalRateLimitPolicy struct { + // Requests defines how many requests per unit of time should + // be allowed before rate limiting occurs. + // +required + // +kubebuilder:validation:Minimum=1 + Requests uint32 `json:"requests"` + + // Unit defines the period of time within which requests + // over the limit will be rate limited. Valid values are + // "second", "minute" and "hour". + // +kubebuilder:validation:Enum=second;minute;hour + // +required + Unit string `json:"unit"` + + // Burst defines the number of requests above the requests per + // unit that should be allowed within a short period of time. + // +optional + Burst uint32 `json:"burst,omitempty"` + + // ResponseStatusCode is the HTTP status code to use for responses + // to rate-limited requests. Codes must be in the 400-599 range + // (inclusive). If not specified, the Envoy default of 429 (Too + // Many Requests) is used. + // +optional + // +kubebuilder:validation:Minimum=400 + // +kubebuilder:validation:Maximum=599 + ResponseStatusCode uint32 `json:"responseStatusCode,omitempty"` + + // ResponseHeadersToAdd is an optional list of response headers to + // set when a request is rate-limited. + // +optional + ResponseHeadersToAdd []HeaderValue `json:"responseHeadersToAdd,omitempty"` +} + +// GlobalRateLimitPolicy defines global rate limiting parameters. +type GlobalRateLimitPolicy struct { + // Descriptors defines the list of descriptors that will + // be generated and sent to the rate limit service. Each + // descriptor contains 1+ key-value pair entries. + // +required + // +kubebuilder:validation:MinItems=1 + Descriptors []RateLimitDescriptor `json:"descriptors,omitempty"` +} + +// RateLimitDescriptor defines a list of key-value pair generators. +type RateLimitDescriptor struct { + // Entries is the list of key-value pair generators. + // +required + // +kubebuilder:validation:MinItems=1 + Entries []RateLimitDescriptorEntry `json:"entries,omitempty"` +} + +// RateLimitDescriptorEntry is a key-value pair generator. Exactly +// one field on this struct must be non-nil. +type RateLimitDescriptorEntry struct { + // GenericKey defines a descriptor entry with a static key and value. + // +optional + GenericKey *GenericKeyDescriptor `json:"genericKey,omitempty"` + + // RequestHeader defines a descriptor entry that's populated only if + // a given header is present on the request. The descriptor key is static, + // and the descriptor value is equal to the value of the header. + // +optional + RequestHeader *RequestHeaderDescriptor `json:"requestHeader,omitempty"` + + // RequestHeaderValueMatch defines a descriptor entry that's populated + // if the request's headers match a set of 1+ match criteria. The + // descriptor key is "header_match", and the descriptor value is static. + // +optional + RequestHeaderValueMatch *RequestHeaderValueMatchDescriptor `json:"requestHeaderValueMatch,omitempty"` + + // RemoteAddress defines a descriptor entry with a key of "remote_address" + // and a value equal to the client's IP address (from x-forwarded-for). + // +optional + RemoteAddress *RemoteAddressDescriptor `json:"remoteAddress,omitempty"` +} + +// GenericKeyDescriptor defines a descriptor entry with a static key and +// value. +type GenericKeyDescriptor struct { + // Key defines the key of the descriptor entry. If not set, the + // key is set to "generic_key". + // +optional + Key string `json:"key,omitempty"` + + // Value defines the value of the descriptor entry. + // +required + // +kubebuilder:validation:MinLength=1 + Value string `json:"value,omitempty"` +} + +// RequestHeaderDescriptor defines a descriptor entry that's populated only +// if a given header is present on the request. The value of the descriptor +// entry is equal to the value of the header (if present). +type RequestHeaderDescriptor struct { + // HeaderName defines the name of the header to look for on the request. + // +required + // +kubebuilder:validation:MinLength=1 + HeaderName string `json:"headerName,omitempty"` + + // DescriptorKey defines the key to use on the descriptor entry. + // +required + // +kubebuilder:validation:MinLength=1 + DescriptorKey string `json:"descriptorKey,omitempty"` +} + +// RequestHeaderValueMatchDescriptor defines a descriptor entry that's populated +// if the request's headers match a set of 1+ match criteria. The descriptor key +// is "header_match", and the descriptor value is statically defined. +type RequestHeaderValueMatchDescriptor struct { + // Headers is a list of 1+ match criteria to apply against the request + // to determine whether to populate the descriptor entry or not. + // +kubebuilder:validation:MinItems=1 + Headers []HeaderMatchCondition `json:"headers,omitempty"` + + // ExpectMatch defines whether the request must positively match the match + // criteria in order to generate a descriptor entry (i.e. true), or not + // match the match criteria in order to generate a descriptor entry (i.e. false). + // The default is true. + // +kubebuilder:default=true + ExpectMatch bool `json:"expectMatch,omitempty"` + + // Value defines the value of the descriptor entry. + // +required + // +kubebuilder:validation:MinLength=1 + Value string `json:"value,omitempty"` +} + +// RemoteAddressDescriptor defines a descriptor entry with a key of +// "remote_address" and a value equal to the client's IP address +// (from x-forwarded-for). +type RemoteAddressDescriptor struct{} + // TCPProxy contains the set of services to proxy TCP connections. type TCPProxy struct { - // The load balancing policy for the backend services. + // The load balancing policy for the backend services. Note that the + // `Cookie` and `RequestHash` load balancing strategies cannot be used + // here. // +optional LoadBalancerPolicy *LoadBalancerPolicy `json:"loadBalancerPolicy,omitempty"` // Services are the services to proxy traffic - Services []Service `json:"services,omitempty"` - + // +optional + Services []Service `json:"services"` // Include specifies that this tcpproxy should be delegated to another HTTPProxy. // +optional - Include *TCPProxyInclude `json:"includes,omitempty"` + Include *TCPProxyInclude `json:"include,omitempty"` + // IncludesDeprecated allow for specific routing configuration to be appended to another HTTPProxy in another namespace. + // + // Exists due to a mistake when developing HTTPProxy and the field was marked plural + // when it should have been singular. This field should stay to not break backwards compatibility to v1 users. + // +optional + IncludesDeprecated *TCPProxyInclude `json:"includes,omitempty"` + // The health check policy for this tcp proxy + // +optional + HealthCheckPolicy *TCPHealthCheckPolicy `json:"healthCheckPolicy,omitempty"` } // TCPProxyInclude describes a target HTTPProxy document which contains the TCPProxy details. @@ -195,25 +669,38 @@ type Service struct { // Names defined here will be used to look up corresponding endpoints which contain the ips to route. Name string `json:"name"` // Port (defined as Integer) to proxy traffic to since a service can have multiple defined. + // + // +required + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65536 + // +kubebuilder:validation:ExclusiveMinimum=false + // +kubebuilder:validation:ExclusiveMaximum=true Port int `json:"port"` // Protocol may be used to specify (or override) the protocol used to reach this Service. - // Values may be tls, h2, h2c. It ommitted protocol-selection falls back on Service annotations. + // Values may be tls, h2, h2c. If omitted, protocol-selection falls back on Service annotations. + // +kubebuilder:validation:Enum=h2;h2c;tls // +optional Protocol *string `json:"protocol,omitempty"` // Weight defines percentage of traffic to balance traffic // +optional - Weight uint32 `json:"weight,omitempty"` + // +kubebuilder:validation:Minimum=0 + Weight int64 `json:"weight,omitempty"` // UpstreamValidation defines how to verify the backend service's certificate // +optional UpstreamValidation *UpstreamValidation `json:"validation,omitempty"` // If Mirror is true the Service will receive a read only mirror of the traffic for this route. Mirror bool `json:"mirror,omitempty"` - // The policy for managing request headers during proxying + // The policy for managing request headers during proxying. + // Rewriting the 'Host' header is not supported. // +optional RequestHeadersPolicy *HeadersPolicy `json:"requestHeadersPolicy,omitempty"` - // The policy for managing response headers during proxying + // The policy for managing response headers during proxying. + // Rewriting the 'Host' header is not supported. // +optional ResponseHeadersPolicy *HeadersPolicy `json:"responseHeadersPolicy,omitempty"` + // The policies for rewriting Set-Cookie header attributes. + // +optional + CookieRewritePolicies []CookieRewritePolicy `json:"cookieRewritePolicies,omitempty"` } // HTTPHealthCheckPolicy defines health checks on the upstream service. @@ -224,6 +711,24 @@ type HTTPHealthCheckPolicy struct { // If left empty (default value), the name "contour-envoy-healthcheck" // will be used. Host string `json:"host,omitempty"` + // The interval (seconds) between health checks + // +optional + IntervalSeconds int64 `json:"intervalSeconds"` + // The time to wait (seconds) for a health check response + // +optional + TimeoutSeconds int64 `json:"timeoutSeconds"` + // The number of unhealthy health checks required before a host is marked unhealthy + // +optional + // +kubebuilder:validation:Minimum=0 + UnhealthyThresholdCount int64 `json:"unhealthyThresholdCount"` + // The number of healthy health checks required before a host is marked healthy + // +optional + // +kubebuilder:validation:Minimum=0 + HealthyThresholdCount int64 `json:"healthyThresholdCount"` +} + +// TCPHealthCheckPolicy defines health checks on the upstream service. +type TCPHealthCheckPolicy struct { // The interval (seconds) between health checks // +optional IntervalSeconds int64 `json:"intervalSeconds"` @@ -238,39 +743,89 @@ type HTTPHealthCheckPolicy struct { HealthyThresholdCount uint32 `json:"healthyThresholdCount"` } -// TimeoutPolicy defines the attributes associated with timeout. +// TimeoutPolicy configures timeouts that are used for handling network requests. +// +// TimeoutPolicy durations are expressed in the Go [Duration format](https://godoc.org/time#ParseDuration). +// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". +// The string "infinity" is also a valid input and specifies no timeout. +// A value of "0s" will be treated as if the field were not set, i.e. by using Envoy's default behavior. +// +// Example input values: "300ms", "5s", "1m". type TimeoutPolicy struct { - // TimeoutPolicy durations are expressed as per the format specified in the ParseDuration documentation: https://godoc.org/time#ParseDuration - // Example input values: "300ms", "5s", "1m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". - // The string 'infinity' is also a valid input and specifies no timeout. - // Timeout for receiving a response from the server after processing a request from client. - // If not supplied the timeout duration is undefined. + // If not supplied, Envoy's default value of 15s applies. // +optional + // +kubebuilder:validation:Pattern=`^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$` Response string `json:"response,omitempty"` - // Timeout after which if there are no active requests for this route, the connection between - // Envoy and the backend will be closed. If not specified, there is no per-route idle timeout. + // Timeout for how long the proxy should wait while there is no activity during single request/response (for HTTP/1.1) or stream (for HTTP/2). + // Timeout will not trigger while HTTP/1.1 connection is idle between two consecutive requests. + // If not specified, there is no per-route idle timeout, though a connection manager-wide + // stream_idle_timeout default of 5m still applies. // +optional + // +kubebuilder:validation:Pattern=`^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$` Idle string `json:"idle,omitempty"` + + // Timeout for how long connection from the proxy to the upstream service is kept when there are no active requests. + // If not supplied, Envoy's default value of 1h applies. + // +optional + // +kubebuilder:validation:Pattern=`^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$` + IdleConnection string `json:"idleConnection,omitempty"` } +// RetryOn is a string type alias with validation to ensure that the value is valid. +// +kubebuilder:validation:Enum="5xx";gateway-error;reset;connect-failure;retriable-4xx;refused-stream;retriable-status-codes;retriable-headers;cancelled;deadline-exceeded;internal;resource-exhausted;unavailable +type RetryOn string + // RetryPolicy defines the attributes associated with retrying policy. type RetryPolicy struct { // NumRetries is maximum allowed number of retries. - // If not supplied, the number of retries is one. + // If set to -1, then retries are disabled. + // If set to 0 or not supplied, the value is set + // to the Envoy default of 1. // +optional - NumRetries uint32 `json:"count"` + // +kubebuilder:default=1 + // +kubebuilder:validation:Minimum=-1 + NumRetries int64 `json:"count"` // PerTryTimeout specifies the timeout per retry attempt. // Ignored if NumRetries is not supplied. + // +optional + // +kubebuilder:validation:Pattern=`^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$` PerTryTimeout string `json:"perTryTimeout,omitempty"` + // RetryOn specifies the conditions on which to retry a request. + // + // Supported [HTTP conditions](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#x-envoy-retry-on): + // + // - `5xx` + // - `gateway-error` + // - `reset` + // - `connect-failure` + // - `retriable-4xx` + // - `refused-stream` + // - `retriable-status-codes` + // - `retriable-headers` + // + // Supported [gRPC conditions](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#x-envoy-retry-grpc-on): + // + // - `cancelled` + // - `deadline-exceeded` + // - `internal` + // - `resource-exhausted` + // - `unavailable` + // +optional + RetryOn []RetryOn `json:"retryOn,omitempty"` + // RetriableStatusCodes specifies the HTTP status codes that should be retried. + // + // This field is only respected when you include `retriable-status-codes` in the `RetryOn` field. + // +optional + RetriableStatusCodes []uint32 `json:"retriableStatusCodes,omitempty"` } // ReplacePrefix describes a path prefix replacement. type ReplacePrefix struct { // Prefix specifies the URL path prefix to be replaced. // - // If Prefix is specified, it must exactly match the Condition + // If Prefix is specified, it must exactly match the MatchCondition // prefix that is rendered by the chain of including HTTPProxies // and only that path prefix will be replaced by Replacement. // This allows HTTPProxies that are included through multiple @@ -304,17 +859,67 @@ type PathRewritePolicy struct { ReplacePrefix []ReplacePrefix `json:"replacePrefix,omitempty"` } +// HeaderHashOptions contains options to configure a HTTP request header hash +// policy, used in request attribute hash based load balancing. +type HeaderHashOptions struct { + // HeaderName is the name of the HTTP request header that will be used to + // calculate the hash key. If the header specified is not present on a + // request, no hash will be produced. + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + HeaderName string `json:"headerName,omitempty"` +} + +// RequestHashPolicy contains configuration for an individual hash policy +// on a request attribute. +type RequestHashPolicy struct { + // Terminal is a flag that allows for short-circuiting computing of a hash + // for a given request. If set to true, and the request attribute specified + // in the attribute hash options is present, no further hash policies will + // be used to calculate a hash for the request. + Terminal bool `json:"terminal,omitempty"` + + // HeaderHashOptions should be set when request header hash based load + // balancing is desired. It must be the only hash option field set, + // otherwise this request hash policy object will be ignored. + // +optional + HeaderHashOptions *HeaderHashOptions `json:"headerHashOptions,omitempty"` + + // HashSourceIP should be set to true when request source IP hash based + // load balancing is desired. It must be the only hash option field set, + // otherwise this request hash policy object will be ignored. + // +optional + HashSourceIP bool `json:"hashSourceIP,omitempty"` +} + // LoadBalancerPolicy defines the load balancing policy. type LoadBalancerPolicy struct { + // Strategy specifies the policy used to balance requests + // across the pool of backend pods. Valid policy names are + // `Random`, `RoundRobin`, `WeightedLeastRequest`, `Cookie`, + // and `RequestHash`. If an unknown strategy name is specified + // or no policy is supplied, the default `RoundRobin` policy + // is used. Strategy string `json:"strategy,omitempty"` + + // RequestHashPolicies contains a list of hash policies to apply when the + // `RequestHash` load balancing strategy is chosen. If an element of the + // supplied list of hash policies is invalid, it will be ignored. If the + // list of hash policies is empty after validation, the load balancing + // strategy will fall back the the default `RoundRobin`. + RequestHashPolicies []RequestHashPolicy `json:"requestHashPolicies,omitempty"` } -// HeadersPolicy defines how headers are managed during forwarding +// HeadersPolicy defines how headers are managed during forwarding. +// The `Host` header is treated specially and if set in a HTTP response +// will be used as the SNI server name when forwarding over TLS. It is an +// error to attempt to set the `Host` header in a HTTP response. type HeadersPolicy struct { - // Set specifies a list of HTTP header values that will be set in the HTTP header + // Set specifies a list of HTTP header values that will be set in the HTTP header. + // If the header does not exist it will be added, otherwise it will be overwritten with the new value. // +optional Set []HeaderValue `json:"set,omitempty"` - // Remove specifies a list of HTTP header names to remove + // Remove specifies a list of HTTP header names to remove. // +optional Remove []string `json:"remove,omitempty"` } @@ -333,37 +938,83 @@ type HeaderValue struct { // UpstreamValidation defines how to verify the backend service's certificate type UpstreamValidation struct { - // Name of the Kubernetes secret be used to validate the certificate presented by the backend + // Name or namespaced name of the Kubernetes secret used to validate the certificate presented by the backend CACertificate string `json:"caSecret"` // Key which is expected to be present in the 'subjectAltName' of the presented certificate SubjectName string `json:"subjectName"` } -// Status reports the current state of the HTTPProxy. -type Status struct { +// DownstreamValidation defines how to verify the client certificate. +type DownstreamValidation struct { + // Name of a Kubernetes secret that contains a CA certificate bundle. + // The client certificate must validate against the certificates in the bundle. + // If specified and SkipClientCertValidation is true, client certificates will + // be required on requests. + // +optional + // +kubebuilder:validation:MinLength=1 + CACertificate string `json:"caSecret,omitempty"` + + // SkipClientCertValidation disables downstream client certificate + // validation. Defaults to false. This field is intended to be used in + // conjunction with external authorization in order to enable the external + // authorization server to validate client certificates. When this field + // is set to true, client certificates are requested but not verified by + // Envoy. If CACertificate is specified, client certificates are required on + // requests, but not verified. If external authorization is in use, they are + // presented to the external authorization server. + // +optional + SkipClientCertValidation bool `json:"skipClientCertValidation"` +} + +// HTTPProxyStatus reports the current state of the HTTPProxy. +type HTTPProxyStatus struct { // +optional CurrentStatus string `json:"currentStatus,omitempty"` // +optional Description string `json:"description,omitempty"` + // +optional + // LoadBalancer contains the current status of the load balancer. + LoadBalancer corev1.LoadBalancerStatus `json:"loadBalancer,omitempty"` + // +optional + // Conditions contains information about the current status of the HTTPProxy, + // in an upstream-friendly container. + // + // Contour will update a single condition, `Valid`, that is in normal-true polarity. + // That is, when `currentStatus` is `valid`, the `Valid` condition will be `status: true`, + // and vice versa. + // + // Contour will leave untouched any other Conditions set in this block, + // in case some other controller wants to add a Condition. + // + // If you are another controller owner and wish to add a condition, you *should* + // namespace your condition with a label, like `controller.domain.com/ConditionName`. + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []DetailedCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` } // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// HTTPProxy is an Ingress CRD specification +// HTTPProxy is an Ingress CRD specification. // +k8s:openapi-gen=true // +kubebuilder:printcolumn:name="FQDN",type="string",JSONPath=".spec.virtualhost.fqdn",description="Fully qualified domain name" // +kubebuilder:printcolumn:name="TLS Secret",type="string",JSONPath=".spec.virtualhost.tls.secretName",description="Secret with TLS credentials" // +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.currentStatus",description="The current status of the HTTPProxy" // +kubebuilder:printcolumn:name="Status Description",type="string",JSONPath=".status.description",description="Description of the current status" // +kubebuilder:resource:scope=Namespaced,path=httpproxies,shortName=proxy;proxies,singular=httpproxy +// +kubebuilder:subresource:status type HTTPProxy struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata"` Spec HTTPProxySpec `json:"spec"` + // Status is a container for computed information about the HTTPProxy. // +optional - Status Status `json:"status,omitempty"` + // +kubebuilder:default={currentStatus: "NotReconciled", description:"Waiting for controller"} + Status HTTPProxyStatus `json:"status,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/projectcontour/v1/zz_generated.deepcopy.go b/pkg/apis/projectcontour/v1/zz_generated.deepcopy.go index be2d9f8d6..1d076e9cd 100644 --- a/pkg/apis/projectcontour/v1/zz_generated.deepcopy.go +++ b/pkg/apis/projectcontour/v1/zz_generated.deepcopy.go @@ -26,22 +26,269 @@ import ( ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Condition) DeepCopyInto(out *Condition) { +func (in *AuthorizationPolicy) DeepCopyInto(out *AuthorizationPolicy) { *out = *in - if in.Header != nil { - in, out := &in.Header, &out.Header - *out = new(HeaderCondition) + if in.Context != nil { + in, out := &in.Context, &out.Context + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationPolicy. +func (in *AuthorizationPolicy) DeepCopy() *AuthorizationPolicy { + if in == nil { + return nil + } + out := new(AuthorizationPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthorizationServer) DeepCopyInto(out *AuthorizationServer) { + *out = *in + out.ExtensionServiceRef = in.ExtensionServiceRef + if in.AuthPolicy != nil { + in, out := &in.AuthPolicy, &out.AuthPolicy + *out = new(AuthorizationPolicy) + (*in).DeepCopyInto(*out) + } + if in.WithRequestBody != nil { + in, out := &in.WithRequestBody, &out.WithRequestBody + *out = new(AuthorizationServerBufferSettings) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationServer. +func (in *AuthorizationServer) DeepCopy() *AuthorizationServer { + if in == nil { + return nil + } + out := new(AuthorizationServer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthorizationServerBufferSettings) DeepCopyInto(out *AuthorizationServerBufferSettings) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationServerBufferSettings. +func (in *AuthorizationServerBufferSettings) DeepCopy() *AuthorizationServerBufferSettings { + if in == nil { + return nil + } + out := new(AuthorizationServerBufferSettings) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CORSPolicy) DeepCopyInto(out *CORSPolicy) { + *out = *in + if in.AllowOrigin != nil { + in, out := &in.AllowOrigin, &out.AllowOrigin + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.AllowMethods != nil { + in, out := &in.AllowMethods, &out.AllowMethods + *out = make([]CORSHeaderValue, len(*in)) + copy(*out, *in) + } + if in.AllowHeaders != nil { + in, out := &in.AllowHeaders, &out.AllowHeaders + *out = make([]CORSHeaderValue, len(*in)) + copy(*out, *in) + } + if in.ExposeHeaders != nil { + in, out := &in.ExposeHeaders, &out.ExposeHeaders + *out = make([]CORSHeaderValue, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CORSPolicy. +func (in *CORSPolicy) DeepCopy() *CORSPolicy { + if in == nil { + return nil + } + out := new(CORSPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CookieDomainRewrite) DeepCopyInto(out *CookieDomainRewrite) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CookieDomainRewrite. +func (in *CookieDomainRewrite) DeepCopy() *CookieDomainRewrite { + if in == nil { + return nil + } + out := new(CookieDomainRewrite) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CookiePathRewrite) DeepCopyInto(out *CookiePathRewrite) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CookiePathRewrite. +func (in *CookiePathRewrite) DeepCopy() *CookiePathRewrite { + if in == nil { + return nil + } + out := new(CookiePathRewrite) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CookieRewritePolicy) DeepCopyInto(out *CookieRewritePolicy) { + *out = *in + if in.PathRewrite != nil { + in, out := &in.PathRewrite, &out.PathRewrite + *out = new(CookiePathRewrite) + **out = **in + } + if in.DomainRewrite != nil { + in, out := &in.DomainRewrite, &out.DomainRewrite + *out = new(CookieDomainRewrite) + **out = **in + } + if in.Secure != nil { + in, out := &in.Secure, &out.Secure + *out = new(bool) + **out = **in + } + if in.SameSite != nil { + in, out := &in.SameSite, &out.SameSite + *out = new(string) **out = **in } return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. -func (in *Condition) DeepCopy() *Condition { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CookieRewritePolicy. +func (in *CookieRewritePolicy) DeepCopy() *CookieRewritePolicy { if in == nil { return nil } - out := new(Condition) + out := new(CookieRewritePolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DetailedCondition) DeepCopyInto(out *DetailedCondition) { + *out = *in + in.Condition.DeepCopyInto(&out.Condition) + if in.Errors != nil { + in, out := &in.Errors, &out.Errors + *out = make([]SubCondition, len(*in)) + copy(*out, *in) + } + if in.Warnings != nil { + in, out := &in.Warnings, &out.Warnings + *out = make([]SubCondition, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DetailedCondition. +func (in *DetailedCondition) DeepCopy() *DetailedCondition { + if in == nil { + return nil + } + out := new(DetailedCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DownstreamValidation) DeepCopyInto(out *DownstreamValidation) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DownstreamValidation. +func (in *DownstreamValidation) DeepCopy() *DownstreamValidation { + if in == nil { + return nil + } + out := new(DownstreamValidation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtensionServiceReference) DeepCopyInto(out *ExtensionServiceReference) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionServiceReference. +func (in *ExtensionServiceReference) DeepCopy() *ExtensionServiceReference { + if in == nil { + return nil + } + out := new(ExtensionServiceReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GenericKeyDescriptor) DeepCopyInto(out *GenericKeyDescriptor) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericKeyDescriptor. +func (in *GenericKeyDescriptor) DeepCopy() *GenericKeyDescriptor { + if in == nil { + return nil + } + out := new(GenericKeyDescriptor) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GlobalRateLimitPolicy) DeepCopyInto(out *GlobalRateLimitPolicy) { + *out = *in + if in.Descriptors != nil { + in, out := &in.Descriptors, &out.Descriptors + *out = make([]RateLimitDescriptor, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalRateLimitPolicy. +func (in *GlobalRateLimitPolicy) DeepCopy() *GlobalRateLimitPolicy { + if in == nil { + return nil + } + out := new(GlobalRateLimitPolicy) in.DeepCopyInto(out) return out } @@ -68,7 +315,7 @@ func (in *HTTPProxy) DeepCopyInto(out *HTTPProxy) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) return } @@ -164,17 +411,103 @@ func (in *HTTPProxySpec) DeepCopy() *HTTPProxySpec { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HeaderCondition) DeepCopyInto(out *HeaderCondition) { +func (in *HTTPProxyStatus) DeepCopyInto(out *HTTPProxyStatus) { + *out = *in + in.LoadBalancer.DeepCopyInto(&out.LoadBalancer) + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]DetailedCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPProxyStatus. +func (in *HTTPProxyStatus) DeepCopy() *HTTPProxyStatus { + if in == nil { + return nil + } + out := new(HTTPProxyStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPRequestRedirectPolicy) DeepCopyInto(out *HTTPRequestRedirectPolicy) { + *out = *in + if in.Scheme != nil { + in, out := &in.Scheme, &out.Scheme + *out = new(string) + **out = **in + } + if in.Hostname != nil { + in, out := &in.Hostname, &out.Hostname + *out = new(string) + **out = **in + } + if in.Port != nil { + in, out := &in.Port, &out.Port + *out = new(int32) + **out = **in + } + if in.StatusCode != nil { + in, out := &in.StatusCode, &out.StatusCode + *out = new(int) + **out = **in + } + if in.Path != nil { + in, out := &in.Path, &out.Path + *out = new(string) + **out = **in + } + if in.Prefix != nil { + in, out := &in.Prefix, &out.Prefix + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRequestRedirectPolicy. +func (in *HTTPRequestRedirectPolicy) DeepCopy() *HTTPRequestRedirectPolicy { + if in == nil { + return nil + } + out := new(HTTPRequestRedirectPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HeaderHashOptions) DeepCopyInto(out *HeaderHashOptions) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeaderHashOptions. +func (in *HeaderHashOptions) DeepCopy() *HeaderHashOptions { + if in == nil { + return nil + } + out := new(HeaderHashOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HeaderMatchCondition) DeepCopyInto(out *HeaderMatchCondition) { *out = *in return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeaderCondition. -func (in *HeaderCondition) DeepCopy() *HeaderCondition { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeaderMatchCondition. +func (in *HeaderMatchCondition) DeepCopy() *HeaderMatchCondition { if in == nil { return nil } - out := new(HeaderCondition) + out := new(HeaderMatchCondition) in.DeepCopyInto(out) return out } @@ -226,7 +559,7 @@ func (in *Include) DeepCopyInto(out *Include) { *out = *in if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions - *out = make([]Condition, len(*in)) + *out = make([]MatchCondition, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -247,6 +580,13 @@ func (in *Include) DeepCopy() *Include { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LoadBalancerPolicy) DeepCopyInto(out *LoadBalancerPolicy) { *out = *in + if in.RequestHashPolicies != nil { + in, out := &in.RequestHashPolicies, &out.RequestHashPolicies + *out = make([]RequestHashPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -260,6 +600,48 @@ func (in *LoadBalancerPolicy) DeepCopy() *LoadBalancerPolicy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalRateLimitPolicy) DeepCopyInto(out *LocalRateLimitPolicy) { + *out = *in + if in.ResponseHeadersToAdd != nil { + in, out := &in.ResponseHeadersToAdd, &out.ResponseHeadersToAdd + *out = make([]HeaderValue, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalRateLimitPolicy. +func (in *LocalRateLimitPolicy) DeepCopy() *LocalRateLimitPolicy { + if in == nil { + return nil + } + out := new(LocalRateLimitPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MatchCondition) DeepCopyInto(out *MatchCondition) { + *out = *in + if in.Header != nil { + in, out := &in.Header, &out.Header + *out = new(HeaderMatchCondition) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchCondition. +func (in *MatchCondition) DeepCopy() *MatchCondition { + if in == nil { + return nil + } + out := new(MatchCondition) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PathRewritePolicy) DeepCopyInto(out *PathRewritePolicy) { *out = *in @@ -281,6 +663,107 @@ func (in *PathRewritePolicy) DeepCopy() *PathRewritePolicy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RateLimitDescriptor) DeepCopyInto(out *RateLimitDescriptor) { + *out = *in + if in.Entries != nil { + in, out := &in.Entries, &out.Entries + *out = make([]RateLimitDescriptorEntry, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitDescriptor. +func (in *RateLimitDescriptor) DeepCopy() *RateLimitDescriptor { + if in == nil { + return nil + } + out := new(RateLimitDescriptor) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RateLimitDescriptorEntry) DeepCopyInto(out *RateLimitDescriptorEntry) { + *out = *in + if in.GenericKey != nil { + in, out := &in.GenericKey, &out.GenericKey + *out = new(GenericKeyDescriptor) + **out = **in + } + if in.RequestHeader != nil { + in, out := &in.RequestHeader, &out.RequestHeader + *out = new(RequestHeaderDescriptor) + **out = **in + } + if in.RequestHeaderValueMatch != nil { + in, out := &in.RequestHeaderValueMatch, &out.RequestHeaderValueMatch + *out = new(RequestHeaderValueMatchDescriptor) + (*in).DeepCopyInto(*out) + } + if in.RemoteAddress != nil { + in, out := &in.RemoteAddress, &out.RemoteAddress + *out = new(RemoteAddressDescriptor) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitDescriptorEntry. +func (in *RateLimitDescriptorEntry) DeepCopy() *RateLimitDescriptorEntry { + if in == nil { + return nil + } + out := new(RateLimitDescriptorEntry) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RateLimitPolicy) DeepCopyInto(out *RateLimitPolicy) { + *out = *in + if in.Local != nil { + in, out := &in.Local, &out.Local + *out = new(LocalRateLimitPolicy) + (*in).DeepCopyInto(*out) + } + if in.Global != nil { + in, out := &in.Global, &out.Global + *out = new(GlobalRateLimitPolicy) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitPolicy. +func (in *RateLimitPolicy) DeepCopy() *RateLimitPolicy { + if in == nil { + return nil + } + out := new(RateLimitPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RemoteAddressDescriptor) DeepCopyInto(out *RemoteAddressDescriptor) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RemoteAddressDescriptor. +func (in *RemoteAddressDescriptor) DeepCopy() *RemoteAddressDescriptor { + if in == nil { + return nil + } + out := new(RemoteAddressDescriptor) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReplacePrefix) DeepCopyInto(out *ReplacePrefix) { *out = *in @@ -297,9 +780,77 @@ func (in *ReplacePrefix) DeepCopy() *ReplacePrefix { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RequestHashPolicy) DeepCopyInto(out *RequestHashPolicy) { + *out = *in + if in.HeaderHashOptions != nil { + in, out := &in.HeaderHashOptions, &out.HeaderHashOptions + *out = new(HeaderHashOptions) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestHashPolicy. +func (in *RequestHashPolicy) DeepCopy() *RequestHashPolicy { + if in == nil { + return nil + } + out := new(RequestHashPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RequestHeaderDescriptor) DeepCopyInto(out *RequestHeaderDescriptor) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestHeaderDescriptor. +func (in *RequestHeaderDescriptor) DeepCopy() *RequestHeaderDescriptor { + if in == nil { + return nil + } + out := new(RequestHeaderDescriptor) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RequestHeaderValueMatchDescriptor) DeepCopyInto(out *RequestHeaderValueMatchDescriptor) { + *out = *in + if in.Headers != nil { + in, out := &in.Headers, &out.Headers + *out = make([]HeaderMatchCondition, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestHeaderValueMatchDescriptor. +func (in *RequestHeaderValueMatchDescriptor) DeepCopy() *RequestHeaderValueMatchDescriptor { + if in == nil { + return nil + } + out := new(RequestHeaderValueMatchDescriptor) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RetryPolicy) DeepCopyInto(out *RetryPolicy) { *out = *in + if in.RetryOn != nil { + in, out := &in.RetryOn, &out.RetryOn + *out = make([]RetryOn, len(*in)) + copy(*out, *in) + } + if in.RetriableStatusCodes != nil { + in, out := &in.RetriableStatusCodes, &out.RetriableStatusCodes + *out = make([]uint32, len(*in)) + copy(*out, *in) + } return } @@ -318,7 +869,7 @@ func (in *Route) DeepCopyInto(out *Route) { *out = *in if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions - *out = make([]Condition, len(*in)) + *out = make([]MatchCondition, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -330,6 +881,11 @@ func (in *Route) DeepCopyInto(out *Route) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.AuthPolicy != nil { + in, out := &in.AuthPolicy, &out.AuthPolicy + *out = new(AuthorizationPolicy) + (*in).DeepCopyInto(*out) + } if in.TimeoutPolicy != nil { in, out := &in.TimeoutPolicy, &out.TimeoutPolicy *out = new(TimeoutPolicy) @@ -338,7 +894,7 @@ func (in *Route) DeepCopyInto(out *Route) { if in.RetryPolicy != nil { in, out := &in.RetryPolicy, &out.RetryPolicy *out = new(RetryPolicy) - **out = **in + (*in).DeepCopyInto(*out) } if in.HealthCheckPolicy != nil { in, out := &in.HealthCheckPolicy, &out.HealthCheckPolicy @@ -348,7 +904,7 @@ func (in *Route) DeepCopyInto(out *Route) { if in.LoadBalancerPolicy != nil { in, out := &in.LoadBalancerPolicy, &out.LoadBalancerPolicy *out = new(LoadBalancerPolicy) - **out = **in + (*in).DeepCopyInto(*out) } if in.PathRewritePolicy != nil { in, out := &in.PathRewritePolicy, &out.PathRewritePolicy @@ -365,6 +921,23 @@ func (in *Route) DeepCopyInto(out *Route) { *out = new(HeadersPolicy) (*in).DeepCopyInto(*out) } + if in.CookieRewritePolicies != nil { + in, out := &in.CookieRewritePolicies, &out.CookieRewritePolicies + *out = make([]CookieRewritePolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.RateLimitPolicy != nil { + in, out := &in.RateLimitPolicy, &out.RateLimitPolicy + *out = new(RateLimitPolicy) + (*in).DeepCopyInto(*out) + } + if in.RequestRedirectPolicy != nil { + in, out := &in.RequestRedirectPolicy, &out.RequestRedirectPolicy + *out = new(HTTPRequestRedirectPolicy) + (*in).DeepCopyInto(*out) + } return } @@ -401,6 +974,13 @@ func (in *Service) DeepCopyInto(out *Service) { *out = new(HeadersPolicy) (*in).DeepCopyInto(*out) } + if in.CookieRewritePolicies != nil { + in, out := &in.CookieRewritePolicies, &out.CookieRewritePolicies + *out = make([]CookieRewritePolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -415,17 +995,33 @@ func (in *Service) DeepCopy() *Service { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Status) DeepCopyInto(out *Status) { +func (in *SubCondition) DeepCopyInto(out *SubCondition) { *out = *in return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status. -func (in *Status) DeepCopy() *Status { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubCondition. +func (in *SubCondition) DeepCopy() *SubCondition { if in == nil { return nil } - out := new(Status) + out := new(SubCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TCPHealthCheckPolicy) DeepCopyInto(out *TCPHealthCheckPolicy) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPHealthCheckPolicy. +func (in *TCPHealthCheckPolicy) DeepCopy() *TCPHealthCheckPolicy { + if in == nil { + return nil + } + out := new(TCPHealthCheckPolicy) in.DeepCopyInto(out) return out } @@ -436,7 +1032,7 @@ func (in *TCPProxy) DeepCopyInto(out *TCPProxy) { if in.LoadBalancerPolicy != nil { in, out := &in.LoadBalancerPolicy, &out.LoadBalancerPolicy *out = new(LoadBalancerPolicy) - **out = **in + (*in).DeepCopyInto(*out) } if in.Services != nil { in, out := &in.Services, &out.Services @@ -450,6 +1046,16 @@ func (in *TCPProxy) DeepCopyInto(out *TCPProxy) { *out = new(TCPProxyInclude) **out = **in } + if in.IncludesDeprecated != nil { + in, out := &in.IncludesDeprecated, &out.IncludesDeprecated + *out = new(TCPProxyInclude) + **out = **in + } + if in.HealthCheckPolicy != nil { + in, out := &in.HealthCheckPolicy, &out.HealthCheckPolicy + *out = new(TCPHealthCheckPolicy) + **out = **in + } return } @@ -482,6 +1088,11 @@ func (in *TCPProxyInclude) DeepCopy() *TCPProxyInclude { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLS) DeepCopyInto(out *TLS) { *out = *in + if in.ClientValidation != nil { + in, out := &in.ClientValidation, &out.ClientValidation + *out = new(DownstreamValidation) + **out = **in + } return } @@ -533,7 +1144,22 @@ func (in *VirtualHost) DeepCopyInto(out *VirtualHost) { if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLS) - **out = **in + (*in).DeepCopyInto(*out) + } + if in.Authorization != nil { + in, out := &in.Authorization, &out.Authorization + *out = new(AuthorizationServer) + (*in).DeepCopyInto(*out) + } + if in.CORSPolicy != nil { + in, out := &in.CORSPolicy, &out.CORSPolicy + *out = new(CORSPolicy) + (*in).DeepCopyInto(*out) + } + if in.RateLimitPolicy != nil { + in, out := &in.RateLimitPolicy, &out.RateLimitPolicy + *out = new(RateLimitPolicy) + (*in).DeepCopyInto(*out) } return } diff --git a/pkg/router/contour.go b/pkg/router/contour.go index ef34ce088..9ca42a875 100644 --- a/pkg/router/contour.go +++ b/pkg/router/contour.go @@ -19,6 +19,7 @@ package router import ( "context" "fmt" + "strings" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -51,7 +52,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error { newSpec := contourv1.HTTPProxySpec{ Routes: []contourv1.Route{ { - Conditions: []contourv1.Condition{ + Conditions: []contourv1.MatchCondition{ { Prefix: cr.makePrefix(canary), }, @@ -62,7 +63,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error { { Name: primaryName, Port: int(canary.Spec.Service.Port), - Weight: uint32(100), + Weight: int64(100), RequestHeadersPolicy: &contourv1.HeadersPolicy{ Set: []contourv1.HeaderValue{ cr.makeLinkerdHeaderValue(canary, primaryName), @@ -72,7 +73,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error { { Name: canaryName, Port: int(canary.Spec.Service.Port), - Weight: uint32(0), + Weight: int64(0), RequestHeadersPolicy: &contourv1.HeadersPolicy{ Set: []contourv1.HeaderValue{ cr.makeLinkerdHeaderValue(canary, canaryName), @@ -95,7 +96,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error { { Name: primaryName, Port: int(canary.Spec.Service.Port), - Weight: uint32(100), + Weight: int64(100), RequestHeadersPolicy: &contourv1.HeadersPolicy{ Set: []contourv1.HeaderValue{ cr.makeLinkerdHeaderValue(canary, primaryName), @@ -105,7 +106,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error { { Name: canaryName, Port: int(canary.Spec.Service.Port), - Weight: uint32(0), + Weight: int64(0), RequestHeadersPolicy: &contourv1.HeadersPolicy{ Set: []contourv1.HeaderValue{ cr.makeLinkerdHeaderValue(canary, canaryName), @@ -115,7 +116,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error { }, }, { - Conditions: []contourv1.Condition{ + Conditions: []contourv1.MatchCondition{ { Prefix: cr.makePrefix(canary), }, @@ -126,7 +127,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error { { Name: primaryName, Port: int(canary.Spec.Service.Port), - Weight: uint32(100), + Weight: int64(100), RequestHeadersPolicy: &contourv1.HeadersPolicy{ Set: []contourv1.HeaderValue{ cr.makeLinkerdHeaderValue(canary, primaryName), @@ -136,7 +137,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error { { Name: canaryName, Port: int(canary.Spec.Service.Port), - Weight: uint32(0), + Weight: int64(0), RequestHeadersPolicy: &contourv1.HeadersPolicy{ Set: []contourv1.HeaderValue{ cr.makeLinkerdHeaderValue(canary, canaryName), @@ -177,7 +178,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error { }, }, Spec: newSpec, - Status: contourv1.Status{ + Status: contourv1.HTTPProxyStatus{ CurrentStatus: "valid", Description: "valid HTTPProxy", }, @@ -274,7 +275,7 @@ func (cr *ContourRouter) SetRoutes( proxy.Spec = contourv1.HTTPProxySpec{ Routes: []contourv1.Route{ { - Conditions: []contourv1.Condition{ + Conditions: []contourv1.MatchCondition{ { Prefix: cr.makePrefix(canary), }, @@ -285,7 +286,7 @@ func (cr *ContourRouter) SetRoutes( { Name: primaryName, Port: int(canary.Spec.Service.Port), - Weight: uint32(primaryWeight), + Weight: int64(primaryWeight), RequestHeadersPolicy: &contourv1.HeadersPolicy{ Set: []contourv1.HeaderValue{ cr.makeLinkerdHeaderValue(canary, primaryName), @@ -295,7 +296,7 @@ func (cr *ContourRouter) SetRoutes( { Name: canaryName, Port: int(canary.Spec.Service.Port), - Weight: uint32(canaryWeight), + Weight: int64(canaryWeight), RequestHeadersPolicy: &contourv1.HeadersPolicy{ Set: []contourv1.HeaderValue{ cr.makeLinkerdHeaderValue(canary, canaryName), @@ -317,7 +318,7 @@ func (cr *ContourRouter) SetRoutes( { Name: primaryName, Port: int(canary.Spec.Service.Port), - Weight: uint32(primaryWeight), + Weight: int64(primaryWeight), RequestHeadersPolicy: &contourv1.HeadersPolicy{ Set: []contourv1.HeaderValue{ cr.makeLinkerdHeaderValue(canary, primaryName), @@ -327,7 +328,7 @@ func (cr *ContourRouter) SetRoutes( { Name: canaryName, Port: int(canary.Spec.Service.Port), - Weight: uint32(canaryWeight), + Weight: int64(canaryWeight), RequestHeadersPolicy: &contourv1.HeadersPolicy{ Set: []contourv1.HeaderValue{ cr.makeLinkerdHeaderValue(canary, canaryName), @@ -337,7 +338,7 @@ func (cr *ContourRouter) SetRoutes( }, }, { - Conditions: []contourv1.Condition{ + Conditions: []contourv1.MatchCondition{ { Prefix: cr.makePrefix(canary), }, @@ -348,7 +349,7 @@ func (cr *ContourRouter) SetRoutes( { Name: primaryName, Port: int(canary.Spec.Service.Port), - Weight: uint32(100), + Weight: int64(100), RequestHeadersPolicy: &contourv1.HeadersPolicy{ Set: []contourv1.HeaderValue{ cr.makeLinkerdHeaderValue(canary, primaryName), @@ -358,7 +359,7 @@ func (cr *ContourRouter) SetRoutes( { Name: canaryName, Port: int(canary.Spec.Service.Port), - Weight: uint32(0), + Weight: int64(0), RequestHeadersPolicy: &contourv1.HeadersPolicy{ Set: []contourv1.HeaderValue{ cr.makeLinkerdHeaderValue(canary, canaryName), @@ -390,36 +391,36 @@ func (cr *ContourRouter) makePrefix(canary *flaggerv1.Canary) string { return prefix } -func (cr *ContourRouter) makeConditions(canary *flaggerv1.Canary) []contourv1.Condition { - list := []contourv1.Condition{} +func (cr *ContourRouter) makeConditions(canary *flaggerv1.Canary) []contourv1.MatchCondition { + list := []contourv1.MatchCondition{} if len(canary.GetAnalysis().Match) > 0 { for _, match := range canary.GetAnalysis().Match { for s, stringMatch := range match.Headers { - h := &contourv1.HeaderCondition{ + h := &contourv1.HeaderMatchCondition{ Name: s, Exact: stringMatch.Exact, } if stringMatch.Suffix != "" { - h = &contourv1.HeaderCondition{ + h = &contourv1.HeaderMatchCondition{ Name: s, Contains: stringMatch.Suffix, } } if stringMatch.Prefix != "" { - h = &contourv1.HeaderCondition{ + h = &contourv1.HeaderMatchCondition{ Name: s, Contains: stringMatch.Prefix, } } - list = append(list, contourv1.Condition{ + list = append(list, contourv1.MatchCondition{ Prefix: cr.makePrefix(canary), Header: h, }) } } } else { - list = []contourv1.Condition{ + list = []contourv1.MatchCondition{ { Prefix: cr.makePrefix(canary), }, @@ -442,13 +443,24 @@ func (cr *ContourRouter) makeTimeoutPolicy(canary *flaggerv1.Canary) *contourv1. func (cr *ContourRouter) makeRetryPolicy(canary *flaggerv1.Canary) *contourv1.RetryPolicy { if canary.Spec.Service.Retries != nil { return &contourv1.RetryPolicy{ - NumRetries: uint32(canary.Spec.Service.Retries.Attempts), + NumRetries: int64(canary.Spec.Service.Retries.Attempts), PerTryTimeout: canary.Spec.Service.Retries.PerTryTimeout, + RetryOn: makeRetryOn(canary.Spec.Service.Retries.RetryOn), } } return nil } +func makeRetryOn(retryOnString string) []contourv1.RetryOn { + retryOnSplit := strings.Split(retryOnString, ",") + + retryOn := make([]contourv1.RetryOn, len(retryOnSplit)) + for i, v := range retryOnSplit { + retryOn[i] = contourv1.RetryOn(v) + } + return retryOn +} + func (cr *ContourRouter) makeLinkerdHeaderValue(canary *flaggerv1.Canary, serviceName string) contourv1.HeaderValue { return contourv1.HeaderValue{ Name: "l5d-dst-override", diff --git a/pkg/router/contour_test.go b/pkg/router/contour_test.go index c54f9c524..712211412 100644 --- a/pkg/router/contour_test.go +++ b/pkg/router/contour_test.go @@ -20,6 +20,8 @@ import ( "context" "testing" + contourv1 "github.com/fluxcd/flagger/pkg/apis/projectcontour/v1" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -46,8 +48,8 @@ func TestContourRouter_Reconcile(t *testing.T) { services := proxy.Spec.Routes[0].Services require.Len(t, services, 2) - assert.Equal(t, uint32(100), services[0].Weight) - assert.Equal(t, uint32(0), services[1].Weight) + assert.Equal(t, int64(100), services[0].Weight) + assert.Equal(t, int64(0), services[1].Weight) assert.Equal(t, "contour", proxy.Annotations["projectcontour.io/ingress.class"]) // test update @@ -69,7 +71,8 @@ func TestContourRouter_Reconcile(t *testing.T) { assert.Equal(t, 8080, proxy.Spec.Routes[0].Services[0].Port) assert.Equal(t, "1m", proxy.Spec.Routes[0].TimeoutPolicy.Response) assert.Equal(t, "/podinfo", proxy.Spec.Routes[0].Conditions[0].Prefix) - assert.Equal(t, uint32(10), proxy.Spec.Routes[0].RetryPolicy.NumRetries) + assert.Equal(t, int64(10), proxy.Spec.Routes[0].RetryPolicy.NumRetries) + assert.Equal(t, []contourv1.RetryOn{"connect-failure", "gateway-error"}, proxy.Spec.Routes[0].RetryPolicy.RetryOn) // test headers update cd, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get(context.TODO(), "podinfo", metav1.GetOptions{}) @@ -111,7 +114,7 @@ func TestContourRouter_Routes(t *testing.T) { require.NoError(t, err) primary := proxy.Spec.Routes[0].Services[0] - assert.Equal(t, uint32(50), primary.Weight) + assert.Equal(t, int64(50), primary.Weight) cd, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get(context.TODO(), "podinfo", metav1.GetOptions{}) require.NoError(t, err) @@ -135,10 +138,10 @@ func TestContourRouter_Routes(t *testing.T) { require.NoError(t, err) primary = proxy.Spec.Routes[0].Services[0] - assert.Equal(t, uint32(100), primary.Weight) + assert.Equal(t, int64(100), primary.Weight) primary = proxy.Spec.Routes[1].Services[0] - assert.Equal(t, uint32(100), primary.Weight) + assert.Equal(t, int64(100), primary.Weight) // test set routers for A/B err = router.SetRoutes(canary, 0, 100, false) @@ -148,8 +151,8 @@ func TestContourRouter_Routes(t *testing.T) { require.NoError(t, err) primary = proxy.Spec.Routes[0].Services[0] - assert.Equal(t, uint32(0), primary.Weight) + assert.Equal(t, int64(0), primary.Weight) primary = proxy.Spec.Routes[1].Services[0] - assert.Equal(t, uint32(100), primary.Weight) + assert.Equal(t, int64(100), primary.Weight) } diff --git a/pkg/router/router_test.go b/pkg/router/router_test.go index 917282bfd..e077e0089 100644 --- a/pkg/router/router_test.go +++ b/pkg/router/router_test.go @@ -132,6 +132,7 @@ func newTestCanary() *flaggerv1.Canary { Retries: &istiov1alpha3.HTTPRetry{ Attempts: 10, PerTryTimeout: "30s", + RetryOn: "connect-failure,gateway-error", }, Gateways: []string{ "public-gateway.istio",