From bf67bcbca775d31bc6c1c0a0f934b6891d23bb4b Mon Sep 17 00:00:00 2001
From: Mohamed Mahmoud <mmahmoud@redhat.com>
Date: Tue, 13 Aug 2024 08:22:35 -0400
Subject: [PATCH] WIP: initial APIS for TCX ebpf program types

Signed-off-by: Mohamed Mahmoud <mmahmoud@redhat.com>
---
 apis/v1alpha1/bpfapplication_types.go         |   2 +-
 apis/v1alpha1/tcxProgram_types.go             |  93 +++++
 apis/v1alpha1/zz_generated.deepcopy.go        | 111 +++++-
 apis/v1alpha1/zz_generated.register.go        |   2 +
 .../manifests/bpfman.io_bpfapplications.yaml  |  44 +--
 .../crd/bases/bpfman.io_bpfapplications.yaml  |  44 +--
 config/crd/bases/bpfman.io_tcxprograms.yaml   | 349 ++++++++++++++++++
 .../apis/v1alpha1/expansion_generated.go      |   4 +
 pkg/client/apis/v1alpha1/tcxprogram.go        |  68 ++++
 .../typed/apis/v1alpha1/apis_client.go        |   5 +
 .../apis/v1alpha1/fake/fake_apis_client.go    |   4 +
 .../apis/v1alpha1/fake/fake_tcxprogram.go     | 132 +++++++
 .../apis/v1alpha1/generated_expansion.go      |   2 +
 .../typed/apis/v1alpha1/tcxprogram.go         | 184 +++++++++
 .../apis/v1alpha1/interface.go                |   7 +
 .../apis/v1alpha1/tcxprogram.go               |  89 +++++
 pkg/client/externalversions/generic.go        |   2 +
 17 files changed, 1072 insertions(+), 70 deletions(-)
 create mode 100644 apis/v1alpha1/tcxProgram_types.go
 create mode 100644 config/crd/bases/bpfman.io_tcxprograms.yaml
 create mode 100644 pkg/client/apis/v1alpha1/tcxprogram.go
 create mode 100644 pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcxprogram.go
 create mode 100644 pkg/client/clientset/typed/apis/v1alpha1/tcxprogram.go
 create mode 100644 pkg/client/externalversions/apis/v1alpha1/tcxprogram.go

