From 4aa584a1a5cb96882e3c661c56bff2c2d9ee5228 Mon Sep 17 00:00:00 2001 From: Zach Smith Date: Mon, 19 Jan 2026 13:08:31 -0800 Subject: [PATCH] feat: HTTPProxy connector name field + validation --- api/v1alpha/httpproxy_types.go | 16 ++ api/v1alpha/zz_generated.deepcopy.go | 20 +++ api/v1alpha1/zz_generated.deepcopy.go | 160 +++++++++++++++++- ...datumapis.com_connectoradvertisements.yaml | 82 +++++++++ .../networking.datumapis.com_httpproxies.yaml | 13 ++ internal/validation/httpproxy_validation.go | 11 ++ .../validation/httpproxy_validation_test.go | 42 +++++ 7 files changed, 342 insertions(+), 2 deletions(-) diff --git a/api/v1alpha/httpproxy_types.go b/api/v1alpha/httpproxy_types.go index e4be37f1..0fd2ed42 100644 --- a/api/v1alpha/httpproxy_types.go +++ b/api/v1alpha/httpproxy_types.go @@ -121,6 +121,14 @@ type HTTPProxyRuleBackend struct { // +kubebuilder:validation:Required Endpoint string `json:"endpoint,omitempty"` + // Connector references the Connector that should be used for this backend. + // + // For now, only a name reference is supported. In the future this can be + // extended to selector-based matching to allow multiple connectors. + // + // +kubebuilder:validation:Optional + Connector *ConnectorReference `json:"connector,omitempty"` + // Filters defined at this level should be executed if and only if the // request is being forwarded to the backend defined here. // @@ -133,6 +141,14 @@ type HTTPProxyRuleBackend struct { Filters []gatewayv1.HTTPRouteFilter `json:"filters,omitempty"` } +// ConnectorReference references a Connector by name. +type ConnectorReference struct { + // Name of the referenced Connector. + // + // +kubebuilder:validation:Required + Name string `json:"name"` +} + // HTTPProxyStatus defines the observed state of HTTPProxy. type HTTPProxyStatus struct { // Addresses lists the network addresses that have been bound to the diff --git a/api/v1alpha/zz_generated.deepcopy.go b/api/v1alpha/zz_generated.deepcopy.go index ad107cea..eee2cb8a 100644 --- a/api/v1alpha/zz_generated.deepcopy.go +++ b/api/v1alpha/zz_generated.deepcopy.go @@ -30,6 +30,21 @@ func (in *AbuseContact) DeepCopy() *AbuseContact { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConnectorReference) DeepCopyInto(out *ConnectorReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectorReference. +func (in *ConnectorReference) DeepCopy() *ConnectorReference { + if in == nil { + return nil + } + out := new(ConnectorReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Contact) DeepCopyInto(out *Contact) { *out = *in @@ -383,6 +398,11 @@ func (in *HTTPProxyRule) DeepCopy() *HTTPProxyRule { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTPProxyRuleBackend) DeepCopyInto(out *HTTPProxyRuleBackend) { *out = *in + if in.Connector != nil { + in, out := &in.Connector, &out.Connector + *out = new(ConnectorReference) + **out = **in + } if in.Filters != nil { in, out := &in.Filters, &out.Filters *out = make([]apisv1.HTTPRouteFilter, len(*in)) diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 497e9444..8b033989 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -16,7 +16,7 @@ func (in *Connector) DeepCopyInto(out *Connector) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) } @@ -43,7 +43,7 @@ func (in *ConnectorAdvertisement) DeepCopyInto(out *ConnectorAdvertisement) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -65,6 +65,48 @@ func (in *ConnectorAdvertisement) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConnectorAdvertisementLayer4) DeepCopyInto(out *ConnectorAdvertisementLayer4) { + *out = *in + if in.Services != nil { + in, out := &in.Services, &out.Services + *out = make([]ConnectorAdvertisementLayer4Service, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectorAdvertisementLayer4. +func (in *ConnectorAdvertisementLayer4) DeepCopy() *ConnectorAdvertisementLayer4 { + if in == nil { + return nil + } + out := new(ConnectorAdvertisementLayer4) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConnectorAdvertisementLayer4Service) DeepCopyInto(out *ConnectorAdvertisementLayer4Service) { + *out = *in + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]Layer4ServicePort, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectorAdvertisementLayer4Service. +func (in *ConnectorAdvertisementLayer4Service) DeepCopy() *ConnectorAdvertisementLayer4Service { + if in == nil { + return nil + } + out := new(ConnectorAdvertisementLayer4Service) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConnectorAdvertisementList) DeepCopyInto(out *ConnectorAdvertisementList) { *out = *in @@ -100,6 +142,18 @@ func (in *ConnectorAdvertisementList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConnectorAdvertisementSpec) DeepCopyInto(out *ConnectorAdvertisementSpec) { *out = *in + if in.ConnectorRef != nil { + in, out := &in.ConnectorRef, &out.ConnectorRef + *out = new(LocalConnectorReference) + **out = **in + } + if in.Layer4 != nil { + in, out := &in.Layer4, &out.Layer4 + *out = make([]ConnectorAdvertisementLayer4, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectorAdvertisementSpec. @@ -130,6 +184,11 @@ func (in *ConnectorAdvertisementStatus) DeepCopy() *ConnectorAdvertisementStatus // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConnectorCapability) DeepCopyInto(out *ConnectorCapability) { *out = *in + if in.ConnectTCP != nil { + in, out := &in.ConnectTCP, &out.ConnectTCP + *out = new(ConnectorCapabilityConnectTCP) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectorCapability. @@ -142,6 +201,59 @@ func (in *ConnectorCapability) DeepCopy() *ConnectorCapability { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConnectorCapabilityCommon) DeepCopyInto(out *ConnectorCapabilityCommon) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectorCapabilityCommon. +func (in *ConnectorCapabilityCommon) DeepCopy() *ConnectorCapabilityCommon { + if in == nil { + return nil + } + out := new(ConnectorCapabilityCommon) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConnectorCapabilityConnectTCP) DeepCopyInto(out *ConnectorCapabilityConnectTCP) { + *out = *in + out.ConnectorCapabilityCommon = in.ConnectorCapabilityCommon +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectorCapabilityConnectTCP. +func (in *ConnectorCapabilityConnectTCP) DeepCopy() *ConnectorCapabilityConnectTCP { + if in == nil { + return nil + } + out := new(ConnectorCapabilityConnectTCP) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConnectorCapabilityStatus) DeepCopyInto(out *ConnectorCapabilityStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectorCapabilityStatus. +func (in *ConnectorCapabilityStatus) DeepCopy() *ConnectorCapabilityStatus { + if in == nil { + return nil + } + out := new(ConnectorCapabilityStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConnectorClass) DeepCopyInto(out *ConnectorClass) { *out = *in @@ -306,6 +418,13 @@ func (in *ConnectorList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConnectorSpec) DeepCopyInto(out *ConnectorSpec) { *out = *in + if in.Capabilities != nil { + in, out := &in.Capabilities, &out.Capabilities + *out = make([]ConnectorCapability, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectorSpec. @@ -321,6 +440,13 @@ func (in *ConnectorSpec) DeepCopy() *ConnectorSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConnectorStatus) DeepCopyInto(out *ConnectorStatus) { *out = *in + if in.Capabilities != nil { + in, out := &in.Capabilities, &out.Capabilities + *out = make([]ConnectorCapabilityStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]v1.Condition, len(*in)) @@ -344,3 +470,33 @@ func (in *ConnectorStatus) DeepCopy() *ConnectorStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Layer4ServicePort) DeepCopyInto(out *Layer4ServicePort) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Layer4ServicePort. +func (in *Layer4ServicePort) DeepCopy() *Layer4ServicePort { + if in == nil { + return nil + } + out := new(Layer4ServicePort) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalConnectorReference) DeepCopyInto(out *LocalConnectorReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalConnectorReference. +func (in *LocalConnectorReference) DeepCopy() *LocalConnectorReference { + if in == nil { + return nil + } + out := new(LocalConnectorReference) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/networking.datumapis.com_connectoradvertisements.yaml b/config/crd/bases/networking.datumapis.com_connectoradvertisements.yaml index 9d1f5e28..2bc646bc 100644 --- a/config/crd/bases/networking.datumapis.com_connectoradvertisements.yaml +++ b/config/crd/bases/networking.datumapis.com_connectoradvertisements.yaml @@ -39,6 +39,88 @@ spec: type: object spec: description: Spec defines the desired state of a ConnectorAdvertisement + properties: + connectorRef: + description: ConnectorRef references the Connector being advertised. + properties: + name: + description: Name of the referenced Connector. + type: string + required: + - name + type: object + layer4: + description: Layer 4 services being advertised. + items: + properties: + name: + description: Name of the advertisement. + type: string + services: + description: Layer 4 services being advertised. + items: + properties: + address: + description: |- + Address of the service. + + Can be an IPv4, IPv6, or a DNS address. A DNS address may contain + wildcards. A DNS address acts as an allow list for what addresses the + connector will allow to be requested through it. + + DNS resolution is the responsibility of the connector. + maxLength: 253 + minLength: 1 + type: string + ports: + description: Ports of the service. + items: + description: Layer4ServicePort represents a port for + a Layer 4 service. + properties: + name: + description: Named port for the service. + type: string + port: + description: Port number for the service. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + default: TCP + description: Protocol for port. Must be TCP or UDP, + defaults to "TCP". + type: string + required: + - name + - port + - protocol + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - address + - ports + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - name + - services + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - connectorRef type: object status: description: Status defines the observed state of a ConnectorAdvertisement diff --git a/config/crd/bases/networking.datumapis.com_httpproxies.yaml b/config/crd/bases/networking.datumapis.com_httpproxies.yaml index 93f9f0e1..57aae645 100644 --- a/config/crd/bases/networking.datumapis.com_httpproxies.yaml +++ b/config/crd/bases/networking.datumapis.com_httpproxies.yaml @@ -127,6 +127,19 @@ spec: will be increased to allow for multiple backends on any given route. items: properties: + connector: + description: |- + Connector references the Connector that should be used for this backend. + + For now, only a name reference is supported. In the future this can be + extended to selector-based matching to allow multiple connectors. + properties: + name: + description: Name of the referenced Connector. + type: string + required: + - name + type: object endpoint: description: |- Endpoint for the backend. Must be a valid URL. diff --git a/internal/validation/httpproxy_validation.go b/internal/validation/httpproxy_validation.go index 89438e56..48cada21 100644 --- a/internal/validation/httpproxy_validation.go +++ b/internal/validation/httpproxy_validation.go @@ -133,6 +133,17 @@ func validateHTTPProxyRuleBackend(backend networkingv1alpha.HTTPProxyRuleBackend } } + if backend.Connector != nil { + connectorFieldPath := fldPath.Child("connector", "name") + if backend.Connector.Name == "" { + allErrs = append(allErrs, field.Required(connectorFieldPath, "connector name is required")) + } else { + for _, msg := range validation.IsDNS1123Label(backend.Connector.Name) { + allErrs = append(allErrs, field.Invalid(connectorFieldPath, backend.Connector.Name, msg)) + } + } + } + allErrs = append(allErrs, validateFilters(backend.Filters, supportedHTTPBackendRefFilters, fldPath.Child("filters"))...) return allErrs } diff --git a/internal/validation/httpproxy_validation_test.go b/internal/validation/httpproxy_validation_test.go index c64950e3..4801098a 100644 --- a/internal/validation/httpproxy_validation_test.go +++ b/internal/validation/httpproxy_validation_test.go @@ -19,6 +19,48 @@ func TestValidateHTTPProxy(t *testing.T) { proxy *networkingv1alpha.HTTPProxy expectedErrors field.ErrorList }{ + "connector name required": { + proxy: &networkingv1alpha.HTTPProxy{ + Spec: networkingv1alpha.HTTPProxySpec{ + Rules: []networkingv1alpha.HTTPProxyRule{ + { + Backends: []networkingv1alpha.HTTPProxyRuleBackend{ + { + Endpoint: "http://example.com", + Connector: &networkingv1alpha.ConnectorReference{ + Name: "", + }, + }, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{ + field.Required(field.NewPath("spec", "rules").Index(0).Child("backends").Index(0).Child("connector", "name"), ""), + }, + }, + "connector name invalid": { + proxy: &networkingv1alpha.HTTPProxy{ + Spec: networkingv1alpha.HTTPProxySpec{ + Rules: []networkingv1alpha.HTTPProxyRule{ + { + Backends: []networkingv1alpha.HTTPProxyRuleBackend{ + { + Endpoint: "http://example.com", + Connector: &networkingv1alpha.ConnectorReference{ + Name: "Invalid.Name", + }, + }, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec", "rules").Index(0).Child("backends").Index(0).Child("connector", "name"), "Invalid", ""), + }, + }, "invalid endpoint URL format": { proxy: &networkingv1alpha.HTTPProxy{ Spec: networkingv1alpha.HTTPProxySpec{