package lb import ( "context" "encoding/json" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/rest" ) // The Gateway API (gateway.networking.k8s.io/v1, as the v1.6.3 standard channel serves it): the subset this agent // implements, as plain types. The upstream Go module needs a newer client-go than the fork's; the JSON is the // contract, and fields this agent does not implement are simply not decoded. const ( GatewayGroup = "gateway.networking.k8s.io" GatewayVersion = "v1" // GatewayControllerName is the spec.controllerName of the GatewayClasses this agent implements. GatewayControllerName = "reweave.levk.dev/gateway" GatewayClasses = "gatewayclasses" Gateways = "gateways" HTTPRoutes = "httproutes" TCPRoutes = "tcproutes" ReferenceGrants = "referencegrants" ) // GatewayGroupVersion of the Gateway API. var GatewayGroupVersion = schema.GroupVersion{Group: GatewayGroup, Version: GatewayVersion} type GatewayClass struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec GatewayClassSpec `json:"spec"` Status GatewayClassStatus `json:"status,omitempty"` } type GatewayClassSpec struct { ControllerName string `json:"controllerName"` } type GatewayClassStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty"` } type Gateway struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec GatewaySpec `json:"spec"` Status GatewayStatus `json:"status,omitempty"` } type GatewaySpec struct { GatewayClassName string `json:"gatewayClassName"` Listeners []Listener `json:"listeners"` Addresses []GatewayAddress `json:"addresses,omitempty"` } // GatewayAddress is a spec or status address; Type defaults to IPAddress. type GatewayAddress struct { Type *string `json:"type,omitempty"` Value string `json:"value"` } type Listener struct { Name string `json:"name"` Hostname *string `json:"hostname,omitempty"` Port int32 `json:"port"` Protocol string `json:"protocol"` TLS *ListenerTLSConfig `json:"tls,omitempty"` AllowedRoutes *AllowedRoutes `json:"allowedRoutes,omitempty"` } type ListenerTLSConfig struct { Mode *string `json:"mode,omitempty"` CertificateRefs []SecretObjectReference `json:"certificateRefs,omitempty"` } type SecretObjectReference struct { Group *string `json:"group,omitempty"` Kind *string `json:"kind,omitempty"` Name string `json:"name"` Namespace *string `json:"namespace,omitempty"` } type AllowedRoutes struct { Namespaces *RouteNamespaces `json:"namespaces,omitempty"` Kinds []RouteGroupKind `json:"kinds,omitempty"` } type RouteNamespaces struct { From *string `json:"from,omitempty"` Selector *metav1.LabelSelector `json:"selector,omitempty"` } type RouteGroupKind struct { Group *string `json:"group,omitempty"` Kind string `json:"kind"` } type GatewayStatus struct { Addresses []GatewayAddress `json:"addresses,omitempty"` Conditions []metav1.Condition `json:"conditions,omitempty"` Listeners []ListenerStatus `json:"listeners,omitempty"` } type ListenerStatus struct { Name string `json:"name"` SupportedKinds []RouteGroupKind `json:"supportedKinds"` AttachedRoutes int32 `json:"attachedRoutes"` Conditions []metav1.Condition `json:"conditions"` } type ParentReference struct { Group *string `json:"group,omitempty"` Kind *string `json:"kind,omitempty"` Namespace *string `json:"namespace,omitempty"` Name string `json:"name"` SectionName *string `json:"sectionName,omitempty"` Port *int32 `json:"port,omitempty"` } type BackendRef struct { Group *string `json:"group,omitempty"` Kind *string `json:"kind,omitempty"` Name string `json:"name"` Namespace *string `json:"namespace,omitempty"` Port *int32 `json:"port,omitempty"` Weight *int32 `json:"weight,omitempty"` } type RouteStatus struct { Parents []RouteParentStatus `json:"parents"` } type RouteParentStatus struct { ParentRef ParentReference `json:"parentRef"` ControllerName string `json:"controllerName"` Conditions []metav1.Condition `json:"conditions,omitempty"` } type HTTPRoute struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec HTTPRouteSpec `json:"spec"` Status RouteStatus `json:"status,omitempty"` } type HTTPRouteSpec struct { ParentRefs []ParentReference `json:"parentRefs,omitempty"` Hostnames []string `json:"hostnames,omitempty"` Rules []HTTPRouteRule `json:"rules,omitempty"` } type HTTPRouteRule struct { Matches []HTTPRouteMatch `json:"matches,omitempty"` Filters []HTTPRouteFilter `json:"filters,omitempty"` BackendRefs []HTTPBackendRef `json:"backendRefs,omitempty"` } type HTTPRouteMatch struct { Path *HTTPPathMatch `json:"path,omitempty"` Headers []HTTPHeaderMatch `json:"headers,omitempty"` QueryParams []HTTPQueryParamMatch `json:"queryParams,omitempty"` Method *string `json:"method,omitempty"` } type HTTPPathMatch struct { Type *string `json:"type,omitempty"` Value *string `json:"value,omitempty"` } type HTTPHeaderMatch struct { Type *string `json:"type,omitempty"` Name string `json:"name"` Value string `json:"value"` } type HTTPQueryParamMatch struct { Type *string `json:"type,omitempty"` Name string `json:"name"` Value string `json:"value"` } // HTTPRouteFilter: the filters this agent implements are decoded; for the others only their presence is known // (through Type), which is enough to refuse them. type HTTPRouteFilter struct { Type string `json:"type"` RequestHeaderModifier *HTTPHeaderFilter `json:"requestHeaderModifier,omitempty"` ResponseHeaderModifier *HTTPHeaderFilter `json:"responseHeaderModifier,omitempty"` RequestRedirect *HTTPRequestRedirectFilter `json:"requestRedirect,omitempty"` URLRewrite *HTTPURLRewriteFilter `json:"urlRewrite,omitempty"` } type HTTPHeaderFilter struct { Set []HTTPHeader `json:"set,omitempty"` Add []HTTPHeader `json:"add,omitempty"` Remove []string `json:"remove,omitempty"` } type HTTPHeader struct { Name string `json:"name"` Value string `json:"value"` } type HTTPRequestRedirectFilter struct { Scheme *string `json:"scheme,omitempty"` Hostname *string `json:"hostname,omitempty"` Path *HTTPPathModifier `json:"path,omitempty"` Port *int32 `json:"port,omitempty"` StatusCode *int `json:"statusCode,omitempty"` } type HTTPURLRewriteFilter struct { Hostname *string `json:"hostname,omitempty"` Path *HTTPPathModifier `json:"path,omitempty"` } type HTTPPathModifier struct { Type string `json:"type"` ReplaceFullPath *string `json:"replaceFullPath,omitempty"` ReplacePrefixMatch *string `json:"replacePrefixMatch,omitempty"` } type HTTPBackendRef struct { BackendRef `json:",inline"` Filters []HTTPRouteFilter `json:"filters,omitempty"` } type TCPRoute struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec TCPRouteSpec `json:"spec"` Status RouteStatus `json:"status,omitempty"` } type TCPRouteSpec struct { ParentRefs []ParentReference `json:"parentRefs,omitempty"` Rules []TCPRouteRule `json:"rules,omitempty"` } type TCPRouteRule struct { BackendRefs []BackendRef `json:"backendRefs,omitempty"` } type ReferenceGrant struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec ReferenceGrantSpec `json:"spec"` } type ReferenceGrantSpec struct { From []ReferenceGrantFrom `json:"from"` To []ReferenceGrantTo `json:"to"` } type ReferenceGrantFrom struct { Group string `json:"group"` Kind string `json:"kind"` Namespace string `json:"namespace"` } type ReferenceGrantTo struct { Group string `json:"group"` Kind string `json:"kind"` Name *string `json:"name,omitempty"` } type GatewayClassList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` Items []GatewayClass `json:"items"` } type GatewayList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` Items []Gateway `json:"items"` } type HTTPRouteList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` Items []HTTPRoute `json:"items"` } type TCPRouteList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` Items []TCPRoute `json:"items"` } type ReferenceGrantList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` Items []ReferenceGrant `json:"items"` } // jsonCopy deep-copies through JSON: these types are only what the JSON says, so nothing is lost. func jsonCopy(in, out interface{}) { b, err := json.Marshal(in) if err != nil { panic(err) } if err := json.Unmarshal(b, out); err != nil { panic(err) } } func (o *GatewayClass) DeepCopyObject() runtime.Object { out := &GatewayClass{} jsonCopy(o, out) return out } func (o *Gateway) DeepCopyObject() runtime.Object { out := &Gateway{} jsonCopy(o, out) return out } func (o *HTTPRoute) DeepCopyObject() runtime.Object { out := &HTTPRoute{} jsonCopy(o, out) return out } func (o *TCPRoute) DeepCopyObject() runtime.Object { out := &TCPRoute{} jsonCopy(o, out) return out } func (o *ReferenceGrant) DeepCopyObject() runtime.Object { out := &ReferenceGrant{} jsonCopy(o, out) return out } func (o *GatewayClassList) DeepCopyObject() runtime.Object { out := &GatewayClassList{} jsonCopy(o, out) return out } func (o *GatewayList) DeepCopyObject() runtime.Object { out := &GatewayList{} jsonCopy(o, out) return out } func (o *HTTPRouteList) DeepCopyObject() runtime.Object { out := &HTTPRouteList{} jsonCopy(o, out) return out } func (o *TCPRouteList) DeepCopyObject() runtime.Object { out := &TCPRouteList{} jsonCopy(o, out) return out } func (o *ReferenceGrantList) DeepCopyObject() runtime.Object { out := &ReferenceGrantList{} jsonCopy(o, out) return out } func addGatewayTypes(s *runtime.Scheme) error { s.AddKnownTypes(GatewayGroupVersion, &GatewayClass{}, &GatewayClassList{}, &Gateway{}, &GatewayList{}, &HTTPRoute{}, &HTTPRouteList{}, &TCPRoute{}, &TCPRouteList{}, &ReferenceGrant{}, &ReferenceGrantList{}) metav1.AddToGroupVersion(s, GatewayGroupVersion) return nil } // NewGatewayClient is a REST client for the Gateway API kinds this agent reads and writes the status of. func NewGatewayClient(base *rest.Config) (*rest.RESTClient, error) { scheme := runtime.NewScheme() if err := addGatewayTypes(scheme); err != nil { return nil, err } cfg := *base cfg.GroupVersion = &GatewayGroupVersion cfg.APIPath = "/apis" cfg.ContentType = runtime.ContentTypeJSON cfg.NegotiatedSerializer = serializer.NewCodecFactory(scheme).WithoutConversion() if cfg.UserAgent == "" { cfg.UserAgent = rest.DefaultKubernetesUserAgent() } return rest.RESTClientFor(&cfg) } // patchStatus merge-patches an object's status. resourceVersion, when given, makes it conditional: a status built // from a stale read fails instead of overwriting what someone else wrote since. func patchStatus(ctx context.Context, c rest.Interface, resource, namespace, name, resourceVersion string, status interface{}) error { body := map[string]interface{}{"status": status} if resourceVersion != "" { body["metadata"] = map[string]interface{}{"resourceVersion": resourceVersion} } patch, err := json.Marshal(body) if err != nil { return err } req := c.Patch(types.MergePatchType).Resource(resource).Name(name).SubResource("status").Body(patch) if namespace != "" { req = req.Namespace(namespace) } return req.Do(ctx).Error() }