diff --git a/apis/v1alpha1/bpfapplication_types.go b/apis/v1alpha1/bpfapplication_types.go
index 6b28064cd..78fb3d595 100644
--- a/apis/v1alpha1/bpfapplication_types.go
+++ b/apis/v1alpha1/bpfapplication_types.go
@@ -87,7 +87,7 @@ type BpfApplicationProgram struct {
 	// tcx defines the desired state of the application's TcPrograms.
 	// +unionMember
 	// +optional
-	TCX *TcProgramInfo `json:"tcx,omitempty"`
+	TCX *TcxProgramInfo `json:"tcx,omitempty"`
 
 	// fentry defines the desired state of the application's FentryPrograms.
 	// +unionMember
diff --git a/apis/v1alpha1/tcxProgram_types.go b/apis/v1alpha1/tcxProgram_types.go
new file mode 100644
index 000000000..bc9a13477
--- /dev/null
+++ b/apis/v1alpha1/tcxProgram_types.go
@@ -0,0 +1,93 @@
+/*
+Copyright 2024.
+
+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.
+*/
+
+// All fields are required unless explicitly marked optional
+// +kubebuilder:validation:Required
+package v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// +genclient
+// +genclient:nonNamespaced
+//+kubebuilder:object:root=true
+//+kubebuilder:subresource:status
+//+kubebuilder:resource:scope=Cluster
+
+// TcxProgram is the Schema for the TcxProgram API
+// +kubebuilder:printcolumn:name="BpfFunctionName",type=string,JSONPath=`.spec.bpffunctionname`
+// +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector`
+// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason`
+// +kubebuilder:printcolumn:name="Direction",type=string,JSONPath=`.spec.direction`,priority=1
+// +kubebuilder:printcolumn:name="InterfaceSelector",type=string,JSONPath=`.spec.interfaceselector`,priority=1
+// +kubebuilder:printcolumn:name="Position",type=string,JSONPath=`.spec.position`,priority=1
+type TcxProgram struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec TcxProgramSpec `json:"spec"`
+	// +optional
+	Status TcxProgramStatus `json:"status,omitempty"`
+}
+
+// TcxProgramSpec defines the desired state of TcxProgram
+type TcxProgramSpec struct {
+	TcxProgramInfo `json:",inline"`
+	BpfAppCommon   `json:",inline"`
+}
+
+// TCXPositionType defines the TCX position string
+type TCXPositionType string
+
+const (
+	// TCXFirst TCX program will be the first program to execute.
+	TCXFirst TCXPositionType = "First"
+
+	// TCXLast TCX program will be the last program to execute.
+	TCXLast TCXPositionType = "Last"
+)
+
+// TcxProgramInfo defines the tc program details
+type TcxProgramInfo struct {
+	BpfProgramCommon `json:",inline"`
+
+	// Selector to determine the network interface (or interfaces)
+	InterfaceSelector InterfaceSelector `json:"interfaceselector"`
+
+	// Direction specifies the direction of traffic the tcx program should
+	// attach to for a given network device.
+	// +kubebuilder:validation:Enum=ingress;egress
+	Direction string `json:"direction"`
+
+	// Anchor defines the order of the TCX relative to other TCX attached to the same interface
+	// +kubebuilder:validation:Enum:="First";"Last"
+	//+kubebuilder:default:=Last
+	Position TCXPositionType `json:"position"`
+}
+
+// TcxProgramStatus defines the observed state of TcProgram
+type TcxProgramStatus struct {
+	BpfProgramStatusCommon `json:",inline"`
+}
+
+// +kubebuilder:object:root=true
+// TcxProgramList contains a list of TcxPrograms
+type TcxProgramList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []TcxProgram `json:"items"`
+}
diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go
index f2e712ba1..80f3b52cd 100644
--- a/apis/v1alpha1/zz_generated.deepcopy.go
+++ b/apis/v1alpha1/zz_generated.deepcopy.go
@@ -132,7 +132,7 @@ func (in *BpfApplicationProgram) DeepCopyInto(out *BpfApplicationProgram) {
 	}
 	if in.TCX != nil {
 		in, out := &in.TCX, &out.TCX
-		*out = new(TcProgramInfo)
+		*out = new(TcxProgramInfo)
 		(*in).DeepCopyInto(*out)
 	}
 	if in.Fentry != nil {
@@ -907,6 +907,115 @@ func (in *TcProgramStatus) DeepCopy() *TcProgramStatus {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TcxProgram) DeepCopyInto(out *TcxProgram) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcxProgram.
+func (in *TcxProgram) DeepCopy() *TcxProgram {
+	if in == nil {
+		return nil
+	}
+	out := new(TcxProgram)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *TcxProgram) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TcxProgramInfo) DeepCopyInto(out *TcxProgramInfo) {
+	*out = *in
+	in.BpfProgramCommon.DeepCopyInto(&out.BpfProgramCommon)
+	in.InterfaceSelector.DeepCopyInto(&out.InterfaceSelector)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcxProgramInfo.
+func (in *TcxProgramInfo) DeepCopy() *TcxProgramInfo {
+	if in == nil {
+		return nil
+	}
+	out := new(TcxProgramInfo)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TcxProgramList) DeepCopyInto(out *TcxProgramList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]TcxProgram, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcxProgramList.
+func (in *TcxProgramList) DeepCopy() *TcxProgramList {
+	if in == nil {
+		return nil
+	}
+	out := new(TcxProgramList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *TcxProgramList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TcxProgramSpec) DeepCopyInto(out *TcxProgramSpec) {
+	*out = *in
+	in.TcxProgramInfo.DeepCopyInto(&out.TcxProgramInfo)
+	in.BpfAppCommon.DeepCopyInto(&out.BpfAppCommon)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcxProgramSpec.
+func (in *TcxProgramSpec) DeepCopy() *TcxProgramSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(TcxProgramSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TcxProgramStatus) DeepCopyInto(out *TcxProgramStatus) {
+	*out = *in
+	in.BpfProgramStatusCommon.DeepCopyInto(&out.BpfProgramStatusCommon)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcxProgramStatus.
+func (in *TcxProgramStatus) DeepCopy() *TcxProgramStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(TcxProgramStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *TracepointProgram) DeepCopyInto(out *TracepointProgram) {
 	*out = *in
diff --git a/apis/v1alpha1/zz_generated.register.go b/apis/v1alpha1/zz_generated.register.go
index 3d3ad9e37..5b55645af 100644
--- a/apis/v1alpha1/zz_generated.register.go
+++ b/apis/v1alpha1/zz_generated.register.go
@@ -73,6 +73,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
 		&KprobeProgramList{},
 		&TcProgram{},
 		&TcProgramList{},
+		&TcxProgram{},
+		&TcxProgramList{},
 		&TracepointProgram{},
 		&TracepointProgramList{},
 		&UprobeProgram{},
diff --git a/bundle/manifests/bpfman.io_bpfapplications.yaml b/bundle/manifests/bpfman.io_bpfapplications.yaml
index df54bbf6c..aadcf815b 100644
--- a/bundle/manifests/bpfman.io_bpfapplications.yaml
+++ b/bundle/manifests/bpfman.io_bpfapplications.yaml
@@ -584,7 +584,7 @@ spec:
                           type: string
                         direction:
                           description: |-
-                            Direction specifies the direction of traffic the tc program should
+                            Direction specifies the direction of traffic the tcx program should
                             attach to for a given network device.
                           enum:
                           - ingress
@@ -658,43 +658,19 @@ spec:
                               type: object
                           type: object
                           x-kubernetes-map-type: atomic
-                        priority:
-                          description: |-
-                            Priority specifies the priority of the tc program in relation to
-                            other programs of the same type with the same attach point. It is a value
-                            from 0 to 1000 where lower values have higher precedence.
-                          format: int32
-                          maximum: 1000
-                          minimum: 0
-                          type: integer
-                        proceedon:
-                          default:
-                          - pipe
-                          - dispatcher_return
-                          description: |-
-                            ProceedOn allows the user to call other tc programs in chain on this exit code.
-                            Multiple values are supported by repeating the parameter.
-                          items:
-                            enum:
-                            - unspec
-                            - ok
-                            - reclassify
-                            - shot
-                            - pipe
-                            - stolen
-                            - queued
-                            - repeat
-                            - redirect
-                            - trap
-                            - dispatcher_return
-                            type: string
-                          maxItems: 11
-                          type: array
+                        position:
+                          default: Last
+                          description: Anchor defines the order of the TCX relative
+                            to other TCX attached to the same interface
+                          enum:
+                          - First
+                          - Last
+                          type: string
                       required:
                       - bpffunctionname
                       - direction
                       - interfaceselector
-                      - priority
+                      - position
                       type: object
                     tracepoint:
                       description: tracepoint defines the desired state of the application's
diff --git a/config/crd/bases/bpfman.io_bpfapplications.yaml b/config/crd/bases/bpfman.io_bpfapplications.yaml
index 845d51f86..f5f73f9d2 100644
--- a/config/crd/bases/bpfman.io_bpfapplications.yaml
+++ b/config/crd/bases/bpfman.io_bpfapplications.yaml
@@ -584,7 +584,7 @@ spec:
                           type: string
                         direction:
                           description: |-
-                            Direction specifies the direction of traffic the tc program should
+                            Direction specifies the direction of traffic the tcx program should
                             attach to for a given network device.
                           enum:
                           - ingress
@@ -658,43 +658,19 @@ spec:
                               type: object
                           type: object
                           x-kubernetes-map-type: atomic
-                        priority:
-                          description: |-
-                            Priority specifies the priority of the tc program in relation to
-                            other programs of the same type with the same attach point. It is a value
-                            from 0 to 1000 where lower values have higher precedence.
-                          format: int32
-                          maximum: 1000
-                          minimum: 0
-                          type: integer
-                        proceedon:
-                          default:
-                          - pipe
-                          - dispatcher_return
-                          description: |-
-                            ProceedOn allows the user to call other tc programs in chain on this exit code.
-                            Multiple values are supported by repeating the parameter.
-                          items:
-                            enum:
-                            - unspec
-                            - ok
-                            - reclassify
-                            - shot
-                            - pipe
-                            - stolen
-                            - queued
-                            - repeat
-                            - redirect
-                            - trap
-                            - dispatcher_return
-                            type: string
-                          maxItems: 11
-                          type: array
+                        position:
+                          default: Last
+                          description: Anchor defines the order of the TCX relative
+                            to other TCX attached to the same interface
+                          enum:
+                          - First
+                          - Last
+                          type: string
                       required:
                       - bpffunctionname
                       - direction
                       - interfaceselector
-                      - priority
+                      - position
                       type: object
                     tracepoint:
                       description: tracepoint defines the desired state of the application's
diff --git a/config/crd/bases/bpfman.io_tcxprograms.yaml b/config/crd/bases/bpfman.io_tcxprograms.yaml
new file mode 100644
index 000000000..9f8cb69bf
--- /dev/null
+++ b/config/crd/bases/bpfman.io_tcxprograms.yaml
@@ -0,0 +1,349 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.15.0
+  name: tcxprograms.bpfman.io
+spec:
+  group: bpfman.io
+  names:
+    kind: TcxProgram
+    listKind: TcxProgramList
+    plural: tcxprograms
+    singular: tcxprogram
+  scope: Cluster
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.bpffunctionname
+      name: BpfFunctionName
+      type: string
+    - jsonPath: .spec.nodeselector
+      name: NodeSelector
+      type: string
+    - jsonPath: .status.conditions[0].reason
+      name: Status
+      type: string
+    - jsonPath: .spec.direction
+      name: Direction
+      priority: 1
+      type: string
+    - jsonPath: .spec.interfaceselector
+      name: InterfaceSelector
+      priority: 1
+      type: string
+    - jsonPath: .spec.position
+      name: Position
+      priority: 1
+      type: string
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: TcxProgram is the Schema for the TcxProgram API
+        properties:
+          apiVersion:
+            description: |-
+              APIVersion defines the versioned schema of this representation of an object.
+              Servers should convert recognized schemas to the latest internal value, and
+              may reject unrecognized values.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+            type: string
+          kind:
+            description: |-
+              Kind is a string value representing the REST resource this object represents.
+              Servers may infer this from the endpoint the client submits requests to.
+              Cannot be updated.
+              In CamelCase.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: TcxProgramSpec defines the desired state of TcxProgram
+            properties:
+              bpffunctionname:
+                description: |-
+                  BpfFunctionName is the name of the function that is the entry point for the BPF
+                  program
+                type: string
+              bytecode:
+                description: |-
+                  Bytecode configures where the bpf program's bytecode should be loaded
+                  from.
+                properties:
+                  image:
+                    description: Image used to specify a bytecode container image.
+                    properties:
+                      imagepullpolicy:
+                        default: IfNotPresent
+                        description: PullPolicy describes a policy for if/when to
+                          pull a bytecode image. Defaults to IfNotPresent.
+                        enum:
+                        - Always
+                        - Never
+                        - IfNotPresent
+                        type: string
+                      imagepullsecret:
+                        description: |-
+                          ImagePullSecret is the name of the secret bpfman should use to get remote image
+                          repository secrets.
+                        properties:
+                          name:
+                            description: Name of the secret which contains the credentials
+                              to access the image repository.
+                            type: string
+                          namespace:
+                            description: Namespace of the secret which contains the
+                              credentials to access the image repository.
+                            type: string
+                        required:
+                        - name
+                        - namespace
+                        type: object
+                      url:
+                        description: Valid container image URL used to reference a
+                          remote bytecode image.
+                        type: string
+                    required:
+                    - url
+                    type: object
+                  path:
+                    description: Path is used to specify a bytecode object via filepath.
+                    type: string
+                type: object
+              direction:
+                description: |-
+                  Direction specifies the direction of traffic the tcx program should
+                  attach to for a given network device.
+                enum:
+                - ingress
+                - egress
+                type: string
+              globaldata:
+                additionalProperties:
+                  format: byte
+                  type: string
+                description: |-
+                  GlobalData allows the user to set global variables when the program is loaded
+                  with an array of raw bytes. This is a very low level primitive. The caller
+                  is responsible for formatting the byte string appropriately considering
+                  such things as size, endianness, alignment and packing of data structures.
+                type: object
+              interfaceselector:
+                description: Selector to determine the network interface (or interfaces)
+                maxProperties: 1
+                minProperties: 1
+                properties:
+                  interfaces:
+                    description: |-
+                      Interfaces refers to a list of network interfaces to attach the BPF
+                      program to.
+                    items:
+                      type: string
+                    type: array
+                  primarynodeinterface:
+                    description: Attach BPF program to the primary interface on the
+                      node. Only 'true' accepted.
+                    type: boolean
+                type: object
+              mapownerselector:
+                description: |-
+                  MapOwnerSelector is used to select the loaded eBPF program this eBPF program
+                  will share a map with. The value is a label applied to the BpfProgram to select.
+                  The selector must resolve to exactly one instance of a BpfProgram on a given node
+                  or the eBPF program will not load.
+                properties:
+                  matchExpressions:
+                    description: matchExpressions is a list of label selector requirements.
+                      The requirements are ANDed.
+                    items:
+                      description: |-
+                        A label selector requirement is a selector that contains values, a key, and an operator that
+                        relates the key and values.
+                      properties:
+                        key:
+                          description: key is the label key that the selector applies
+                            to.
+                          type: string
+                        operator:
+                          description: |-
+                            operator represents a key's relationship to a set of values.
+                            Valid operators are In, NotIn, Exists and DoesNotExist.
+                          type: string
+                        values:
+                          description: |-
+                            values is an array of string values. If the operator is In or NotIn,
+                            the values array must be non-empty. If the operator is Exists or DoesNotExist,
+                            the values array must be empty. This array is replaced during a strategic
+                            merge patch.
+                          items:
+                            type: string
+                          type: array
+                          x-kubernetes-list-type: atomic
+                      required:
+                      - key
+                      - operator
+                      type: object
+                    type: array
+                    x-kubernetes-list-type: atomic
+                  matchLabels:
+                    additionalProperties:
+                      type: string
+                    description: |-
+                      matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
+                      map is equivalent to an element of matchExpressions, whose key field is "key", the
+                      operator is "In", and the values array contains only "value". The requirements are ANDed.
+                    type: object
+                type: object
+                x-kubernetes-map-type: atomic
+              nodeselector:
+                description: |-
+                  NodeSelector allows the user to specify which nodes to deploy the
+                  bpf program to. This field must be specified, to select all nodes
+                  use standard metav1.LabelSelector semantics and make it empty.
+                properties:
+                  matchExpressions:
+                    description: matchExpressions is a list of label selector requirements.
+                      The requirements are ANDed.
+                    items:
+                      description: |-
+                        A label selector requirement is a selector that contains values, a key, and an operator that
+                        relates the key and values.
+                      properties:
+                        key:
+                          description: key is the label key that the selector applies
+                            to.
+                          type: string
+                        operator:
+                          description: |-
+                            operator represents a key's relationship to a set of values.
+                            Valid operators are In, NotIn, Exists and DoesNotExist.
+                          type: string
+                        values:
+                          description: |-
+                            values is an array of string values. If the operator is In or NotIn,
+                            the values array must be non-empty. If the operator is Exists or DoesNotExist,
+                            the values array must be empty. This array is replaced during a strategic
+                            merge patch.
+                          items:
+                            type: string
+                          type: array
+                          x-kubernetes-list-type: atomic
+                      required:
+                      - key
+                      - operator
+                      type: object
+                    type: array
+                    x-kubernetes-list-type: atomic
+                  matchLabels:
+                    additionalProperties:
+                      type: string
+                    description: |-
+                      matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
+                      map is equivalent to an element of matchExpressions, whose key field is "key", the
+                      operator is "In", and the values array contains only "value". The requirements are ANDed.
+                    type: object
+                type: object
+                x-kubernetes-map-type: atomic
+              position:
+                default: Last
+                description: Anchor defines the order of the TCX relative to other
+                  TCX attached to the same interface
+                enum:
+                - First
+                - Last
+                type: string
+            required:
+            - bpffunctionname
+            - bytecode
+            - direction
+            - interfaceselector
+            - nodeselector
+            - position
+            type: object
+          status:
+            description: TcxProgramStatus defines the observed state of TcProgram
+            properties:
+              conditions:
+                description: |-
+                  Conditions houses the global cluster state for the eBPFProgram. The explicit
+                  condition types are defined internally.
+                items:
+                  description: "Condition contains details for one aspect of the current
+                    state of this API Resource.\n---\nThis struct is intended for
+                    direct use as an array at the field path .status.conditions.  For
+                    example,\n\n\n\ttype FooStatus struct{\n\t    // Represents the
+                    observations of a foo's current state.\n\t    // Known .status.conditions.type
+                    are: \"Available\", \"Progressing\", and \"Degraded\"\n\t    //
+                    +patchMergeKey=type\n\t    // +patchStrategy=merge\n\t    // +listType=map\n\t
+                    \   // +listMapKey=type\n\t    Conditions []metav1.Condition `json:\"conditions,omitempty\"
+                    patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t
+                    \   // other fields\n\t}"
+                  properties:
+                    lastTransitionTime:
+                      description: |-
+                        lastTransitionTime is the last time the condition transitioned from one status to another.
+                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.
+                      format: date-time
+                      type: string
+                    message:
+                      description: |-
+                        message is a human readable message indicating details about the transition.
+                        This may be an empty string.
+                      maxLength: 32768
+                      type: string
+                    observedGeneration:
+                      description: |-
+                        observedGeneration represents the .metadata.generation that the condition was set based upon.
+                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+                        with respect to the current state of the instance.
+                      format: int64
+                      minimum: 0
+                      type: integer
+                    reason:
+                      description: |-
+                        reason contains a programmatic identifier indicating the reason for the condition's last transition.
+                        Producers of specific condition types may define expected values and meanings for this field,
+                        and whether the values are considered a guaranteed API.
+                        The value should be a CamelCase string.
+                        This field may not be empty.
+                      maxLength: 1024
+                      minLength: 1
+                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+                      type: string
+                    status:
+                      description: status of the condition, one of True, False, Unknown.
+                      enum:
+                      - "True"
+                      - "False"
+                      - Unknown
+                      type: string
+                    type:
+                      description: |-
+                        type of condition in CamelCase or in foo.example.com/CamelCase.
+                        ---
+                        Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be
+                        useful (see .node.status.conditions), the ability to deconflict is important.
+                        The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
+                      maxLength: 316
+                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+                      type: string
+                  required:
+                  - lastTransitionTime
+                  - message
+                  - reason
+                  - status
+                  - type
+                  type: object
+                type: array
+                x-kubernetes-list-map-keys:
+                - type
+                x-kubernetes-list-type: map
+            type: object
+        required:
+        - spec
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
diff --git a/pkg/client/apis/v1alpha1/expansion_generated.go b/pkg/client/apis/v1alpha1/expansion_generated.go
index 7c31cea1b..df6e451bd 100644
--- a/pkg/client/apis/v1alpha1/expansion_generated.go
+++ b/pkg/client/apis/v1alpha1/expansion_generated.go
@@ -42,6 +42,10 @@ type KprobeProgramListerExpansion interface{}
 // TcProgramLister.
 type TcProgramListerExpansion interface{}
 
+// TcxProgramListerExpansion allows custom methods to be added to
+// TcxProgramLister.
+type TcxProgramListerExpansion interface{}
+
 // TracepointProgramListerExpansion allows custom methods to be added to
 // TracepointProgramLister.
 type TracepointProgramListerExpansion interface{}
diff --git a/pkg/client/apis/v1alpha1/tcxprogram.go b/pkg/client/apis/v1alpha1/tcxprogram.go
new file mode 100644
index 000000000..c65dfd8b5
--- /dev/null
+++ b/pkg/client/apis/v1alpha1/tcxprogram.go
@@ -0,0 +1,68 @@
+/*
+Copyright 2023 The bpfman 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.
+*/
+
+// Code generated by lister-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+	v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1"
+	"k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/client-go/tools/cache"
+)
+
+// TcxProgramLister helps list TcxPrograms.
+// All objects returned here must be treated as read-only.
+type TcxProgramLister interface {
+	// List lists all TcxPrograms in the indexer.
+	// Objects returned here must be treated as read-only.
+	List(selector labels.Selector) (ret []*v1alpha1.TcxProgram, err error)
+	// Get retrieves the TcxProgram from the index for a given name.
+	// Objects returned here must be treated as read-only.
+	Get(name string) (*v1alpha1.TcxProgram, error)
+	TcxProgramListerExpansion
+}
+
+// tcxProgramLister implements the TcxProgramLister interface.
+type tcxProgramLister struct {
+	indexer cache.Indexer
+}
+
+// NewTcxProgramLister returns a new TcxProgramLister.
+func NewTcxProgramLister(indexer cache.Indexer) TcxProgramLister {
+	return &tcxProgramLister{indexer: indexer}
+}
+
+// List lists all TcxPrograms in the indexer.
+func (s *tcxProgramLister) List(selector labels.Selector) (ret []*v1alpha1.TcxProgram, err error) {
+	err = cache.ListAll(s.indexer, selector, func(m interface{}) {
+		ret = append(ret, m.(*v1alpha1.TcxProgram))
+	})
+	return ret, err
+}
+
+// Get retrieves the TcxProgram from the index for a given name.
+func (s *tcxProgramLister) Get(name string) (*v1alpha1.TcxProgram, error) {
+	obj, exists, err := s.indexer.GetByKey(name)
+	if err != nil {
+		return nil, err
+	}
+	if !exists {
+		return nil, errors.NewNotFound(v1alpha1.Resource("tcxprogram"), name)
+	}
+	return obj.(*v1alpha1.TcxProgram), nil
+}
diff --git a/pkg/client/clientset/typed/apis/v1alpha1/apis_client.go b/pkg/client/clientset/typed/apis/v1alpha1/apis_client.go
index c1d54ecdd..ac2e4d3a1 100644
--- a/pkg/client/clientset/typed/apis/v1alpha1/apis_client.go
+++ b/pkg/client/clientset/typed/apis/v1alpha1/apis_client.go
@@ -34,6 +34,7 @@ type BpfmanV1alpha1Interface interface {
 	FexitProgramsGetter
 	KprobeProgramsGetter
 	TcProgramsGetter
+	TcxProgramsGetter
 	TracepointProgramsGetter
 	UprobeProgramsGetter
 	XdpProgramsGetter
@@ -68,6 +69,10 @@ func (c *BpfmanV1alpha1Client) TcPrograms() TcProgramInterface {
 	return newTcPrograms(c)
 }
 
+func (c *BpfmanV1alpha1Client) TcxPrograms() TcxProgramInterface {
+	return newTcxPrograms(c)
+}
+
 func (c *BpfmanV1alpha1Client) TracepointPrograms() TracepointProgramInterface {
 	return newTracepointPrograms(c)
 }
diff --git a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_apis_client.go b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_apis_client.go
index 7acfcffc8..b83fbca87 100644
--- a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_apis_client.go
+++ b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_apis_client.go
@@ -52,6 +52,10 @@ func (c *FakeBpfmanV1alpha1) TcPrograms() v1alpha1.TcProgramInterface {
 	return &FakeTcPrograms{c}
 }
 
+func (c *FakeBpfmanV1alpha1) TcxPrograms() v1alpha1.TcxProgramInterface {
+	return &FakeTcxPrograms{c}
+}
+
 func (c *FakeBpfmanV1alpha1) TracepointPrograms() v1alpha1.TracepointProgramInterface {
 	return &FakeTracepointPrograms{c}
 }
diff --git a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcxprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcxprogram.go
new file mode 100644
index 000000000..24df1d922
--- /dev/null
+++ b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcxprogram.go
@@ -0,0 +1,132 @@
+/*
+Copyright 2023 The bpfman 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package fake
+
+import (
+	"context"
+
+	v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	labels "k8s.io/apimachinery/pkg/labels"
+	types "k8s.io/apimachinery/pkg/types"
+	watch "k8s.io/apimachinery/pkg/watch"
+	testing "k8s.io/client-go/testing"
+)
+
+// FakeTcxPrograms implements TcxProgramInterface
+type FakeTcxPrograms struct {
+	Fake *FakeBpfmanV1alpha1
+}
+
+var tcxprogramsResource = v1alpha1.SchemeGroupVersion.WithResource("tcxprograms")
+
+var tcxprogramsKind = v1alpha1.SchemeGroupVersion.WithKind("TcxProgram")
+
+// Get takes name of the tcxProgram, and returns the corresponding tcxProgram object, and an error if there is any.
+func (c *FakeTcxPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.TcxProgram, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewRootGetAction(tcxprogramsResource, name), &v1alpha1.TcxProgram{})
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*v1alpha1.TcxProgram), err
+}
+
+// List takes label and field selectors, and returns the list of TcxPrograms that match those selectors.
+func (c *FakeTcxPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.TcxProgramList, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewRootListAction(tcxprogramsResource, tcxprogramsKind, opts), &v1alpha1.TcxProgramList{})
+	if obj == nil {
+		return nil, err
+	}
+
+	label, _, _ := testing.ExtractFromListOptions(opts)
+	if label == nil {
+		label = labels.Everything()
+	}
+	list := &v1alpha1.TcxProgramList{ListMeta: obj.(*v1alpha1.TcxProgramList).ListMeta}
+	for _, item := range obj.(*v1alpha1.TcxProgramList).Items {
+		if label.Matches(labels.Set(item.Labels)) {
+			list.Items = append(list.Items, item)
+		}
+	}
+	return list, err
+}
+
+// Watch returns a watch.Interface that watches the requested tcxPrograms.
+func (c *FakeTcxPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
+	return c.Fake.
+		InvokesWatch(testing.NewRootWatchAction(tcxprogramsResource, opts))
+}
+
+// Create takes the representation of a tcxProgram and creates it.  Returns the server's representation of the tcxProgram, and an error, if there is any.
+func (c *FakeTcxPrograms) Create(ctx context.Context, tcxProgram *v1alpha1.TcxProgram, opts v1.CreateOptions) (result *v1alpha1.TcxProgram, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewRootCreateAction(tcxprogramsResource, tcxProgram), &v1alpha1.TcxProgram{})
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*v1alpha1.TcxProgram), err
+}
+
+// Update takes the representation of a tcxProgram and updates it. Returns the server's representation of the tcxProgram, and an error, if there is any.
+func (c *FakeTcxPrograms) Update(ctx context.Context, tcxProgram *v1alpha1.TcxProgram, opts v1.UpdateOptions) (result *v1alpha1.TcxProgram, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewRootUpdateAction(tcxprogramsResource, tcxProgram), &v1alpha1.TcxProgram{})
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*v1alpha1.TcxProgram), err
+}
+
+// UpdateStatus was generated because the type contains a Status member.
+// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+func (c *FakeTcxPrograms) UpdateStatus(ctx context.Context, tcxProgram *v1alpha1.TcxProgram, opts v1.UpdateOptions) (*v1alpha1.TcxProgram, error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewRootUpdateSubresourceAction(tcxprogramsResource, "status", tcxProgram), &v1alpha1.TcxProgram{})
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*v1alpha1.TcxProgram), err
+}
+
+// Delete takes name of the tcxProgram and deletes it. Returns an error if one occurs.
+func (c *FakeTcxPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
+	_, err := c.Fake.
+		Invokes(testing.NewRootDeleteActionWithOptions(tcxprogramsResource, name, opts), &v1alpha1.TcxProgram{})
+	return err
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *FakeTcxPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
+	action := testing.NewRootDeleteCollectionAction(tcxprogramsResource, listOpts)
+
+	_, err := c.Fake.Invokes(action, &v1alpha1.TcxProgramList{})
+	return err
+}
+
+// Patch applies the patch and returns the patched tcxProgram.
+func (c *FakeTcxPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TcxProgram, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewRootPatchSubresourceAction(tcxprogramsResource, name, pt, data, subresources...), &v1alpha1.TcxProgram{})
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*v1alpha1.TcxProgram), err
+}
diff --git a/pkg/client/clientset/typed/apis/v1alpha1/generated_expansion.go b/pkg/client/clientset/typed/apis/v1alpha1/generated_expansion.go
index 6c297eeee..c514790d0 100644
--- a/pkg/client/clientset/typed/apis/v1alpha1/generated_expansion.go
+++ b/pkg/client/clientset/typed/apis/v1alpha1/generated_expansion.go
@@ -30,6 +30,8 @@ type KprobeProgramExpansion interface{}
 
 type TcProgramExpansion interface{}
 
+type TcxProgramExpansion interface{}
+
 type TracepointProgramExpansion interface{}
 
 type UprobeProgramExpansion interface{}
diff --git a/pkg/client/clientset/typed/apis/v1alpha1/tcxprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/tcxprogram.go
new file mode 100644
index 000000000..8363b99d8
--- /dev/null
+++ b/pkg/client/clientset/typed/apis/v1alpha1/tcxprogram.go
@@ -0,0 +1,184 @@
+/*
+Copyright 2023 The bpfman 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+	"context"
+	"time"
+
+	v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1"
+	scheme "github.com/bpfman/bpfman-operator/pkg/client/clientset/scheme"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	types "k8s.io/apimachinery/pkg/types"
+	watch "k8s.io/apimachinery/pkg/watch"
+	rest "k8s.io/client-go/rest"
+)
+
+// TcxProgramsGetter has a method to return a TcxProgramInterface.
+// A group's client should implement this interface.
+type TcxProgramsGetter interface {
+	TcxPrograms() TcxProgramInterface
+}
+
+// TcxProgramInterface has methods to work with TcxProgram resources.
+type TcxProgramInterface interface {
+	Create(ctx context.Context, tcxProgram *v1alpha1.TcxProgram, opts v1.CreateOptions) (*v1alpha1.TcxProgram, error)
+	Update(ctx context.Context, tcxProgram *v1alpha1.TcxProgram, opts v1.UpdateOptions) (*v1alpha1.TcxProgram, error)
+	UpdateStatus(ctx context.Context, tcxProgram *v1alpha1.TcxProgram, opts v1.UpdateOptions) (*v1alpha1.TcxProgram, error)
+	Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
+	DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
+	Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.TcxProgram, error)
+	List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.TcxProgramList, error)
+	Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
+	Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TcxProgram, err error)
+	TcxProgramExpansion
+}
+
+// tcxPrograms implements TcxProgramInterface
+type tcxPrograms struct {
+	client rest.Interface
+}
+
+// newTcxPrograms returns a TcxPrograms
+func newTcxPrograms(c *BpfmanV1alpha1Client) *tcxPrograms {
+	return &tcxPrograms{
+		client: c.RESTClient(),
+	}
+}
+
+// Get takes name of the tcxProgram, and returns the corresponding tcxProgram object, and an error if there is any.
+func (c *tcxPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.TcxProgram, err error) {
+	result = &v1alpha1.TcxProgram{}
+	err = c.client.Get().
+		Resource("tcxprograms").
+		Name(name).
+		VersionedParams(&options, scheme.ParameterCodec).
+		Do(ctx).
+		Into(result)
+	return
+}
+
+// List takes label and field selectors, and returns the list of TcxPrograms that match those selectors.
+func (c *tcxPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.TcxProgramList, err error) {
+	var timeout time.Duration
+	if opts.TimeoutSeconds != nil {
+		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+	}
+	result = &v1alpha1.TcxProgramList{}
+	err = c.client.Get().
+		Resource("tcxprograms").
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Timeout(timeout).
+		Do(ctx).
+		Into(result)
+	return
+}
+
+// Watch returns a watch.Interface that watches the requested tcxPrograms.
+func (c *tcxPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
+	var timeout time.Duration
+	if opts.TimeoutSeconds != nil {
+		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+	}
+	opts.Watch = true
+	return c.client.Get().
+		Resource("tcxprograms").
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Timeout(timeout).
+		Watch(ctx)
+}
+
+// Create takes the representation of a tcxProgram and creates it.  Returns the server's representation of the tcxProgram, and an error, if there is any.
+func (c *tcxPrograms) Create(ctx context.Context, tcxProgram *v1alpha1.TcxProgram, opts v1.CreateOptions) (result *v1alpha1.TcxProgram, err error) {
+	result = &v1alpha1.TcxProgram{}
+	err = c.client.Post().
+		Resource("tcxprograms").
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Body(tcxProgram).
+		Do(ctx).
+		Into(result)
+	return
+}
+
+// Update takes the representation of a tcxProgram and updates it. Returns the server's representation of the tcxProgram, and an error, if there is any.
+func (c *tcxPrograms) Update(ctx context.Context, tcxProgram *v1alpha1.TcxProgram, opts v1.UpdateOptions) (result *v1alpha1.TcxProgram, err error) {
+	result = &v1alpha1.TcxProgram{}
+	err = c.client.Put().
+		Resource("tcxprograms").
+		Name(tcxProgram.Name).
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Body(tcxProgram).
+		Do(ctx).
+		Into(result)
+	return
+}
+
+// UpdateStatus was generated because the type contains a Status member.
+// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+func (c *tcxPrograms) UpdateStatus(ctx context.Context, tcxProgram *v1alpha1.TcxProgram, opts v1.UpdateOptions) (result *v1alpha1.TcxProgram, err error) {
+	result = &v1alpha1.TcxProgram{}
+	err = c.client.Put().
+		Resource("tcxprograms").
+		Name(tcxProgram.Name).
+		SubResource("status").
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Body(tcxProgram).
+		Do(ctx).
+		Into(result)
+	return
+}
+
+// Delete takes name of the tcxProgram and deletes it. Returns an error if one occurs.
+func (c *tcxPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
+	return c.client.Delete().
+		Resource("tcxprograms").
+		Name(name).
+		Body(&opts).
+		Do(ctx).
+		Error()
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *tcxPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
+	var timeout time.Duration
+	if listOpts.TimeoutSeconds != nil {
+		timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
+	}
+	return c.client.Delete().
+		Resource("tcxprograms").
+		VersionedParams(&listOpts, scheme.ParameterCodec).
+		Timeout(timeout).
+		Body(&opts).
+		Do(ctx).
+		Error()
+}
+
+// Patch applies the patch and returns the patched tcxProgram.
+func (c *tcxPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TcxProgram, err error) {
+	result = &v1alpha1.TcxProgram{}
+	err = c.client.Patch(pt).
+		Resource("tcxprograms").
+		Name(name).
+		SubResource(subresources...).
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Body(data).
+		Do(ctx).
+		Into(result)
+	return
+}
diff --git a/pkg/client/externalversions/apis/v1alpha1/interface.go b/pkg/client/externalversions/apis/v1alpha1/interface.go
index 0b792e2fe..432597f9d 100644
--- a/pkg/client/externalversions/apis/v1alpha1/interface.go
+++ b/pkg/client/externalversions/apis/v1alpha1/interface.go
@@ -36,6 +36,8 @@ type Interface interface {
 	KprobePrograms() KprobeProgramInformer
 	// TcPrograms returns a TcProgramInformer.
 	TcPrograms() TcProgramInformer
+	// TcxPrograms returns a TcxProgramInformer.
+	TcxPrograms() TcxProgramInformer
 	// TracepointPrograms returns a TracepointProgramInformer.
 	TracepointPrograms() TracepointProgramInformer
 	// UprobePrograms returns a UprobeProgramInformer.
@@ -85,6 +87,11 @@ func (v *version) TcPrograms() TcProgramInformer {
 	return &tcProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
 }
 
+// TcxPrograms returns a TcxProgramInformer.
+func (v *version) TcxPrograms() TcxProgramInformer {
+	return &tcxProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
+}
+
 // TracepointPrograms returns a TracepointProgramInformer.
 func (v *version) TracepointPrograms() TracepointProgramInformer {
 	return &tracepointProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
diff --git a/pkg/client/externalversions/apis/v1alpha1/tcxprogram.go b/pkg/client/externalversions/apis/v1alpha1/tcxprogram.go
new file mode 100644
index 000000000..ca8fdaadc
--- /dev/null
+++ b/pkg/client/externalversions/apis/v1alpha1/tcxprogram.go
@@ -0,0 +1,89 @@
+/*
+Copyright 2023 The bpfman 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.
+*/
+
+// Code generated by informer-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+	"context"
+	time "time"
+
+	apisv1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1"
+	v1alpha1 "github.com/bpfman/bpfman-operator/pkg/client/apis/v1alpha1"
+	clientset "github.com/bpfman/bpfman-operator/pkg/client/clientset"
+	internalinterfaces "github.com/bpfman/bpfman-operator/pkg/client/externalversions/internalinterfaces"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+	watch "k8s.io/apimachinery/pkg/watch"
+	cache "k8s.io/client-go/tools/cache"
+)
+
+// TcxProgramInformer provides access to a shared informer and lister for
+// TcxPrograms.
+type TcxProgramInformer interface {
+	Informer() cache.SharedIndexInformer
+	Lister() v1alpha1.TcxProgramLister
+}
+
+type tcxProgramInformer struct {
+	factory          internalinterfaces.SharedInformerFactory
+	tweakListOptions internalinterfaces.TweakListOptionsFunc
+}
+
+// NewTcxProgramInformer constructs a new informer for TcxProgram type.
+// Always prefer using an informer factory to get a shared informer instead of getting an independent
+// one. This reduces memory footprint and number of connections to the server.
+func NewTcxProgramInformer(client clientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
+	return NewFilteredTcxProgramInformer(client, resyncPeriod, indexers, nil)
+}
+
+// NewFilteredTcxProgramInformer constructs a new informer for TcxProgram type.
+// Always prefer using an informer factory to get a shared informer instead of getting an independent
+// one. This reduces memory footprint and number of connections to the server.
+func NewFilteredTcxProgramInformer(client clientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
+	return cache.NewSharedIndexInformer(
+		&cache.ListWatch{
+			ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
+				if tweakListOptions != nil {
+					tweakListOptions(&options)
+				}
+				return client.BpfmanV1alpha1().TcxPrograms().List(context.TODO(), options)
+			},
+			WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
+				if tweakListOptions != nil {
+					tweakListOptions(&options)
+				}
+				return client.BpfmanV1alpha1().TcxPrograms().Watch(context.TODO(), options)
+			},
+		},
+		&apisv1alpha1.TcxProgram{},
+		resyncPeriod,
+		indexers,
+	)
+}
+
+func (f *tcxProgramInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
+	return NewFilteredTcxProgramInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
+}
+
+func (f *tcxProgramInformer) Informer() cache.SharedIndexInformer {
+	return f.factory.InformerFor(&apisv1alpha1.TcxProgram{}, f.defaultInformer)
+}
+
+func (f *tcxProgramInformer) Lister() v1alpha1.TcxProgramLister {
+	return v1alpha1.NewTcxProgramLister(f.Informer().GetIndexer())
+}
diff --git a/pkg/client/externalversions/generic.go b/pkg/client/externalversions/generic.go
index e045549cf..4db1e4a1e 100644
--- a/pkg/client/externalversions/generic.go
+++ b/pkg/client/externalversions/generic.go
@@ -65,6 +65,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
 		return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().KprobePrograms().Informer()}, nil
 	case v1alpha1.SchemeGroupVersion.WithResource("tcprograms"):
 		return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().TcPrograms().Informer()}, nil
+	case v1alpha1.SchemeGroupVersion.WithResource("tcxprograms"):
+		return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().TcxPrograms().Informer()}, nil
 	case v1alpha1.SchemeGroupVersion.WithResource("tracepointprograms"):
 		return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().TracepointPrograms().Informer()}, nil
 	case v1alpha1.SchemeGroupVersion.WithResource("uprobeprograms"):