diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 7b817d46050..a282400787f 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -116,9 +116,18 @@ rules: resources: - routes verbs: + - create - get - list + - patch + - update - watch +- apiGroups: + - route.openshift.io + resources: + - routes/status + verbs: + - get - apiGroups: - serving.knative.dev resources: diff --git a/pkg/controller/v1alpha1/inferencegraph/controller.go b/pkg/controller/v1alpha1/inferencegraph/controller.go index cbbeb78e6c1..348848db4cb 100644 --- a/pkg/controller/v1alpha1/inferencegraph/controller.go +++ b/pkg/controller/v1alpha1/inferencegraph/controller.go @@ -19,6 +19,8 @@ limitations under the License. // +kubebuilder:rbac:groups=serving.knative.dev,resources=services,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=serving.knative.dev,resources=services/finalizers,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=serving.knative.dev,resources=services/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=route.openshift.io,resources=routes,verbs=create;get;update;patch;watch +// +kubebuilder:rbac:groups=route.openshift.io,resources=routes/status,verbs=get package inferencegraph import ( @@ -27,7 +29,7 @@ import ( "fmt" "github.com/go-logr/logr" - "github.com/kserve/kserve/pkg/utils" + osv1 "github.com/openshift/api/route/v1" "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" @@ -52,6 +54,7 @@ import ( v1beta1api "github.com/kserve/kserve/pkg/apis/serving/v1beta1" "github.com/kserve/kserve/pkg/constants" isvcutils "github.com/kserve/kserve/pkg/controller/v1beta1/inferenceservice/utils" + "github.com/kserve/kserve/pkg/utils" ) // InferenceGraphReconciler reconciles a InferenceGraph object @@ -121,7 +124,7 @@ func getRouterConfigs(configMap *v1.ConfigMap) (*RouterConfig, error) { func (r *InferenceGraphReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = context.Background() - // Fetch the InferenceService instance + // Fetch the InferenceGraph instance graph := &v1alpha1api.InferenceGraph{} if err := r.Get(ctx, req.NamespacedName, graph); err != nil { if apierr.IsNotFound(err) { @@ -193,6 +196,17 @@ func (r *InferenceGraphReconciler) Reconcile(ctx context.Context, req ctrl.Reque return reconcile.Result{Requeue: true}, errors.Wrapf(err, "Failed to find inference graph deployment %s", graph.Name) } + + routeReconciler := OpenShiftRouteReconciler{ + Scheme: r.Scheme, + Client: r.Client, + } + hostname, err := routeReconciler.Reconcile(ctx, graph) + url.Host = hostname + if err != nil { + return ctrl.Result{}, errors.Wrapf(err, "fails to reconcile Route for InferenceGraph") + } + logger.Info("Inference graph raw before propagate status") PropagateRawStatus(&graph.Status, deployment, url) } else { @@ -291,7 +305,8 @@ func (r *InferenceGraphReconciler) SetupWithManager(mgr ctrl.Manager, deployConf ctrlBuilder := ctrl.NewControllerManagedBy(mgr). For(&v1alpha1api.InferenceGraph{}). - Owns(&appsv1.Deployment{}) + Owns(&appsv1.Deployment{}). + Owns(&osv1.Route{}) if ksvcFound { ctrlBuilder = ctrlBuilder.Owns(&knservingv1.Service{}) diff --git a/pkg/controller/v1alpha1/inferencegraph/controller_test.go b/pkg/controller/v1alpha1/inferencegraph/controller_test.go index f6290f484b7..a576964925f 100644 --- a/pkg/controller/v1alpha1/inferencegraph/controller_test.go +++ b/pkg/controller/v1alpha1/inferencegraph/controller_test.go @@ -23,6 +23,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + osv1 "github.com/openshift/api/route/v1" "google.golang.org/protobuf/proto" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" @@ -671,6 +672,29 @@ var _ = Describe("Inference Graph controller test", func() { Expect(actualK8sDeploymentCreated.Spec.Template.Spec.Containers).To(Not(BeNil())) Expect(actualK8sDeploymentCreated.Spec.Template.Spec.Containers[0].Image).To(Not(BeNil())) Expect(actualK8sDeploymentCreated.Spec.Template.Spec.Containers[0].Args).To(Not(BeNil())) + + // There should be an OpenShift route + actualK8sDeploymentCreated.Status.Conditions = []appsv1.DeploymentCondition{ + {Type: appsv1.DeploymentAvailable}, + } + Expect(k8sClient.Status().Update(ctx, actualK8sDeploymentCreated)).Should(Succeed()) + osRoute := osv1.Route{} + Eventually(func() error { + osRouteKey := types.NamespacedName{Name: inferenceGraphSubmitted.GetName() + "-route", Namespace: inferenceGraphSubmitted.GetNamespace()} + return k8sClient.Get(ctx, osRouteKey, &osRoute) + }, timeout, interval).Should(Succeed()) + + // OpenShift route hostname should be set to InferenceGraph + osRoute.Status.Ingress = []osv1.RouteIngress{ + { + Host: "openshift-route-example.com", + }, + } + k8sClient.Status().Update(ctx, &osRoute) + Eventually(func() string { + k8sClient.Get(ctx, serviceKey, inferenceGraphSubmitted) + return inferenceGraphSubmitted.Status.URL.Host + }, timeout, interval).Should(Equal(osRoute.Status.Ingress[0].Host)) }) }) diff --git a/pkg/controller/v1alpha1/inferencegraph/openshift_route_reconciler.go b/pkg/controller/v1alpha1/inferencegraph/openshift_route_reconciler.go new file mode 100644 index 00000000000..a970ac99996 --- /dev/null +++ b/pkg/controller/v1alpha1/inferencegraph/openshift_route_reconciler.go @@ -0,0 +1,86 @@ +package inferencegraph + +import ( + "context" + "fmt" + "reflect" + + v1 "github.com/openshift/api/route/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + ctrlLog "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/kserve/kserve/pkg/apis/serving/v1alpha1" +) + +type OpenShiftRouteReconciler struct { + Scheme *runtime.Scheme + Client client.Client +} + +func (r *OpenShiftRouteReconciler) Reconcile(ctx context.Context, inferenceGraph *v1alpha1.InferenceGraph) (string, error) { + logger := ctrlLog.FromContext(ctx, "subreconciler", "OpenShiftRoute") + + desiredRoute, err := r.buildOpenShiftRoute(inferenceGraph) + if err != nil { + return "", err + } + + nsName := types.NamespacedName{ + Namespace: desiredRoute.Namespace, + Name: desiredRoute.Name, + } + + actualRoute := v1.Route{} + err = client.IgnoreNotFound(r.Client.Get(ctx, nsName, &actualRoute)) + if err != nil { + return "", err + } + + if len(actualRoute.Name) == 0 { + logger.Info("Creating a new OpenShift Route for InferenceGraph", "namespace", desiredRoute.Namespace, "name", desiredRoute.Name) + err = r.Client.Create(ctx, &desiredRoute) + return getRouteHostname(&desiredRoute), err + } + + if !reflect.DeepEqual(actualRoute.Spec, desiredRoute.Spec) { + logger.Info("Updating OpenShift Route for InferenceGraph", "namespace", desiredRoute.Namespace, "name", desiredRoute.Name) + actualRoute.Spec = desiredRoute.Spec + err = r.Client.Update(ctx, &actualRoute) + } + + return getRouteHostname(&actualRoute), err +} + +func (r *OpenShiftRouteReconciler) buildOpenShiftRoute(inferenceGraph *v1alpha1.InferenceGraph) (v1.Route, error) { + route := v1.Route{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-route", inferenceGraph.Name), + Namespace: inferenceGraph.Namespace, + }, + Spec: v1.RouteSpec{ + To: v1.RouteTargetReference{ + Kind: "Service", + Name: inferenceGraph.GetName(), + }, + Port: &v1.RoutePort{ + TargetPort: intstr.FromString(inferenceGraph.GetName()), + }, + }, + } + + err := controllerutil.SetControllerReference(inferenceGraph, &route, r.Scheme) + return route, err +} + +func getRouteHostname(route *v1.Route) string { + for _, entry := range route.Status.Ingress { + return entry.Host + } + return "" +} diff --git a/pkg/controller/v1alpha1/inferencegraph/suite_test.go b/pkg/controller/v1alpha1/inferencegraph/suite_test.go index 9e653c69e89..261bd38983e 100644 --- a/pkg/controller/v1alpha1/inferencegraph/suite_test.go +++ b/pkg/controller/v1alpha1/inferencegraph/suite_test.go @@ -23,6 +23,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + routev1 "github.com/openshift/api/route/v1" v1 "k8s.io/api/core/v1" netv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -80,6 +81,8 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) err = netv1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = routev1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) Expect(err).ToNot(HaveOccurred()) diff --git a/pkg/controller/v1beta1/inferenceservice/rawkube_controller_test.go b/pkg/controller/v1beta1/inferenceservice/rawkube_controller_test.go index 63927a83baa..a38bbb57e51 100644 --- a/pkg/controller/v1beta1/inferenceservice/rawkube_controller_test.go +++ b/pkg/controller/v1beta1/inferenceservice/rawkube_controller_test.go @@ -19,7 +19,6 @@ package inferenceservice import ( "context" "fmt" - "time" "github.com/kserve/kserve/pkg/apis/serving/v1alpha1" @@ -29,9 +28,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + . "github.com/onsi/ginkgo/v2" + "github.com/kserve/kserve/pkg/apis/serving/v1beta1" "github.com/kserve/kserve/pkg/constants" - . "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" . "github.com/onsi/gomega" @@ -41,7 +41,6 @@ import ( autoscalingv2 "k8s.io/api/autoscaling/v2" v1 "k8s.io/api/core/v1" - v1beta1utils "github.com/kserve/kserve/pkg/controller/v1beta1/inferenceservice/utils" routev1 "github.com/openshift/api/route/v1" netv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -51,6 +50,8 @@ import ( "knative.dev/pkg/apis" duckv1 "knative.dev/pkg/apis/duck/v1" "sigs.k8s.io/controller-runtime/pkg/reconcile" + + v1beta1utils "github.com/kserve/kserve/pkg/controller/v1beta1/inferenceservice/utils" ) var _ = Describe("v1beta1 inference service controller", func() { @@ -2605,21 +2606,22 @@ var _ = Describe("v1beta1 inference service controller", func() { }, WildcardPolicy: routev1.WildcardPolicyNone, }, - Status: routev1.RouteStatus{ - Ingress: []routev1.RouteIngress{ - { - Host: "raw-auth-default.example.com", - Conditions: []routev1.RouteIngressCondition{ - { - Type: routev1.RouteAdmitted, - Status: v1.ConditionTrue, - }, + } + Expect(k8sClient.Create(context.TODO(), route)).Should(Succeed()) + route.Status = routev1.RouteStatus{ + Ingress: []routev1.RouteIngress{ + { + Host: "raw-auth-default.example.com", + Conditions: []routev1.RouteIngressCondition{ + { + Type: routev1.RouteAdmitted, + Status: v1.ConditionTrue, }, }, }, }, } - Expect(k8sClient.Create(context.TODO(), route)).Should(Succeed()) + Expect(k8sClient.Status().Update(ctx, route)).Should(Succeed()) //check isvc status updatedDeployment := actualDeployment.DeepCopy() diff --git a/test/crds/route.openshift.io_routes.yaml b/test/crds/route.openshift.io_routes.yaml index 314ff2fc410..ed0665f53b6 100644 --- a/test/crds/route.openshift.io_routes.yaml +++ b/test/crds/route.openshift.io_routes.yaml @@ -1,10 +1,12 @@ ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + api-approved.openshift.io: https://github.com/openshift/api/pull/1228 + api.openshift.io/merged-by-featuregates: "true" + include.release.openshift.io/ibm-cloud-managed: "true" + include.release.openshift.io/self-managed-high-availability: "true" + release.openshift.io/feature-set: Default name: routes.route.openshift.io spec: group: route.openshift.io @@ -15,88 +17,420 @@ spec: singular: route scope: Namespaced versions: - - name: v1 + - additionalPrinterColumns: + - jsonPath: .status.ingress[0].host + name: Host + type: string + - jsonPath: .status.ingress[0].conditions[?(@.type=="Admitted")].status + name: Admitted + type: string + - jsonPath: .spec.to.name + name: Service + type: string + - jsonPath: .spec.tls.type + name: TLS + type: string + name: v1 schema: openAPIV3Schema: - description: "A route allows developers to expose services through an HTTP(S) - aware load balancing and proxy layer via a public DNS entry. The route may - further specify TLS options and a certificate, or specify a public CNAME - that the router should also accept for HTTP and HTTPS traffic. An administrator - typically configures their router to be visible outside the cluster firewall, - and may also add additional security, caching, or traffic controls on the - service content. Routers usually talk directly to the service endpoints. - \n Once a route is created, the `host` field may not be changed. Generally, - routers use the oldest route with a given host when resolving conflicts. - \n Routers are subject to additional customization and may support additional - controls via the annotations field. \n Because administrators may configure - multiple routers, the route status field is used to return information to - clients about the names and states of the route under each router. If a - client chooses a duplicate name, for instance, the route status conditions - are used to indicate the route cannot be chosen." + description: |- + A route allows developers to expose services through an HTTP(S) aware load balancing and proxy + layer via a public DNS entry. The route may further specify TLS options and a certificate, or + specify a public CNAME that the router should also accept for HTTP and HTTPS traffic. An + administrator typically configures their router to be visible outside the cluster firewall, and + may also add additional security, caching, or traffic controls on the service content. Routers + usually talk directly to the service endpoints. + + Once a route is created, the `host` field may not be changed. Generally, routers use the oldest + route with a given host when resolving conflicts. + + Routers are subject to additional customization and may support additional controls via the + annotations field. + + Because administrators may configure multiple routers, the route status field is used to + return information to clients about the names and states of the route under each router. + If a client chooses a duplicate name, for instance, the route status conditions are used + to indicate the route cannot be chosen. + + To enable HTTP/2 ALPN on a route it requires a custom + (non-wildcard) certificate. This prevents connection coalescing by + clients, notably web browsers. We do not support HTTP/2 ALPN on + routes that use the default certificate because of the risk of + connection re-use/coalescing. Routes that do not have their own + custom certificate will not be HTTP/2 ALPN-enabled on either the + frontend or the backend. + + Compatibility level 1: Stable within a major release for a minimum of 12 months or 3 minor releases (whichever is longer). 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' + 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' + 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: + allOf: + - anyOf: + - properties: + path: + maxLength: 0 + - properties: + tls: + enum: + - null + - not: + properties: + tls: + properties: + termination: + enum: + - passthrough + - anyOf: + - not: + properties: + host: + maxLength: 0 + - not: + properties: + wildcardPolicy: + enum: + - Subdomain description: spec is the desired state of the route properties: alternateBackends: - description: alternateBackends allows up to 3 additional backends - to be assigned to the route. Only the Service kind is allowed, and - it will be defaulted to Service. Use the weight field in RouteTargetReference - object to specify relative preference. + description: |- + alternateBackends allows up to 3 additional backends to be assigned to the route. + Only the Service kind is allowed, and it will be defaulted to Service. + Use the weight field in RouteTargetReference object to specify relative preference. items: - description: RouteTargetReference specifies the target that resolve - into endpoints. Only the 'Service' kind is allowed. Use 'weight' - field to emphasize one over others. + description: |- + RouteTargetReference specifies the target that resolve into endpoints. Only the 'Service' + kind is allowed. Use 'weight' field to emphasize one over others. properties: kind: + default: Service description: The kind of target that the route is referring to. Currently, only 'Service' is allowed + enum: + - Service + - "" type: string name: description: name of the service/target that is being referred to. e.g. name of the service + minLength: 1 type: string weight: - description: weight as an integer between 0 and 256, default - 1, that specifies the target's relative weight against other - target reference objects. 0 suppresses requests to this backend. + default: 100 + description: |- + weight as an integer between 0 and 256, default 100, that specifies the target's relative weight + against other target reference objects. 0 suppresses requests to this backend. format: int32 + maximum: 256 + minimum: 0 type: integer required: - kind - name - - weight type: object + maxItems: 3 type: array + x-kubernetes-list-map-keys: + - name + - kind + x-kubernetes-list-type: map host: - description: host is an alias/DNS that points to the service. Optional. - If not specified a route name will typically be automatically chosen. + description: |- + host is an alias/DNS that points to the service. Optional. + If not specified a route name will typically be automatically + chosen. Must follow DNS952 subdomain conventions. + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ type: string + httpHeaders: + description: httpHeaders defines policy for HTTP headers. + properties: + actions: + description: |- + actions specifies options for modifying headers and their values. + Note that this option only applies to cleartext HTTP connections + and to secure HTTP connections for which the ingress controller + terminates encryption (that is, edge-terminated or reencrypt + connections). Headers cannot be modified for TLS passthrough + connections. + Setting the HSTS (`Strict-Transport-Security`) header is not supported via actions. + `Strict-Transport-Security` may only be configured using the "haproxy.router.openshift.io/hsts_header" + route annotation, and only in accordance with the policy specified in Ingress.Spec.RequiredHSTSPolicies. + In case of HTTP request headers, the actions specified in spec.httpHeaders.actions on the Route will be executed after + the actions specified in the IngressController's spec.httpHeaders.actions field. + In case of HTTP response headers, the actions specified in spec.httpHeaders.actions on the IngressController will be + executed after the actions specified in the Route's spec.httpHeaders.actions field. + The headers set via this API will not appear in access logs. + Any actions defined here are applied after any actions related to the following other fields: + cache-control, spec.clientTLS, + spec.httpHeaders.forwardedHeaderPolicy, spec.httpHeaders.uniqueId, + and spec.httpHeaders.headerNameCaseAdjustments. + The following header names are reserved and may not be modified via this API: + Strict-Transport-Security, Proxy, Cookie, Set-Cookie. + Note that the total size of all net added headers *after* interpolating dynamic values + must not exceed the value of spec.tuningOptions.headerBufferMaxRewriteBytes on the + IngressController. Please refer to the documentation + for that API field for more details. + properties: + request: + description: |- + request is a list of HTTP request headers to modify. + Currently, actions may define to either `Set` or `Delete` headers values. + Actions defined here will modify the request headers of all requests made through a route. + These actions are applied to a specific Route defined within a cluster i.e. connections made through a route. + Currently, actions may define to either `Set` or `Delete` headers values. + Route actions will be executed after IngressController actions for request headers. + Actions are applied in sequence as defined in this list. + A maximum of 20 request header actions may be configured. + You can use this field to specify HTTP request headers that should be set or deleted + when forwarding connections from the client to your application. + Sample fetchers allowed are "req.hdr" and "ssl_c_der". + Converters allowed are "lower" and "base64". + Example header values: "%[req.hdr(X-target),lower]", "%{+Q}[ssl_c_der,base64]". + Any request header configuration applied directly via a Route resource using this API + will override header configuration for a header of the same name applied via + spec.httpHeaders.actions on the IngressController or route annotation. + Note: This field cannot be used if your route uses TLS passthrough. + items: + description: RouteHTTPHeader specifies configuration for + setting or deleting an HTTP header. + properties: + action: + description: action specifies actions to perform on + headers, such as setting or deleting headers. + properties: + set: + description: |- + set defines the HTTP header that should be set: added if it doesn't exist or replaced if it does. + This field is required when type is Set and forbidden otherwise. + properties: + value: + description: |- + value specifies a header value. + Dynamic values can be added. The value will be interpreted as an HAProxy format string as defined in + http://cbonte.github.io/haproxy-dconv/2.6/configuration.html#8.2.6 and may use HAProxy's %[] syntax and + otherwise must be a valid HTTP header value as defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2. + The value of this field must be no more than 16384 characters in length. + Note that the total size of all net added headers *after* interpolating dynamic values + must not exceed the value of spec.tuningOptions.headerBufferMaxRewriteBytes on the + IngressController. + maxLength: 16384 + minLength: 1 + type: string + required: + - value + type: object + type: + description: |- + type defines the type of the action to be applied on the header. + Possible values are Set or Delete. + Set allows you to set HTTP request and response headers. + Delete allows you to delete HTTP request and response headers. + enum: + - Set + - Delete + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: set is required when type is Set, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''Set'' ? has(self.set) + : !has(self.set)' + name: + description: |- + name specifies the name of a header on which to perform an action. Its value must be a valid HTTP header + name as defined in RFC 2616 section 4.2. + The name must consist only of alphanumeric and the following special characters, "-!#$%&'*+.^_`". + The following header names are reserved and may not be modified via this API: + Strict-Transport-Security, Proxy, Cookie, Set-Cookie. + It must be no more than 255 characters in length. + Header name must be unique. + maxLength: 255 + minLength: 1 + pattern: ^[-!#$%&'*+.0-9A-Z^_`a-z|~]+$ + type: string + x-kubernetes-validations: + - message: strict-transport-security header may not + be modified via header actions + rule: self.lowerAscii() != 'strict-transport-security' + - message: proxy header may not be modified via header + actions + rule: self.lowerAscii() != 'proxy' + - message: cookie header may not be modified via header + actions + rule: self.lowerAscii() != 'cookie' + - message: set-cookie header may not be modified via + header actions + rule: self.lowerAscii() != 'set-cookie' + required: + - action + - name + type: object + maxItems: 20 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: Either the header value provided is not in correct + format or the sample fetcher/converter specified is not + allowed. The dynamic header value will be interpreted + as an HAProxy format string as defined in http://cbonte.github.io/haproxy-dconv/2.6/configuration.html#8.2.6 + and may use HAProxy's %[] syntax and otherwise must be + a valid HTTP header value as defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2. + Sample fetchers allowed are req.hdr, ssl_c_der. Converters + allowed are lower, base64. + rule: self.all(key, key.action.type == "Delete" || (has(key.action.set) + && key.action.set.value.matches('^(?:%(?:%|(?:\\{[-+]?[QXE](?:,[-+]?[QXE])*\\})?\\[(?:req\\.hdr\\([0-9A-Za-z-]+\\)|ssl_c_der)(?:,(?:lower|base64))*\\])|[^%[:cntrl:]])+$'))) + response: + description: |- + response is a list of HTTP response headers to modify. + Currently, actions may define to either `Set` or `Delete` headers values. + Actions defined here will modify the response headers of all requests made through a route. + These actions are applied to a specific Route defined within a cluster i.e. connections made through a route. + Route actions will be executed before IngressController actions for response headers. + Actions are applied in sequence as defined in this list. + A maximum of 20 response header actions may be configured. + You can use this field to specify HTTP response headers that should be set or deleted + when forwarding responses from your application to the client. + Sample fetchers allowed are "res.hdr" and "ssl_c_der". + Converters allowed are "lower" and "base64". + Example header values: "%[res.hdr(X-target),lower]", "%{+Q}[ssl_c_der,base64]". + Note: This field cannot be used if your route uses TLS passthrough. + items: + description: RouteHTTPHeader specifies configuration for + setting or deleting an HTTP header. + properties: + action: + description: action specifies actions to perform on + headers, such as setting or deleting headers. + properties: + set: + description: |- + set defines the HTTP header that should be set: added if it doesn't exist or replaced if it does. + This field is required when type is Set and forbidden otherwise. + properties: + value: + description: |- + value specifies a header value. + Dynamic values can be added. The value will be interpreted as an HAProxy format string as defined in + http://cbonte.github.io/haproxy-dconv/2.6/configuration.html#8.2.6 and may use HAProxy's %[] syntax and + otherwise must be a valid HTTP header value as defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2. + The value of this field must be no more than 16384 characters in length. + Note that the total size of all net added headers *after* interpolating dynamic values + must not exceed the value of spec.tuningOptions.headerBufferMaxRewriteBytes on the + IngressController. + maxLength: 16384 + minLength: 1 + type: string + required: + - value + type: object + type: + description: |- + type defines the type of the action to be applied on the header. + Possible values are Set or Delete. + Set allows you to set HTTP request and response headers. + Delete allows you to delete HTTP request and response headers. + enum: + - Set + - Delete + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: set is required when type is Set, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''Set'' ? has(self.set) + : !has(self.set)' + name: + description: |- + name specifies the name of a header on which to perform an action. Its value must be a valid HTTP header + name as defined in RFC 2616 section 4.2. + The name must consist only of alphanumeric and the following special characters, "-!#$%&'*+.^_`". + The following header names are reserved and may not be modified via this API: + Strict-Transport-Security, Proxy, Cookie, Set-Cookie. + It must be no more than 255 characters in length. + Header name must be unique. + maxLength: 255 + minLength: 1 + pattern: ^[-!#$%&'*+.0-9A-Z^_`a-z|~]+$ + type: string + x-kubernetes-validations: + - message: strict-transport-security header may not + be modified via header actions + rule: self.lowerAscii() != 'strict-transport-security' + - message: proxy header may not be modified via header + actions + rule: self.lowerAscii() != 'proxy' + - message: cookie header may not be modified via header + actions + rule: self.lowerAscii() != 'cookie' + - message: set-cookie header may not be modified via + header actions + rule: self.lowerAscii() != 'set-cookie' + required: + - action + - name + type: object + maxItems: 20 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: Either the header value provided is not in correct + format or the sample fetcher/converter specified is not + allowed. The dynamic header value will be interpreted + as an HAProxy format string as defined in http://cbonte.github.io/haproxy-dconv/2.6/configuration.html#8.2.6 + and may use HAProxy's %[] syntax and otherwise must be + a valid HTTP header value as defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2. + Sample fetchers allowed are res.hdr, ssl_c_der. Converters + allowed are lower, base64. + rule: self.all(key, key.action.type == "Delete" || (has(key.action.set) + && key.action.set.value.matches('^(?:%(?:%|(?:\\{[-+]?[QXE](?:,[-+]?[QXE])*\\})?\\[(?:res\\.hdr\\([0-9A-Za-z-]+\\)|ssl_c_der)(?:,(?:lower|base64))*\\])|[^%[:cntrl:]])+$'))) + type: object + type: object path: - description: Path that the router watches for, to route traffic for + description: path that the router watches for, to route traffic for to the service. Optional + pattern: ^/ type: string port: - description: If specified, the port to be used by the router. Most - routers will use all endpoints exposed by the service by default - - set this value to instruct routers which port to use. + description: |- + If specified, the port to be used by the router. Most routers will use all + endpoints exposed by the service by default - set this value to instruct routers + which port to use. properties: targetPort: - anyOf: - - type: integer - - type: string + allOf: + - not: + enum: + - 0 + - not: + enum: + - "" + anyOf: null description: The target port on pods selected by the service this route points to. If this is a string, it will be looked up as a named port in the target endpoints port list. Required @@ -104,7 +438,49 @@ spec: required: - targetPort type: object + subdomain: + description: |- + subdomain is a DNS subdomain that is requested within the ingress controller's + domain (as a subdomain). If host is set this field is ignored. An ingress + controller may choose to ignore this suggested name, in which case the controller + will report the assigned name in the status.ingress array or refuse to admit the + route. If this value is set and the server does not support this field host will + be populated automatically. Otherwise host is left empty. The field may have + multiple parts separated by a dot, but not all ingress controllers may honor + the request. This field may not be changed after creation except by a user with + the update routes/custom-host permission. + + Example: subdomain `frontend` automatically receives the router subdomain + `apps.mycluster.com` to have a full hostname `frontend.apps.mycluster.com`. + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string tls: + allOf: + - anyOf: + - properties: + caCertificate: + maxLength: 0 + certificate: + maxLength: 0 + destinationCACertificate: + maxLength: 0 + key: + maxLength: 0 + - not: + properties: + termination: + enum: + - passthrough + - anyOf: + - properties: + destinationCACertificate: + maxLength: 0 + - not: + properties: + termination: + enum: + - edge description: The tls field provides the ability to configure certificates and termination for the route. properties: @@ -113,76 +489,119 @@ spec: contents type: string certificate: - description: certificate provides certificate contents + description: |- + certificate provides certificate contents. This should be a single serving certificate, not a certificate + chain. Do not include a CA certificate. type: string destinationCACertificate: - description: destinationCACertificate provides the contents of - the ca certificate of the final destination. When using reencrypt - termination this file should be provided in order to have routers - use it for health checks on the secure connection. If this field - is not specified, the router may provide its own destination - CA and perform hostname validation using the short service name - (service.namespace.svc), which allows infrastructure generated - certificates to automatically verify. + description: |- + destinationCACertificate provides the contents of the ca certificate of the final destination. When using reencrypt + termination this file should be provided in order to have routers use it for health checks on the secure connection. + If this field is not specified, the router may provide its own destination CA and perform hostname validation using + the short service name (service.namespace.svc), which allows infrastructure generated certificates to automatically + verify. type: string insecureEdgeTerminationPolicy: - description: "insecureEdgeTerminationPolicy indicates the desired - behavior for insecure connections to a route. While each router - may make its own decisions on which ports to expose, this is - normally port 80. \n * Allow - traffic is sent to the server - on the insecure port (default) * Disable - no traffic is allowed - on the insecure port. * Redirect - clients are redirected to - the secure port." + description: |- + insecureEdgeTerminationPolicy indicates the desired behavior for insecure connections to a route. While + each router may make its own decisions on which ports to expose, this is normally port 80. + + If a route does not specify insecureEdgeTerminationPolicy, then the default behavior is "None". + + * Allow - traffic is sent to the server on the insecure port (edge/reencrypt terminations only). + + * None - no traffic is allowed on the insecure port (default). + + * Redirect - clients are redirected to the secure port. + enum: + - Allow + - None + - Redirect + - "" type: string key: description: key provides key file contents type: string termination: - description: termination indicates termination type. + description: |- + termination indicates termination type. + + * edge - TLS termination is done by the router and http is used to communicate with the backend (default) + * passthrough - Traffic is sent straight to the destination without the router providing TLS termination + * reencrypt - TLS termination is done by the router and https is used to communicate with the backend + + Note: passthrough termination is incompatible with httpHeader actions + enum: + - edge + - reencrypt + - passthrough type: string required: - termination type: object + x-kubernetes-validations: + - message: 'cannot have both spec.tls.termination: passthrough and + spec.tls.insecureEdgeTerminationPolicy: Allow' + rule: 'has(self.termination) && has(self.insecureEdgeTerminationPolicy) + ? !((self.termination==''passthrough'') && (self.insecureEdgeTerminationPolicy==''Allow'')) + : true' to: - description: to is an object the route should use as the primary backend. - Only the Service kind is allowed, and it will be defaulted to Service. - If the weight field (0-256 default 1) is set to zero, no traffic - will be sent to this backend. + description: |- + to is an object the route should use as the primary backend. Only the Service kind + is allowed, and it will be defaulted to Service. If the weight field (0-256 default 100) + is set to zero, no traffic will be sent to this backend. properties: kind: + default: Service description: The kind of target that the route is referring to. Currently, only 'Service' is allowed + enum: + - Service + - "" type: string name: description: name of the service/target that is being referred to. e.g. name of the service + minLength: 1 type: string weight: - description: weight as an integer between 0 and 256, default 1, - that specifies the target's relative weight against other target - reference objects. 0 suppresses requests to this backend. + default: 100 + description: |- + weight as an integer between 0 and 256, default 100, that specifies the target's relative weight + against other target reference objects. 0 suppresses requests to this backend. format: int32 + maximum: 256 + minimum: 0 type: integer required: - kind - name - - weight type: object wildcardPolicy: - description: Wildcard policy if any for the route. Currently only - 'Subdomain' or 'None' is allowed. + default: None + description: |- + Wildcard policy if any for the route. + Currently only 'Subdomain' or 'None' is allowed. + enum: + - None + - Subdomain + - "" type: string required: - - host - to type: object + x-kubernetes-validations: + - message: header actions are not permitted when tls termination is passthrough. + rule: '!has(self.tls) || self.tls.termination != ''passthrough'' || + !has(self.httpHeaders)' status: description: status is the current state of the route properties: ingress: - description: ingress describes the places where the route may be exposed. - The list of ingress points may contain duplicate Host or RouterName - values. Routes are considered live once they are `Ready` + description: |- + ingress describes the places where the route may be exposed. The list of + ingress points may contain duplicate Host or RouterName values. Routes + are considered live once they are `Ready` items: description: RouteIngress holds information about the places where a route is exposed. @@ -190,8 +609,9 @@ spec: conditions: description: Conditions is the state of the route, may be empty. items: - description: RouteIngressCondition contains details for the - current condition of this route on a particular router. + description: |- + RouteIngressCondition contains details for the current condition of this route on a particular + router. properties: lastTransitionTime: description: RFC 3339 date and time when this condition @@ -203,31 +623,36 @@ spec: about last transition. type: string reason: - description: (brief) reason for the condition's last transition, - and is usually a machine and human readable constant + description: |- + (brief) reason for the condition's last transition, and is usually a machine and human + readable constant type: string status: - description: Status is the status of the condition. Can - be True, False, Unknown. + description: |- + Status is the status of the condition. + Can be True, False, Unknown. type: string type: - description: Type is the type of the condition. Currently - only Ready. + description: |- + Type is the type of the condition. + Currently only Admitted or UnservableInFutureVersions. type: string required: - status - type type: object type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map host: description: Host is the host string under which the route is exposed; this value is required type: string routerCanonicalHostname: - description: CanonicalHostname is the external host name for - the router that can be used as a CNAME for the host requested - for this route. This value is optional and may not be set - in all cases. + description: |- + CanonicalHostname is the external host name for the router that can be used as a CNAME + for the host requested for this route. This value is optional and may not be set in all cases. type: string routerName: description: Name is a name chosen by the router to identify @@ -239,18 +664,12 @@ spec: type: string type: object type: array - required: - - ingress + x-kubernetes-list-type: atomic type: object required: - spec - - status type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] + subresources: + status: {}