diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml
index da07b7eee..43e864a0a 100644
--- a/.github/workflows/e2e.yaml
+++ b/.github/workflows/e2e.yaml
@@ -36,7 +36,20 @@ jobs:
with:
enable_workaround_docker_io: 'false'
branch: ${{ matrix.openstack_version }}
- enabled_services: "openstack-cli-server"
+ enabled_services: "openstack-cli-server,octavia,o-api,o-cw,o-hm,o-hk,o-da"
+ conf_overrides: |
+ enable_plugin octavia https://github.com/openstack/octavia ${{ matrix.openstack_version }}
+ enable_plugin neutron https://github.com/openstack/neutron ${{ matrix.openstack_version }}
+
+ ${{ matrix.devstack_conf_overrides }}
+ - name: Calculate go version
+ id: vars
+ run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT
+
+ - name: Set up Go
+ uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # tag=v6.1.0
+ with:
+ go-version: ${{ steps.vars.outputs.go_version }}
- name: Deploy a Kind Cluster
uses: helm/kind-action@92086f6be054225fa813e0a4b13787fc9088faab
diff --git a/PROJECT b/PROJECT
index 8d6e2c12d..b56b59130 100644
--- a/PROJECT
+++ b/PROJECT
@@ -56,6 +56,22 @@ resources:
kind: KeyPair
path: github.com/k-orc/openstack-resource-controller/api/v1alpha1
version: v1alpha1
+- api:
+ crdVersion: v1
+ namespaced: true
+ domain: k-orc.cloud
+ group: openstack
+ kind: Listener
+ path: github.com/k-orc/openstack-resource-controller/api/v1alpha1
+ version: v1alpha1
+- api:
+ crdVersion: v1
+ namespaced: true
+ domain: k-orc.cloud
+ group: openstack
+ kind: LoadBalancer
+ path: github.com/k-orc/openstack-resource-controller/api/v1alpha1
+ version: v1alpha1
- api:
crdVersion: v1
namespaced: true
diff --git a/api/v1alpha1/listener_types.go b/api/v1alpha1/listener_types.go
new file mode 100644
index 000000000..6cf3ca034
--- /dev/null
+++ b/api/v1alpha1/listener_types.go
@@ -0,0 +1,346 @@
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package v1alpha1
+
+// ListenerProtocol represents the protocol used by a listener.
+// +kubebuilder:validation:Enum=HTTP;HTTPS;SCTP;PROMETHEUS;TCP;TERMINATED_HTTPS;UDP
+type ListenerProtocol string
+
+const (
+ ListenerProtocolHTTP ListenerProtocol = "HTTP"
+ ListenerProtocolHTTPS ListenerProtocol = "HTTPS"
+ ListenerProtocolSCTP ListenerProtocol = "SCTP"
+ ListenerProtocolPROMETHEUS ListenerProtocol = "PROMETHEUS"
+ ListenerProtocolTCP ListenerProtocol = "TCP"
+ ListenerProtocolTerminatedHTTPS ListenerProtocol = "TERMINATED_HTTPS"
+ ListenerProtocolUDP ListenerProtocol = "UDP"
+)
+
+// ListenerClientAuthentication represents TLS client authentication mode.
+// +kubebuilder:validation:Enum=NONE;OPTIONAL;MANDATORY
+type ListenerClientAuthentication string
+
+const (
+ ListenerClientAuthNone ListenerClientAuthentication = "NONE"
+ ListenerClientAuthOptional ListenerClientAuthentication = "OPTIONAL"
+ ListenerClientAuthMandatory ListenerClientAuthentication = "MANDATORY"
+)
+
+// +kubebuilder:validation:MinLength:=1
+// +kubebuilder:validation:MaxLength:=255
+type ListenerTag string
+
+// ListenerHSTS represents HTTP Strict Transport Security configuration.
+type ListenerHSTS struct {
+ // maxAge is the maximum time in seconds that the browser should remember
+ // that this site is only to be accessed using HTTPS.
+ // +kubebuilder:validation:Minimum=0
+ // +optional
+ MaxAge *int32 `json:"maxAge,omitempty"`
+
+ // includeSubDomains specifies whether this rule applies to all subdomains.
+ // +optional
+ IncludeSubDomains *bool `json:"includeSubDomains,omitempty"`
+
+ // preload specifies whether the domain should be included in browsers' preload list.
+ // +optional
+ Preload *bool `json:"preload,omitempty"`
+}
+
+// ListenerResourceSpec contains the desired state of the resource.
+type ListenerResourceSpec struct {
+ // name will be the name of the created resource. If not specified, the
+ // name of the ORC object will be used.
+ // +optional
+ Name *OpenStackName `json:"name,omitempty"`
+
+ // description is a human-readable description for the resource.
+ // +kubebuilder:validation:MinLength:=1
+ // +kubebuilder:validation:MaxLength:=255
+ // +optional
+ Description *string `json:"description,omitempty"`
+
+ // loadBalancerRef is a reference to the LoadBalancer this listener belongs to.
+ // +required
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="loadBalancerRef is immutable"
+ LoadBalancerRef KubernetesNameRef `json:"loadBalancerRef,omitempty"`
+
+ // protocol is the protocol the listener will use.
+ // +required
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="protocol is immutable"
+ Protocol ListenerProtocol `json:"protocol,omitempty"`
+
+ // protocolPort is the port on which the listener will accept connections.
+ // +required
+ // +kubebuilder:validation:Minimum=1
+ // +kubebuilder:validation:Maximum=65535
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="protocolPort is immutable"
+ ProtocolPort int32 `json:"protocolPort,omitempty"`
+
+ // adminStateUp is the administrative state of the listener, which is up (true) or down (false).
+ // +optional
+ AdminStateUp *bool `json:"adminStateUp,omitempty"`
+
+ // connectionLimit is the maximum number of connections permitted for this listener.
+ // Default value is -1 which represents infinite connections.
+ // +kubebuilder:validation:Minimum=-1
+ // +optional
+ ConnectionLimit *int32 `json:"connectionLimit,omitempty"`
+
+ // defaultTLSContainerRef is a reference to a secret containing a PKCS12 format
+ // certificate/key bundle for TERMINATED_HTTPS listeners.
+ // +kubebuilder:validation:MaxLength=255
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="defaultTLSContainerRef is immutable"
+ DefaultTLSContainerRef *string `json:"defaultTLSContainerRef,omitempty"`
+
+ // sniContainerRefs is a list of references to secrets containing PKCS12 format
+ // certificate/key bundles for TERMINATED_HTTPS listeners using SNI.
+ // +listType=set
+ // +optional
+ // +kubebuilder:validation:MaxItems:=25
+ // +kubebuilder:validation:items:MaxLength=255
+ SNIContainerRefs []string `json:"sniContainerRefs,omitempty"`
+
+ // defaultPoolRef is a reference to the default Pool for this listener.
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="defaultPoolRef is immutable"
+ DefaultPoolRef *KubernetesNameRef `json:"defaultPoolRef,omitempty"`
+
+ // insertHeaders is a dictionary of optional headers to insert into the request
+ // before it is sent to the backend member.
+ // +optional
+ InsertHeaders map[string]string `json:"insertHeaders,omitempty"`
+
+ // timeoutClientData is the frontend client inactivity timeout in milliseconds.
+ // +kubebuilder:validation:Minimum=0
+ // +optional
+ TimeoutClientData *int32 `json:"timeoutClientData,omitempty"`
+
+ // timeoutMemberConnect is the backend member connection timeout in milliseconds.
+ // +kubebuilder:validation:Minimum=0
+ // +optional
+ TimeoutMemberConnect *int32 `json:"timeoutMemberConnect,omitempty"`
+
+ // timeoutMemberData is the backend member inactivity timeout in milliseconds.
+ // +kubebuilder:validation:Minimum=0
+ // +optional
+ TimeoutMemberData *int32 `json:"timeoutMemberData,omitempty"`
+
+ // timeoutTCPInspect is the time in milliseconds to wait for additional TCP packets
+ // for content inspection.
+ // +kubebuilder:validation:Minimum=0
+ // +optional
+ TimeoutTCPInspect *int32 `json:"timeoutTCPInspect,omitempty"`
+
+ // allowedCIDRs is a list of IPv4/IPv6 CIDRs that are permitted to connect to this listener.
+ // +listType=set
+ // +optional
+ // +kubebuilder:validation:MaxItems:=256
+ // +kubebuilder:validation:items:MaxLength=64
+ AllowedCIDRs []string `json:"allowedCIDRs,omitempty"`
+
+ // tlsCiphers is a colon-separated list of ciphers for TLS-terminated listeners.
+ // +kubebuilder:validation:MaxLength=2048
+ // +optional
+ TLSCiphers *string `json:"tlsCiphers,omitempty"`
+
+ // tlsVersions is a list of TLS protocol versions to be used by the listener.
+ // +listType=set
+ // +optional
+ // +kubebuilder:validation:MaxItems:=10
+ // +kubebuilder:validation:items:MaxLength=32
+ TLSVersions []string `json:"tlsVersions,omitempty"`
+
+ // alpnProtocols is a list of ALPN protocols for TLS-enabled listeners.
+ // +listType=set
+ // +optional
+ // +kubebuilder:validation:MaxItems:=10
+ // +kubebuilder:validation:items:MaxLength=32
+ ALPNProtocols []string `json:"alpnProtocols,omitempty"`
+
+ // clientAuthentication is the TLS client authentication mode.
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="clientAuthentication is immutable"
+ ClientAuthentication *ListenerClientAuthentication `json:"clientAuthentication,omitempty"`
+
+ // clientCATLSContainerRef is a reference to a secret containing the CA certificate
+ // for client authentication.
+ // +kubebuilder:validation:MaxLength=255
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="clientCATLSContainerRef is immutable"
+ ClientCATLSContainerRef *string `json:"clientCATLSContainerRef,omitempty"`
+
+ // clientCRLContainerRef is a reference to a secret containing the CA revocation list
+ // for client authentication.
+ // +kubebuilder:validation:MaxLength=255
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="clientCRLContainerRef is immutable"
+ ClientCRLContainerRef *string `json:"clientCRLContainerRef,omitempty"`
+
+ // hsts is the HTTP Strict Transport Security configuration.
+ // +optional
+ HSTS *ListenerHSTS `json:"hsts,omitempty"`
+
+ // tags is a list of tags which will be applied to the listener.
+ // +kubebuilder:validation:MaxItems:=64
+ // +listType=set
+ // +optional
+ Tags []ListenerTag `json:"tags,omitempty"`
+}
+
+// ListenerFilter defines an existing resource by its properties.
+// +kubebuilder:validation:MinProperties:=1
+type ListenerFilter struct {
+ // name of the existing resource.
+ // +optional
+ Name *OpenStackName `json:"name,omitempty"`
+
+ // description of the existing resource.
+ // +kubebuilder:validation:MinLength:=1
+ // +kubebuilder:validation:MaxLength:=255
+ // +optional
+ Description *string `json:"description,omitempty"`
+
+ // loadBalancerRef filters by the LoadBalancer this listener belongs to.
+ // +optional
+ LoadBalancerRef *KubernetesNameRef `json:"loadBalancerRef,omitempty"`
+
+ // protocol filters by the protocol used by the listener.
+ // +optional
+ Protocol *ListenerProtocol `json:"protocol,omitempty"`
+
+ // protocolPort filters by the port used by the listener.
+ // +kubebuilder:validation:Minimum=1
+ // +kubebuilder:validation:Maximum=65535
+ // +optional
+ ProtocolPort *int32 `json:"protocolPort,omitempty"`
+
+ // tags is a list of tags to filter by. If specified, the resource must
+ // have all of the tags specified to be included in the result.
+ // +listType=set
+ // +optional
+ // +kubebuilder:validation:MaxItems:=64
+ Tags []ListenerTag `json:"tags,omitempty"`
+
+ // tagsAny is a list of tags to filter by. If specified, the resource
+ // must have at least one of the tags specified to be included in the
+ // result.
+ // +listType=set
+ // +optional
+ // +kubebuilder:validation:MaxItems:=64
+ TagsAny []ListenerTag `json:"tagsAny,omitempty"`
+
+ // notTags is a list of tags to filter by. If specified, resources which
+ // contain all of the given tags will be excluded from the result.
+ // +listType=set
+ // +optional
+ // +kubebuilder:validation:MaxItems:=64
+ NotTags []ListenerTag `json:"notTags,omitempty"`
+
+ // notTagsAny is a list of tags to filter by. If specified, resources
+ // which contain any of the given tags will be excluded from the result.
+ // +listType=set
+ // +optional
+ // +kubebuilder:validation:MaxItems:=64
+ NotTagsAny []ListenerTag `json:"notTagsAny,omitempty"`
+}
+
+// ListenerResourceStatus represents the observed state of the resource.
+type ListenerResourceStatus struct {
+ // name is a human-readable name for the resource.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ Name string `json:"name,omitempty"`
+
+ // description is a human-readable description for the resource.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ Description string `json:"description,omitempty"`
+
+ // loadBalancerID is the ID of the LoadBalancer this listener belongs to.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ LoadBalancerID string `json:"loadBalancerID,omitempty"`
+
+ // protocol is the protocol used by the listener.
+ // +kubebuilder:validation:MaxLength=64
+ // +optional
+ Protocol string `json:"protocol,omitempty"`
+
+ // protocolPort is the port used by the listener.
+ // +optional
+ ProtocolPort int32 `json:"protocolPort,omitempty"`
+
+ // adminStateUp is the administrative state of the listener,
+ // which is up (true) or down (false).
+ // +optional
+ AdminStateUp *bool `json:"adminStateUp,omitempty"`
+
+ // connectionLimit is the maximum number of connections permitted for this listener.
+ // +optional
+ ConnectionLimit int32 `json:"connectionLimit,omitempty"`
+
+ // defaultPoolID is the ID of the default pool for this listener.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ DefaultPoolID string `json:"defaultPoolID,omitempty"`
+
+ // provisioningStatus is the provisioning status of the listener.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ ProvisioningStatus string `json:"provisioningStatus,omitempty"`
+
+ // operatingStatus is the operating status of the listener.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ OperatingStatus string `json:"operatingStatus,omitempty"`
+
+ // allowedCIDRs is the list of CIDRs permitted to connect to this listener.
+ // +listType=atomic
+ // +optional
+ // +kubebuilder:validation:MaxItems:=256
+ // +kubebuilder:validation:items:MaxLength=64
+ AllowedCIDRs []string `json:"allowedCIDRs,omitempty"`
+
+ // timeoutClientData is the frontend client inactivity timeout in milliseconds.
+ // +optional
+ TimeoutClientData int32 `json:"timeoutClientData,omitempty"`
+
+ // timeoutMemberConnect is the backend member connection timeout in milliseconds.
+ // +optional
+ TimeoutMemberConnect int32 `json:"timeoutMemberConnect,omitempty"`
+
+ // timeoutMemberData is the backend member inactivity timeout in milliseconds.
+ // +optional
+ TimeoutMemberData int32 `json:"timeoutMemberData,omitempty"`
+
+ // timeoutTCPInspect is the time to wait for additional TCP packets in milliseconds.
+ // +optional
+ TimeoutTCPInspect int32 `json:"timeoutTCPInspect,omitempty"`
+
+ // insertHeaders is a dictionary of headers inserted into the request.
+ // +optional
+ InsertHeaders map[string]string `json:"insertHeaders,omitempty"`
+
+ // tags is the list of tags on the resource.
+ // +listType=atomic
+ // +optional
+ // +kubebuilder:validation:MaxItems:=64
+ // +kubebuilder:validation:items:MaxLength=255
+ Tags []string `json:"tags,omitempty"`
+}
diff --git a/api/v1alpha1/loadbalancer_types.go b/api/v1alpha1/loadbalancer_types.go
new file mode 100644
index 000000000..761435115
--- /dev/null
+++ b/api/v1alpha1/loadbalancer_types.go
@@ -0,0 +1,243 @@
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package v1alpha1
+
+// +kubebuilder:validation:MinLength:=1
+// +kubebuilder:validation:MaxLength:=255
+type LoadBalancerTag string
+
+// LoadBalancerResourceSpec contains the desired state of the resource.
+// +kubebuilder:validation:XValidation:rule="has(self.vipSubnetRef) || has(self.vipNetworkRef) || has(self.vipPortRef)",message="at least one of vipSubnetRef, vipNetworkRef, or vipPortRef must be specified"
+type LoadBalancerResourceSpec struct {
+ // name will be the name of the created resource. If not specified, the
+ // name of the ORC object will be used.
+ // +optional
+ Name *OpenStackName `json:"name,omitempty"`
+
+ // description is a human-readable description for the resource.
+ // +kubebuilder:validation:MinLength:=1
+ // +kubebuilder:validation:MaxLength:=255
+ // +optional
+ Description *string `json:"description,omitempty"`
+
+ // vipSubnetRef is the subnet on which to allocate the load balancer's address.
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="vipSubnetRef is immutable"
+ VipSubnetRef *KubernetesNameRef `json:"vipSubnetRef,omitempty"`
+
+ // vipNetworkRef is the network on which to allocate the load balancer's address.
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="vipNetworkRef is immutable"
+ VipNetworkRef *KubernetesNameRef `json:"vipNetworkRef,omitempty"`
+
+ // vipPortRef is a reference to a neutron port to use for the VIP. If the port
+ // has more than one subnet you must specify either vipSubnetRef or vipAddress
+ // to clarify which address should be used for the VIP.
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="vipPortRef is immutable"
+ VipPortRef *KubernetesNameRef `json:"vipPortRef,omitempty"`
+
+ // flavorRef is a reference to the ORC Flavor which this resource is associated with.
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="flavorRef is immutable"
+ FlavorRef *KubernetesNameRef `json:"flavorRef,omitempty"`
+
+ // projectRef is a reference to the ORC Project which this resource is associated with.
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="projectRef is immutable"
+ ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"`
+
+ // adminStateUp is the administrative state of the load balancer, which is up (true) or down (false)
+ // +optional
+ AdminStateUp *bool `json:"adminStateUp,omitempty"`
+
+ // availabilityZone is the availability zone in which to create the load balancer.
+ // +kubebuilder:validation:MaxLength=255
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="availabilityZone is immutable"
+ AvailabilityZone string `json:"availabilityZone,omitempty"`
+
+ // provider is the name of the load balancer provider.
+ // +kubebuilder:validation:MaxLength=255
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="provider is immutable"
+ Provider string `json:"provider,omitempty"`
+
+ // vipAddress is the specific IP address to use for the VIP (optional).
+ // If not specified, one is allocated automatically from the subnet.
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="vipAddress is immutable"
+ VipAddress *IPvAny `json:"vipAddress,omitempty"`
+
+ // tags is a list of tags which will be applied to the load balancer.
+ // +kubebuilder:validation:MaxItems:=64
+ // +listType=set
+ // +optional
+ Tags []LoadBalancerTag `json:"tags,omitempty"`
+}
+
+// LoadBalancerFilter defines an existing resource by its properties
+// +kubebuilder:validation:MinProperties:=1
+type LoadBalancerFilter struct {
+ // name of the existing resource
+ // +optional
+ Name *OpenStackName `json:"name,omitempty"`
+
+ // description of the existing resource
+ // +kubebuilder:validation:MinLength:=1
+ // +kubebuilder:validation:MaxLength:=255
+ // +optional
+ Description *string `json:"description,omitempty"`
+
+ // projectRef is a reference to the ORC Project this resource is associated with.
+ // Typically, only used by admin.
+ // +optional
+ ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"`
+
+ // vipSubnetRef filters by the subnet on which the load balancer's address is allocated.
+ // +optional
+ VipSubnetRef *KubernetesNameRef `json:"vipSubnetRef,omitempty"`
+
+ // vipNetworkRef filters by the network on which the load balancer's address is allocated.
+ // +optional
+ VipNetworkRef *KubernetesNameRef `json:"vipNetworkRef,omitempty"`
+
+ // vipPortRef filters by the neutron port used for the VIP.
+ // +optional
+ VipPortRef *KubernetesNameRef `json:"vipPortRef,omitempty"`
+
+ // availabilityZone is the availability zone in which to create the load balancer.
+ // +kubebuilder:validation:MaxLength=255
+ // +optional
+ AvailabilityZone string `json:"availabilityZone,omitempty"`
+
+ // provider filters by the name of the load balancer provider.
+ // +kubebuilder:validation:MaxLength=255
+ // +optional
+ Provider string `json:"provider,omitempty"`
+
+ // vipAddress filters by the IP address of the load balancer's VIP.
+ // +kubebuilder:validation:MaxLength=64
+ // +optional
+ VipAddress string `json:"vipAddress,omitempty"`
+
+ // tags is a list of tags to filter by. If specified, the resource must
+ // have all of the tags specified to be included in the result.
+ // +listType=set
+ // +optional
+ // +kubebuilder:validation:MaxItems:=64
+ Tags []LoadBalancerTag `json:"tags,omitempty"`
+
+ // tagsAny is a list of tags to filter by. If specified, the resource
+ // must have at least one of the tags specified to be included in the
+ // result.
+ // +listType=set
+ // +optional
+ // +kubebuilder:validation:MaxItems:=64
+ TagsAny []LoadBalancerTag `json:"tagsAny,omitempty"`
+
+ // notTags is a list of tags to filter by. If specified, resources which
+ // contain all of the given tags will be excluded from the result.
+ // +listType=set
+ // +optional
+ // +kubebuilder:validation:MaxItems:=64
+ NotTags []LoadBalancerTag `json:"notTags,omitempty"`
+
+ // notTagsAny is a list of tags to filter by. If specified, resources
+ // which contain any of the given tags will be excluded from the result.
+ // +listType=set
+ // +optional
+ // +kubebuilder:validation:MaxItems:=64
+ NotTagsAny []LoadBalancerTag `json:"notTagsAny,omitempty"`
+}
+
+// LoadBalancerResourceStatus represents the observed state of the resource.
+type LoadBalancerResourceStatus struct {
+ // name is a Human-readable name for the resource. Might not be unique.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ Name string `json:"name,omitempty"`
+
+ // description is a human-readable description for the resource.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ Description string `json:"description,omitempty"`
+
+ // vipSubnetID is the ID of the Subnet to which the resource is associated.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ VipSubnetID string `json:"vipSubnetID,omitempty"`
+
+ // vipNetworkID is the ID of the Network to which the resource is associated.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ VipNetworkID string `json:"vipNetworkID,omitempty"`
+
+ // vipPortID is the ID of the Port to which the resource is associated.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ VipPortID string `json:"vipPortID,omitempty"`
+
+ // flavorID is the ID of the Flavor to which the resource is associated.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ FlavorID string `json:"flavorID,omitempty"`
+
+ // projectID is the ID of the Project to which the resource is associated.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ ProjectID string `json:"projectID,omitempty"`
+
+ // adminStateUp is the administrative state of the load balancer,
+ // which is up (true) or down (false).
+ // +optional
+ AdminStateUp *bool `json:"adminStateUp,omitempty"`
+
+ // tags is the list of tags on the resource.
+ // +listType=atomic
+ // +optional
+ // +kubebuilder:validation:MaxItems:=64
+ // +kubebuilder:validation:items:MaxLength=255
+ Tags []string `json:"tags,omitempty"`
+
+ // availabilityZone is the availability zone where the load balancer is located.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ AvailabilityZone string `json:"availabilityZone,omitempty"`
+
+ // provisioningStatus is the provisioning status of the load balancer.
+ // This value is ACTIVE, PENDING_CREATE or ERROR.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ ProvisioningStatus string `json:"provisioningStatus,omitempty"`
+
+ // operatingStatus is the operating status of the load balancer,
+ // such as ONLINE or OFFLINE.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ OperatingStatus string `json:"operatingStatus,omitempty"`
+
+ // provider is the name of the load balancer provider.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ Provider string `json:"provider,omitempty"`
+
+ // vipAddress is the IP address of the load balancer's VIP.
+ // +optional
+ // +kubebuilder:validation:MaxLength=64
+ VipAddress string `json:"vipAddress,omitempty"`
+}
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 093e63451..abecc4099 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -1975,6 +1975,704 @@ func (in *KeyPairStatus) DeepCopy() *KeyPairStatus {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Listener) DeepCopyInto(out *Listener) {
+ *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 Listener.
+func (in *Listener) DeepCopy() *Listener {
+ if in == nil {
+ return nil
+ }
+ out := new(Listener)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Listener) 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 *ListenerFilter) DeepCopyInto(out *ListenerFilter) {
+ *out = *in
+ if in.Name != nil {
+ in, out := &in.Name, &out.Name
+ *out = new(OpenStackName)
+ **out = **in
+ }
+ if in.Description != nil {
+ in, out := &in.Description, &out.Description
+ *out = new(string)
+ **out = **in
+ }
+ if in.LoadBalancerRef != nil {
+ in, out := &in.LoadBalancerRef, &out.LoadBalancerRef
+ *out = new(KubernetesNameRef)
+ **out = **in
+ }
+ if in.Protocol != nil {
+ in, out := &in.Protocol, &out.Protocol
+ *out = new(ListenerProtocol)
+ **out = **in
+ }
+ if in.ProtocolPort != nil {
+ in, out := &in.ProtocolPort, &out.ProtocolPort
+ *out = new(int32)
+ **out = **in
+ }
+ if in.Tags != nil {
+ in, out := &in.Tags, &out.Tags
+ *out = make([]ListenerTag, len(*in))
+ copy(*out, *in)
+ }
+ if in.TagsAny != nil {
+ in, out := &in.TagsAny, &out.TagsAny
+ *out = make([]ListenerTag, len(*in))
+ copy(*out, *in)
+ }
+ if in.NotTags != nil {
+ in, out := &in.NotTags, &out.NotTags
+ *out = make([]ListenerTag, len(*in))
+ copy(*out, *in)
+ }
+ if in.NotTagsAny != nil {
+ in, out := &in.NotTagsAny, &out.NotTagsAny
+ *out = make([]ListenerTag, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListenerFilter.
+func (in *ListenerFilter) DeepCopy() *ListenerFilter {
+ if in == nil {
+ return nil
+ }
+ out := new(ListenerFilter)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ListenerHSTS) DeepCopyInto(out *ListenerHSTS) {
+ *out = *in
+ if in.MaxAge != nil {
+ in, out := &in.MaxAge, &out.MaxAge
+ *out = new(int32)
+ **out = **in
+ }
+ if in.IncludeSubDomains != nil {
+ in, out := &in.IncludeSubDomains, &out.IncludeSubDomains
+ *out = new(bool)
+ **out = **in
+ }
+ if in.Preload != nil {
+ in, out := &in.Preload, &out.Preload
+ *out = new(bool)
+ **out = **in
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListenerHSTS.
+func (in *ListenerHSTS) DeepCopy() *ListenerHSTS {
+ if in == nil {
+ return nil
+ }
+ out := new(ListenerHSTS)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ListenerImport) DeepCopyInto(out *ListenerImport) {
+ *out = *in
+ if in.ID != nil {
+ in, out := &in.ID, &out.ID
+ *out = new(string)
+ **out = **in
+ }
+ if in.Filter != nil {
+ in, out := &in.Filter, &out.Filter
+ *out = new(ListenerFilter)
+ (*in).DeepCopyInto(*out)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListenerImport.
+func (in *ListenerImport) DeepCopy() *ListenerImport {
+ if in == nil {
+ return nil
+ }
+ out := new(ListenerImport)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ListenerList) DeepCopyInto(out *ListenerList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]Listener, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListenerList.
+func (in *ListenerList) DeepCopy() *ListenerList {
+ if in == nil {
+ return nil
+ }
+ out := new(ListenerList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ListenerList) 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 *ListenerResourceSpec) DeepCopyInto(out *ListenerResourceSpec) {
+ *out = *in
+ if in.Name != nil {
+ in, out := &in.Name, &out.Name
+ *out = new(OpenStackName)
+ **out = **in
+ }
+ if in.Description != nil {
+ in, out := &in.Description, &out.Description
+ *out = new(string)
+ **out = **in
+ }
+ if in.AdminStateUp != nil {
+ in, out := &in.AdminStateUp, &out.AdminStateUp
+ *out = new(bool)
+ **out = **in
+ }
+ if in.ConnectionLimit != nil {
+ in, out := &in.ConnectionLimit, &out.ConnectionLimit
+ *out = new(int32)
+ **out = **in
+ }
+ if in.DefaultTLSContainerRef != nil {
+ in, out := &in.DefaultTLSContainerRef, &out.DefaultTLSContainerRef
+ *out = new(string)
+ **out = **in
+ }
+ if in.SNIContainerRefs != nil {
+ in, out := &in.SNIContainerRefs, &out.SNIContainerRefs
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+ if in.DefaultPoolRef != nil {
+ in, out := &in.DefaultPoolRef, &out.DefaultPoolRef
+ *out = new(KubernetesNameRef)
+ **out = **in
+ }
+ if in.InsertHeaders != nil {
+ in, out := &in.InsertHeaders, &out.InsertHeaders
+ *out = make(map[string]string, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
+ if in.TimeoutClientData != nil {
+ in, out := &in.TimeoutClientData, &out.TimeoutClientData
+ *out = new(int32)
+ **out = **in
+ }
+ if in.TimeoutMemberConnect != nil {
+ in, out := &in.TimeoutMemberConnect, &out.TimeoutMemberConnect
+ *out = new(int32)
+ **out = **in
+ }
+ if in.TimeoutMemberData != nil {
+ in, out := &in.TimeoutMemberData, &out.TimeoutMemberData
+ *out = new(int32)
+ **out = **in
+ }
+ if in.TimeoutTCPInspect != nil {
+ in, out := &in.TimeoutTCPInspect, &out.TimeoutTCPInspect
+ *out = new(int32)
+ **out = **in
+ }
+ if in.AllowedCIDRs != nil {
+ in, out := &in.AllowedCIDRs, &out.AllowedCIDRs
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+ if in.TLSCiphers != nil {
+ in, out := &in.TLSCiphers, &out.TLSCiphers
+ *out = new(string)
+ **out = **in
+ }
+ if in.TLSVersions != nil {
+ in, out := &in.TLSVersions, &out.TLSVersions
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+ if in.ALPNProtocols != nil {
+ in, out := &in.ALPNProtocols, &out.ALPNProtocols
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+ if in.ClientAuthentication != nil {
+ in, out := &in.ClientAuthentication, &out.ClientAuthentication
+ *out = new(ListenerClientAuthentication)
+ **out = **in
+ }
+ if in.ClientCATLSContainerRef != nil {
+ in, out := &in.ClientCATLSContainerRef, &out.ClientCATLSContainerRef
+ *out = new(string)
+ **out = **in
+ }
+ if in.ClientCRLContainerRef != nil {
+ in, out := &in.ClientCRLContainerRef, &out.ClientCRLContainerRef
+ *out = new(string)
+ **out = **in
+ }
+ if in.HSTS != nil {
+ in, out := &in.HSTS, &out.HSTS
+ *out = new(ListenerHSTS)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.Tags != nil {
+ in, out := &in.Tags, &out.Tags
+ *out = make([]ListenerTag, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListenerResourceSpec.
+func (in *ListenerResourceSpec) DeepCopy() *ListenerResourceSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(ListenerResourceSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ListenerResourceStatus) DeepCopyInto(out *ListenerResourceStatus) {
+ *out = *in
+ if in.AdminStateUp != nil {
+ in, out := &in.AdminStateUp, &out.AdminStateUp
+ *out = new(bool)
+ **out = **in
+ }
+ if in.AllowedCIDRs != nil {
+ in, out := &in.AllowedCIDRs, &out.AllowedCIDRs
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+ if in.InsertHeaders != nil {
+ in, out := &in.InsertHeaders, &out.InsertHeaders
+ *out = make(map[string]string, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
+ if in.Tags != nil {
+ in, out := &in.Tags, &out.Tags
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListenerResourceStatus.
+func (in *ListenerResourceStatus) DeepCopy() *ListenerResourceStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(ListenerResourceStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ListenerSpec) DeepCopyInto(out *ListenerSpec) {
+ *out = *in
+ if in.Import != nil {
+ in, out := &in.Import, &out.Import
+ *out = new(ListenerImport)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.Resource != nil {
+ in, out := &in.Resource, &out.Resource
+ *out = new(ListenerResourceSpec)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.ManagedOptions != nil {
+ in, out := &in.ManagedOptions, &out.ManagedOptions
+ *out = new(ManagedOptions)
+ **out = **in
+ }
+ out.CloudCredentialsRef = in.CloudCredentialsRef
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListenerSpec.
+func (in *ListenerSpec) DeepCopy() *ListenerSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(ListenerSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ListenerStatus) DeepCopyInto(out *ListenerStatus) {
+ *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])
+ }
+ }
+ if in.ID != nil {
+ in, out := &in.ID, &out.ID
+ *out = new(string)
+ **out = **in
+ }
+ if in.Resource != nil {
+ in, out := &in.Resource, &out.Resource
+ *out = new(ListenerResourceStatus)
+ (*in).DeepCopyInto(*out)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListenerStatus.
+func (in *ListenerStatus) DeepCopy() *ListenerStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(ListenerStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LoadBalancer) DeepCopyInto(out *LoadBalancer) {
+ *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 LoadBalancer.
+func (in *LoadBalancer) DeepCopy() *LoadBalancer {
+ if in == nil {
+ return nil
+ }
+ out := new(LoadBalancer)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *LoadBalancer) 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 *LoadBalancerFilter) DeepCopyInto(out *LoadBalancerFilter) {
+ *out = *in
+ if in.Name != nil {
+ in, out := &in.Name, &out.Name
+ *out = new(OpenStackName)
+ **out = **in
+ }
+ if in.Description != nil {
+ in, out := &in.Description, &out.Description
+ *out = new(string)
+ **out = **in
+ }
+ if in.ProjectRef != nil {
+ in, out := &in.ProjectRef, &out.ProjectRef
+ *out = new(KubernetesNameRef)
+ **out = **in
+ }
+ if in.VipSubnetRef != nil {
+ in, out := &in.VipSubnetRef, &out.VipSubnetRef
+ *out = new(KubernetesNameRef)
+ **out = **in
+ }
+ if in.VipNetworkRef != nil {
+ in, out := &in.VipNetworkRef, &out.VipNetworkRef
+ *out = new(KubernetesNameRef)
+ **out = **in
+ }
+ if in.VipPortRef != nil {
+ in, out := &in.VipPortRef, &out.VipPortRef
+ *out = new(KubernetesNameRef)
+ **out = **in
+ }
+ if in.Tags != nil {
+ in, out := &in.Tags, &out.Tags
+ *out = make([]LoadBalancerTag, len(*in))
+ copy(*out, *in)
+ }
+ if in.TagsAny != nil {
+ in, out := &in.TagsAny, &out.TagsAny
+ *out = make([]LoadBalancerTag, len(*in))
+ copy(*out, *in)
+ }
+ if in.NotTags != nil {
+ in, out := &in.NotTags, &out.NotTags
+ *out = make([]LoadBalancerTag, len(*in))
+ copy(*out, *in)
+ }
+ if in.NotTagsAny != nil {
+ in, out := &in.NotTagsAny, &out.NotTagsAny
+ *out = make([]LoadBalancerTag, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancerFilter.
+func (in *LoadBalancerFilter) DeepCopy() *LoadBalancerFilter {
+ if in == nil {
+ return nil
+ }
+ out := new(LoadBalancerFilter)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LoadBalancerImport) DeepCopyInto(out *LoadBalancerImport) {
+ *out = *in
+ if in.ID != nil {
+ in, out := &in.ID, &out.ID
+ *out = new(string)
+ **out = **in
+ }
+ if in.Filter != nil {
+ in, out := &in.Filter, &out.Filter
+ *out = new(LoadBalancerFilter)
+ (*in).DeepCopyInto(*out)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancerImport.
+func (in *LoadBalancerImport) DeepCopy() *LoadBalancerImport {
+ if in == nil {
+ return nil
+ }
+ out := new(LoadBalancerImport)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LoadBalancerList) DeepCopyInto(out *LoadBalancerList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]LoadBalancer, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancerList.
+func (in *LoadBalancerList) DeepCopy() *LoadBalancerList {
+ if in == nil {
+ return nil
+ }
+ out := new(LoadBalancerList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *LoadBalancerList) 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 *LoadBalancerResourceSpec) DeepCopyInto(out *LoadBalancerResourceSpec) {
+ *out = *in
+ if in.Name != nil {
+ in, out := &in.Name, &out.Name
+ *out = new(OpenStackName)
+ **out = **in
+ }
+ if in.Description != nil {
+ in, out := &in.Description, &out.Description
+ *out = new(string)
+ **out = **in
+ }
+ if in.VipSubnetRef != nil {
+ in, out := &in.VipSubnetRef, &out.VipSubnetRef
+ *out = new(KubernetesNameRef)
+ **out = **in
+ }
+ if in.VipNetworkRef != nil {
+ in, out := &in.VipNetworkRef, &out.VipNetworkRef
+ *out = new(KubernetesNameRef)
+ **out = **in
+ }
+ if in.VipPortRef != nil {
+ in, out := &in.VipPortRef, &out.VipPortRef
+ *out = new(KubernetesNameRef)
+ **out = **in
+ }
+ if in.FlavorRef != nil {
+ in, out := &in.FlavorRef, &out.FlavorRef
+ *out = new(KubernetesNameRef)
+ **out = **in
+ }
+ if in.ProjectRef != nil {
+ in, out := &in.ProjectRef, &out.ProjectRef
+ *out = new(KubernetesNameRef)
+ **out = **in
+ }
+ if in.AdminStateUp != nil {
+ in, out := &in.AdminStateUp, &out.AdminStateUp
+ *out = new(bool)
+ **out = **in
+ }
+ if in.VipAddress != nil {
+ in, out := &in.VipAddress, &out.VipAddress
+ *out = new(IPvAny)
+ **out = **in
+ }
+ if in.Tags != nil {
+ in, out := &in.Tags, &out.Tags
+ *out = make([]LoadBalancerTag, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancerResourceSpec.
+func (in *LoadBalancerResourceSpec) DeepCopy() *LoadBalancerResourceSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(LoadBalancerResourceSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LoadBalancerResourceStatus) DeepCopyInto(out *LoadBalancerResourceStatus) {
+ *out = *in
+ if in.AdminStateUp != nil {
+ in, out := &in.AdminStateUp, &out.AdminStateUp
+ *out = new(bool)
+ **out = **in
+ }
+ if in.Tags != nil {
+ in, out := &in.Tags, &out.Tags
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancerResourceStatus.
+func (in *LoadBalancerResourceStatus) DeepCopy() *LoadBalancerResourceStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(LoadBalancerResourceStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LoadBalancerSpec) DeepCopyInto(out *LoadBalancerSpec) {
+ *out = *in
+ if in.Import != nil {
+ in, out := &in.Import, &out.Import
+ *out = new(LoadBalancerImport)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.Resource != nil {
+ in, out := &in.Resource, &out.Resource
+ *out = new(LoadBalancerResourceSpec)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.ManagedOptions != nil {
+ in, out := &in.ManagedOptions, &out.ManagedOptions
+ *out = new(ManagedOptions)
+ **out = **in
+ }
+ out.CloudCredentialsRef = in.CloudCredentialsRef
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancerSpec.
+func (in *LoadBalancerSpec) DeepCopy() *LoadBalancerSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(LoadBalancerSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LoadBalancerStatus) DeepCopyInto(out *LoadBalancerStatus) {
+ *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])
+ }
+ }
+ if in.ID != nil {
+ in, out := &in.ID, &out.ID
+ *out = new(string)
+ **out = **in
+ }
+ if in.Resource != nil {
+ in, out := &in.Resource, &out.Resource
+ *out = new(LoadBalancerResourceStatus)
+ (*in).DeepCopyInto(*out)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancerStatus.
+func (in *LoadBalancerStatus) DeepCopy() *LoadBalancerStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(LoadBalancerStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ManagedOptions) DeepCopyInto(out *ManagedOptions) {
*out = *in
diff --git a/api/v1alpha1/zz_generated.listener-resource.go b/api/v1alpha1/zz_generated.listener-resource.go
new file mode 100644
index 000000000..998bc153a
--- /dev/null
+++ b/api/v1alpha1/zz_generated.listener-resource.go
@@ -0,0 +1,177 @@
+// Code generated by resource-generator. DO NOT EDIT.
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package v1alpha1
+
+import (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// ListenerImport specifies an existing resource which will be imported instead of
+// creating a new one
+// +kubebuilder:validation:MinProperties:=1
+// +kubebuilder:validation:MaxProperties:=1
+type ListenerImport struct {
+ // id contains the unique identifier of an existing OpenStack resource. Note
+ // that when specifying an import by ID, the resource MUST already exist.
+ // The ORC object will enter an error state if the resource does not exist.
+ // +optional
+ // +kubebuilder:validation:Format:=uuid
+ ID *string `json:"id,omitempty"`
+
+ // filter contains a resource query which is expected to return a single
+ // result. The controller will continue to retry if filter returns no
+ // results. If filter returns multiple results the controller will set an
+ // error state and will not continue to retry.
+ // +optional
+ Filter *ListenerFilter `json:"filter,omitempty"`
+}
+
+// ListenerSpec defines the desired state of an ORC object.
+// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'managed' ? has(self.resource) : true",message="resource must be specified when policy is managed"
+// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'managed' ? !has(self.__import__) : true",message="import may not be specified when policy is managed"
+// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'unmanaged' ? !has(self.resource) : true",message="resource may not be specified when policy is unmanaged"
+// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'unmanaged' ? has(self.__import__) : true",message="import must be specified when policy is unmanaged"
+// +kubebuilder:validation:XValidation:rule="has(self.managedOptions) ? self.managementPolicy == 'managed' : true",message="managedOptions may only be provided when policy is managed"
+type ListenerSpec struct {
+ // import refers to an existing OpenStack resource which will be imported instead of
+ // creating a new one.
+ // +optional
+ Import *ListenerImport `json:"import,omitempty"`
+
+ // resource specifies the desired state of the resource.
+ //
+ // resource may not be specified if the management policy is `unmanaged`.
+ //
+ // resource must be specified if the management policy is `managed`.
+ // +optional
+ Resource *ListenerResourceSpec `json:"resource,omitempty"`
+
+ // managementPolicy defines how ORC will treat the object. Valid values are
+ // `managed`: ORC will create, update, and delete the resource; `unmanaged`:
+ // ORC will import an existing resource, and will not apply updates to it or
+ // delete it.
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="managementPolicy is immutable"
+ // +kubebuilder:default:=managed
+ // +optional
+ ManagementPolicy ManagementPolicy `json:"managementPolicy,omitempty"`
+
+ // managedOptions specifies options which may be applied to managed objects.
+ // +optional
+ ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"`
+
+ // cloudCredentialsRef points to a secret containing OpenStack credentials
+ // +required
+ CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"`
+}
+
+// ListenerStatus defines the observed state of an ORC resource.
+type ListenerStatus struct {
+ // conditions represents the observed status of the object.
+ // Known .status.conditions.type are: "Available", "Progressing"
+ //
+ // Available represents the availability of the OpenStack resource. If it is
+ // true then the resource is ready for use.
+ //
+ // Progressing indicates whether the controller is still attempting to
+ // reconcile the current state of the OpenStack resource to the desired
+ // state. Progressing will be False either because the desired state has
+ // been achieved, or because some terminal error prevents it from ever being
+ // achieved and the controller is no longer attempting to reconcile. If
+ // Progressing is True, an observer waiting on the resource should continue
+ // to wait.
+ //
+ // +kubebuilder:validation:MaxItems:=32
+ // +patchMergeKey=type
+ // +patchStrategy=merge
+ // +listType=map
+ // +listMapKey=type
+ // +optional
+ Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
+
+ // id is the unique identifier of the OpenStack resource.
+ // +optional
+ ID *string `json:"id,omitempty"`
+
+ // resource contains the observed state of the OpenStack resource.
+ // +optional
+ Resource *ListenerResourceStatus `json:"resource,omitempty"`
+}
+
+var _ ObjectWithConditions = &Listener{}
+
+func (i *Listener) GetConditions() []metav1.Condition {
+ return i.Status.Conditions
+}
+
+// +genclient
+// +kubebuilder:object:root=true
+// +kubebuilder:resource:categories=openstack
+// +kubebuilder:subresource:status
+// +kubebuilder:printcolumn:name="ID",type="string",JSONPath=".status.id",description="Resource ID"
+// +kubebuilder:printcolumn:name="Available",type="string",JSONPath=".status.conditions[?(@.type=='Available')].status",description="Availability status of resource"
+// +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[?(@.type=='Progressing')].message",description="Message describing current progress status"
+
+// Listener is the Schema for an ORC resource.
+type Listener struct {
+ metav1.TypeMeta `json:",inline"`
+
+ // metadata contains the object metadata
+ // +optional
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ // spec specifies the desired state of the resource.
+ // +optional
+ Spec ListenerSpec `json:"spec,omitempty"`
+
+ // status defines the observed state of the resource.
+ // +optional
+ Status ListenerStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// ListenerList contains a list of Listener.
+type ListenerList struct {
+ metav1.TypeMeta `json:",inline"`
+
+ // metadata contains the list metadata
+ // +optional
+ metav1.ListMeta `json:"metadata,omitempty"`
+
+ // items contains a list of Listener.
+ // +required
+ Items []Listener `json:"items"`
+}
+
+func (l *ListenerList) GetItems() []Listener {
+ return l.Items
+}
+
+func init() {
+ SchemeBuilder.Register(&Listener{}, &ListenerList{})
+}
+
+func (i *Listener) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) {
+ if i == nil {
+ return nil, nil
+ }
+
+ return &i.Namespace, &i.Spec.CloudCredentialsRef
+}
+
+var _ CloudCredentialsRefProvider = &Listener{}
diff --git a/api/v1alpha1/zz_generated.loadbalancer-resource.go b/api/v1alpha1/zz_generated.loadbalancer-resource.go
new file mode 100644
index 000000000..abd944e9d
--- /dev/null
+++ b/api/v1alpha1/zz_generated.loadbalancer-resource.go
@@ -0,0 +1,177 @@
+// Code generated by resource-generator. DO NOT EDIT.
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package v1alpha1
+
+import (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// LoadBalancerImport specifies an existing resource which will be imported instead of
+// creating a new one
+// +kubebuilder:validation:MinProperties:=1
+// +kubebuilder:validation:MaxProperties:=1
+type LoadBalancerImport struct {
+ // id contains the unique identifier of an existing OpenStack resource. Note
+ // that when specifying an import by ID, the resource MUST already exist.
+ // The ORC object will enter an error state if the resource does not exist.
+ // +optional
+ // +kubebuilder:validation:Format:=uuid
+ ID *string `json:"id,omitempty"`
+
+ // filter contains a resource query which is expected to return a single
+ // result. The controller will continue to retry if filter returns no
+ // results. If filter returns multiple results the controller will set an
+ // error state and will not continue to retry.
+ // +optional
+ Filter *LoadBalancerFilter `json:"filter,omitempty"`
+}
+
+// LoadBalancerSpec defines the desired state of an ORC object.
+// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'managed' ? has(self.resource) : true",message="resource must be specified when policy is managed"
+// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'managed' ? !has(self.__import__) : true",message="import may not be specified when policy is managed"
+// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'unmanaged' ? !has(self.resource) : true",message="resource may not be specified when policy is unmanaged"
+// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'unmanaged' ? has(self.__import__) : true",message="import must be specified when policy is unmanaged"
+// +kubebuilder:validation:XValidation:rule="has(self.managedOptions) ? self.managementPolicy == 'managed' : true",message="managedOptions may only be provided when policy is managed"
+type LoadBalancerSpec struct {
+ // import refers to an existing OpenStack resource which will be imported instead of
+ // creating a new one.
+ // +optional
+ Import *LoadBalancerImport `json:"import,omitempty"`
+
+ // resource specifies the desired state of the resource.
+ //
+ // resource may not be specified if the management policy is `unmanaged`.
+ //
+ // resource must be specified if the management policy is `managed`.
+ // +optional
+ Resource *LoadBalancerResourceSpec `json:"resource,omitempty"`
+
+ // managementPolicy defines how ORC will treat the object. Valid values are
+ // `managed`: ORC will create, update, and delete the resource; `unmanaged`:
+ // ORC will import an existing resource, and will not apply updates to it or
+ // delete it.
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="managementPolicy is immutable"
+ // +kubebuilder:default:=managed
+ // +optional
+ ManagementPolicy ManagementPolicy `json:"managementPolicy,omitempty"`
+
+ // managedOptions specifies options which may be applied to managed objects.
+ // +optional
+ ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"`
+
+ // cloudCredentialsRef points to a secret containing OpenStack credentials
+ // +required
+ CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"`
+}
+
+// LoadBalancerStatus defines the observed state of an ORC resource.
+type LoadBalancerStatus struct {
+ // conditions represents the observed status of the object.
+ // Known .status.conditions.type are: "Available", "Progressing"
+ //
+ // Available represents the availability of the OpenStack resource. If it is
+ // true then the resource is ready for use.
+ //
+ // Progressing indicates whether the controller is still attempting to
+ // reconcile the current state of the OpenStack resource to the desired
+ // state. Progressing will be False either because the desired state has
+ // been achieved, or because some terminal error prevents it from ever being
+ // achieved and the controller is no longer attempting to reconcile. If
+ // Progressing is True, an observer waiting on the resource should continue
+ // to wait.
+ //
+ // +kubebuilder:validation:MaxItems:=32
+ // +patchMergeKey=type
+ // +patchStrategy=merge
+ // +listType=map
+ // +listMapKey=type
+ // +optional
+ Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
+
+ // id is the unique identifier of the OpenStack resource.
+ // +optional
+ ID *string `json:"id,omitempty"`
+
+ // resource contains the observed state of the OpenStack resource.
+ // +optional
+ Resource *LoadBalancerResourceStatus `json:"resource,omitempty"`
+}
+
+var _ ObjectWithConditions = &LoadBalancer{}
+
+func (i *LoadBalancer) GetConditions() []metav1.Condition {
+ return i.Status.Conditions
+}
+
+// +genclient
+// +kubebuilder:object:root=true
+// +kubebuilder:resource:categories=openstack
+// +kubebuilder:subresource:status
+// +kubebuilder:printcolumn:name="ID",type="string",JSONPath=".status.id",description="Resource ID"
+// +kubebuilder:printcolumn:name="Available",type="string",JSONPath=".status.conditions[?(@.type=='Available')].status",description="Availability status of resource"
+// +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[?(@.type=='Progressing')].message",description="Message describing current progress status"
+
+// LoadBalancer is the Schema for an ORC resource.
+type LoadBalancer struct {
+ metav1.TypeMeta `json:",inline"`
+
+ // metadata contains the object metadata
+ // +optional
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ // spec specifies the desired state of the resource.
+ // +optional
+ Spec LoadBalancerSpec `json:"spec,omitempty"`
+
+ // status defines the observed state of the resource.
+ // +optional
+ Status LoadBalancerStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// LoadBalancerList contains a list of LoadBalancer.
+type LoadBalancerList struct {
+ metav1.TypeMeta `json:",inline"`
+
+ // metadata contains the list metadata
+ // +optional
+ metav1.ListMeta `json:"metadata,omitempty"`
+
+ // items contains a list of LoadBalancer.
+ // +required
+ Items []LoadBalancer `json:"items"`
+}
+
+func (l *LoadBalancerList) GetItems() []LoadBalancer {
+ return l.Items
+}
+
+func init() {
+ SchemeBuilder.Register(&LoadBalancer{}, &LoadBalancerList{})
+}
+
+func (i *LoadBalancer) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) {
+ if i == nil {
+ return nil, nil
+ }
+
+ return &i.Namespace, &i.Spec.CloudCredentialsRef
+}
+
+var _ CloudCredentialsRefProvider = &LoadBalancer{}
diff --git a/cmd/manager/main.go b/cmd/manager/main.go
index 293aa2bab..995010bd4 100644
--- a/cmd/manager/main.go
+++ b/cmd/manager/main.go
@@ -34,6 +34,8 @@ import (
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/group"
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/image"
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/keypair"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/listener"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/loadbalancer"
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/network"
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/port"
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/project"
@@ -126,6 +128,8 @@ func main() {
keypair.New(scopeFactory),
group.New(scopeFactory),
role.New(scopeFactory),
+ listener.New(scopeFactory),
+ loadbalancer.New(scopeFactory),
}
restConfig := ctrl.GetConfigOrDie()
diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go
index 8eab33c2d..00ec7d045 100644
--- a/cmd/models-schema/zz_generated.openapi.go
+++ b/cmd/models-schema/zz_generated.openapi.go
@@ -100,6 +100,23 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairResourceStatus(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairSpec": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairSpec(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairStatus": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairStatus(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Listener": schema_openstack_resource_controller_v2_api_v1alpha1_Listener(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ListenerFilter(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerHSTS": schema_openstack_resource_controller_v2_api_v1alpha1_ListenerHSTS(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerImport": schema_openstack_resource_controller_v2_api_v1alpha1_ListenerImport(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerList": schema_openstack_resource_controller_v2_api_v1alpha1_ListenerList(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ListenerResourceSpec(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ListenerResourceStatus(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ListenerSpec(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ListenerStatus(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancer": schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancer(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerFilter": schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerFilter(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerImport": schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerImport(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerList": schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerList(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerResourceSpec(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerResourceStatus(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerSpec": schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerSpec(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerStatus": schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerStatus(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions": schema_openstack_resource_controller_v2_api_v1alpha1_ManagedOptions(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Network": schema_openstack_resource_controller_v2_api_v1alpha1_Network(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkFilter": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkFilter(ref),
@@ -3759,6 +3776,1440 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairStatus(ref comm
}
}
+func schema_openstack_resource_controller_v2_api_v1alpha1_Listener(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "Listener is the Schema for an ORC resource.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "kind": {
+ SchemaProps: spec.SchemaProps{
+ 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{"string"},
+ Format: "",
+ },
+ },
+ "apiVersion": {
+ SchemaProps: spec.SchemaProps{
+ 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{"string"},
+ Format: "",
+ },
+ },
+ "metadata": {
+ SchemaProps: spec.SchemaProps{
+ Description: "metadata contains the object metadata",
+ Default: map[string]interface{}{},
+ Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
+ },
+ },
+ "spec": {
+ SchemaProps: spec.SchemaProps{
+ Description: "spec specifies the desired state of the resource.",
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerSpec"),
+ },
+ },
+ "status": {
+ SchemaProps: spec.SchemaProps{
+ Description: "status defines the observed state of the resource.",
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerStatus"),
+ },
+ },
+ },
+ },
+ },
+ Dependencies: []string{
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_ListenerFilter(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "ListenerFilter defines an existing resource by its properties.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "name": {
+ SchemaProps: spec.SchemaProps{
+ Description: "name of the existing resource.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "description": {
+ SchemaProps: spec.SchemaProps{
+ Description: "description of the existing resource.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "loadBalancerRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "loadBalancerRef filters by the LoadBalancer this listener belongs to.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "protocol": {
+ SchemaProps: spec.SchemaProps{
+ Description: "protocol filters by the protocol used by the listener.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "protocolPort": {
+ SchemaProps: spec.SchemaProps{
+ Description: "protocolPort filters by the port used by the listener.",
+ Type: []string{"integer"},
+ Format: "int32",
+ },
+ },
+ "tags": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "set",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "tags is a list of tags to filter by. If specified, the resource must have all of the tags specified to be included in the result.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "tagsAny": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "set",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "tagsAny is a list of tags to filter by. If specified, the resource must have at least one of the tags specified to be included in the result.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "notTags": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "set",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "notTags is a list of tags to filter by. If specified, resources which contain all of the given tags will be excluded from the result.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "notTagsAny": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "set",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "notTagsAny is a list of tags to filter by. If specified, resources which contain any of the given tags will be excluded from the result.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_ListenerHSTS(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "ListenerHSTS represents HTTP Strict Transport Security configuration.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "maxAge": {
+ SchemaProps: spec.SchemaProps{
+ Description: "maxAge is the maximum time in seconds that the browser should remember that this site is only to be accessed using HTTPS.",
+ Type: []string{"integer"},
+ Format: "int32",
+ },
+ },
+ "includeSubDomains": {
+ SchemaProps: spec.SchemaProps{
+ Description: "includeSubDomains specifies whether this rule applies to all subdomains.",
+ Type: []string{"boolean"},
+ Format: "",
+ },
+ },
+ "preload": {
+ SchemaProps: spec.SchemaProps{
+ Description: "preload specifies whether the domain should be included in browsers' preload list.",
+ Type: []string{"boolean"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_ListenerImport(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "ListenerImport specifies an existing resource which will be imported instead of creating a new one",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "id": {
+ SchemaProps: spec.SchemaProps{
+ Description: "id contains the unique identifier of an existing OpenStack resource. Note that when specifying an import by ID, the resource MUST already exist. The ORC object will enter an error state if the resource does not exist.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "filter": {
+ SchemaProps: spec.SchemaProps{
+ Description: "filter contains a resource query which is expected to return a single result. The controller will continue to retry if filter returns no results. If filter returns multiple results the controller will set an error state and will not continue to retry.",
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerFilter"),
+ },
+ },
+ },
+ },
+ },
+ Dependencies: []string{
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerFilter"},
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_ListenerList(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "ListenerList contains a list of Listener.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "kind": {
+ SchemaProps: spec.SchemaProps{
+ 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{"string"},
+ Format: "",
+ },
+ },
+ "apiVersion": {
+ SchemaProps: spec.SchemaProps{
+ 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{"string"},
+ Format: "",
+ },
+ },
+ "metadata": {
+ SchemaProps: spec.SchemaProps{
+ Description: "metadata contains the list metadata",
+ Default: map[string]interface{}{},
+ Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
+ },
+ },
+ "items": {
+ SchemaProps: spec.SchemaProps{
+ Description: "items contains a list of Listener.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Listener"),
+ },
+ },
+ },
+ },
+ },
+ },
+ Required: []string{"items"},
+ },
+ },
+ Dependencies: []string{
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Listener", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_ListenerResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "ListenerResourceSpec contains the desired state of the resource.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "name": {
+ SchemaProps: spec.SchemaProps{
+ Description: "name will be the name of the created resource. If not specified, the name of the ORC object will be used.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "description": {
+ SchemaProps: spec.SchemaProps{
+ Description: "description is a human-readable description for the resource.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "loadBalancerRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "loadBalancerRef is a reference to the LoadBalancer this listener belongs to.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "protocol": {
+ SchemaProps: spec.SchemaProps{
+ Description: "protocol is the protocol the listener will use.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "protocolPort": {
+ SchemaProps: spec.SchemaProps{
+ Description: "protocolPort is the port on which the listener will accept connections.",
+ Type: []string{"integer"},
+ Format: "int32",
+ },
+ },
+ "adminStateUp": {
+ SchemaProps: spec.SchemaProps{
+ Description: "adminStateUp is the administrative state of the listener, which is up (true) or down (false).",
+ Type: []string{"boolean"},
+ Format: "",
+ },
+ },
+ "connectionLimit": {
+ SchemaProps: spec.SchemaProps{
+ Description: "connectionLimit is the maximum number of connections permitted for this listener. Default value is -1 which represents infinite connections.",
+ Type: []string{"integer"},
+ Format: "int32",
+ },
+ },
+ "defaultTLSContainerRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "defaultTLSContainerRef is a reference to a secret containing a PKCS12 format certificate/key bundle for TERMINATED_HTTPS listeners.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "sniContainerRefs": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "set",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "sniContainerRefs is a list of references to secrets containing PKCS12 format certificate/key bundles for TERMINATED_HTTPS listeners using SNI.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "defaultPoolRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "defaultPoolRef is a reference to the default Pool for this listener.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "insertHeaders": {
+ SchemaProps: spec.SchemaProps{
+ Description: "insertHeaders is a dictionary of optional headers to insert into the request before it is sent to the backend member.",
+ Type: []string{"object"},
+ AdditionalProperties: &spec.SchemaOrBool{
+ Allows: true,
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "timeoutClientData": {
+ SchemaProps: spec.SchemaProps{
+ Description: "timeoutClientData is the frontend client inactivity timeout in milliseconds.",
+ Type: []string{"integer"},
+ Format: "int32",
+ },
+ },
+ "timeoutMemberConnect": {
+ SchemaProps: spec.SchemaProps{
+ Description: "timeoutMemberConnect is the backend member connection timeout in milliseconds.",
+ Type: []string{"integer"},
+ Format: "int32",
+ },
+ },
+ "timeoutMemberData": {
+ SchemaProps: spec.SchemaProps{
+ Description: "timeoutMemberData is the backend member inactivity timeout in milliseconds.",
+ Type: []string{"integer"},
+ Format: "int32",
+ },
+ },
+ "timeoutTCPInspect": {
+ SchemaProps: spec.SchemaProps{
+ Description: "timeoutTCPInspect is the time in milliseconds to wait for additional TCP packets for content inspection.",
+ Type: []string{"integer"},
+ Format: "int32",
+ },
+ },
+ "allowedCIDRs": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "set",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "allowedCIDRs is a list of IPv4/IPv6 CIDRs that are permitted to connect to this listener.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "tlsCiphers": {
+ SchemaProps: spec.SchemaProps{
+ Description: "tlsCiphers is a colon-separated list of ciphers for TLS-terminated listeners.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "tlsVersions": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "set",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "tlsVersions is a list of TLS protocol versions to be used by the listener.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "alpnProtocols": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "set",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "alpnProtocols is a list of ALPN protocols for TLS-enabled listeners.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "clientAuthentication": {
+ SchemaProps: spec.SchemaProps{
+ Description: "clientAuthentication is the TLS client authentication mode.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "clientCATLSContainerRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "clientCATLSContainerRef is a reference to a secret containing the CA certificate for client authentication.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "clientCRLContainerRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "clientCRLContainerRef is a reference to a secret containing the CA revocation list for client authentication.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "hsts": {
+ SchemaProps: spec.SchemaProps{
+ Description: "hsts is the HTTP Strict Transport Security configuration.",
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerHSTS"),
+ },
+ },
+ "tags": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "set",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "tags is a list of tags which will be applied to the listener.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ },
+ Required: []string{"loadBalancerRef", "protocol", "protocolPort"},
+ },
+ },
+ Dependencies: []string{
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerHSTS"},
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_ListenerResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "ListenerResourceStatus represents the observed state of the resource.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "name": {
+ SchemaProps: spec.SchemaProps{
+ Description: "name is a human-readable name for the resource.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "description": {
+ SchemaProps: spec.SchemaProps{
+ Description: "description is a human-readable description for the resource.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "loadBalancerID": {
+ SchemaProps: spec.SchemaProps{
+ Description: "loadBalancerID is the ID of the LoadBalancer this listener belongs to.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "protocol": {
+ SchemaProps: spec.SchemaProps{
+ Description: "protocol is the protocol used by the listener.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "protocolPort": {
+ SchemaProps: spec.SchemaProps{
+ Description: "protocolPort is the port used by the listener.",
+ Type: []string{"integer"},
+ Format: "int32",
+ },
+ },
+ "adminStateUp": {
+ SchemaProps: spec.SchemaProps{
+ Description: "adminStateUp is the administrative state of the listener, which is up (true) or down (false).",
+ Type: []string{"boolean"},
+ Format: "",
+ },
+ },
+ "connectionLimit": {
+ SchemaProps: spec.SchemaProps{
+ Description: "connectionLimit is the maximum number of connections permitted for this listener.",
+ Type: []string{"integer"},
+ Format: "int32",
+ },
+ },
+ "defaultPoolID": {
+ SchemaProps: spec.SchemaProps{
+ Description: "defaultPoolID is the ID of the default pool for this listener.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "provisioningStatus": {
+ SchemaProps: spec.SchemaProps{
+ Description: "provisioningStatus is the provisioning status of the listener.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "operatingStatus": {
+ SchemaProps: spec.SchemaProps{
+ Description: "operatingStatus is the operating status of the listener.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "allowedCIDRs": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "atomic",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "allowedCIDRs is the list of CIDRs permitted to connect to this listener.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "timeoutClientData": {
+ SchemaProps: spec.SchemaProps{
+ Description: "timeoutClientData is the frontend client inactivity timeout in milliseconds.",
+ Type: []string{"integer"},
+ Format: "int32",
+ },
+ },
+ "timeoutMemberConnect": {
+ SchemaProps: spec.SchemaProps{
+ Description: "timeoutMemberConnect is the backend member connection timeout in milliseconds.",
+ Type: []string{"integer"},
+ Format: "int32",
+ },
+ },
+ "timeoutMemberData": {
+ SchemaProps: spec.SchemaProps{
+ Description: "timeoutMemberData is the backend member inactivity timeout in milliseconds.",
+ Type: []string{"integer"},
+ Format: "int32",
+ },
+ },
+ "timeoutTCPInspect": {
+ SchemaProps: spec.SchemaProps{
+ Description: "timeoutTCPInspect is the time to wait for additional TCP packets in milliseconds.",
+ Type: []string{"integer"},
+ Format: "int32",
+ },
+ },
+ "insertHeaders": {
+ SchemaProps: spec.SchemaProps{
+ Description: "insertHeaders is a dictionary of headers inserted into the request.",
+ Type: []string{"object"},
+ AdditionalProperties: &spec.SchemaOrBool{
+ Allows: true,
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "tags": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "atomic",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "tags is the list of tags on the resource.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_ListenerSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "ListenerSpec defines the desired state of an ORC object.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "import": {
+ SchemaProps: spec.SchemaProps{
+ Description: "import refers to an existing OpenStack resource which will be imported instead of creating a new one.",
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerImport"),
+ },
+ },
+ "resource": {
+ SchemaProps: spec.SchemaProps{
+ Description: "resource specifies the desired state of the resource.\n\nresource may not be specified if the management policy is `unmanaged`.\n\nresource must be specified if the management policy is `managed`.",
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerResourceSpec"),
+ },
+ },
+ "managementPolicy": {
+ SchemaProps: spec.SchemaProps{
+ Description: "managementPolicy defines how ORC will treat the object. Valid values are `managed`: ORC will create, update, and delete the resource; `unmanaged`: ORC will import an existing resource, and will not apply updates to it or delete it.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "managedOptions": {
+ SchemaProps: spec.SchemaProps{
+ Description: "managedOptions specifies options which may be applied to managed objects.",
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"),
+ },
+ },
+ "cloudCredentialsRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials",
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference"),
+ },
+ },
+ },
+ Required: []string{"cloudCredentialsRef"},
+ },
+ },
+ Dependencies: []string{
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"},
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_ListenerStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "ListenerStatus defines the observed state of an ORC resource.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "conditions": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-map-keys": []interface{}{
+ "type",
+ },
+ "x-kubernetes-list-type": "map",
+ "x-kubernetes-patch-merge-key": "type",
+ "x-kubernetes-patch-strategy": "merge",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "conditions represents the observed status of the object. Known .status.conditions.type are: \"Available\", \"Progressing\"\n\nAvailable represents the availability of the OpenStack resource. If it is true then the resource is ready for use.\n\nProgressing indicates whether the controller is still attempting to reconcile the current state of the OpenStack resource to the desired state. Progressing will be False either because the desired state has been achieved, or because some terminal error prevents it from ever being achieved and the controller is no longer attempting to reconcile. If Progressing is True, an observer waiting on the resource should continue to wait.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Condition"),
+ },
+ },
+ },
+ },
+ },
+ "id": {
+ SchemaProps: spec.SchemaProps{
+ Description: "id is the unique identifier of the OpenStack resource.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "resource": {
+ SchemaProps: spec.SchemaProps{
+ Description: "resource contains the observed state of the OpenStack resource.",
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerResourceStatus"),
+ },
+ },
+ },
+ },
+ },
+ Dependencies: []string{
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ListenerResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"},
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancer(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "LoadBalancer is the Schema for an ORC resource.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "kind": {
+ SchemaProps: spec.SchemaProps{
+ 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{"string"},
+ Format: "",
+ },
+ },
+ "apiVersion": {
+ SchemaProps: spec.SchemaProps{
+ 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{"string"},
+ Format: "",
+ },
+ },
+ "metadata": {
+ SchemaProps: spec.SchemaProps{
+ Description: "metadata contains the object metadata",
+ Default: map[string]interface{}{},
+ Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
+ },
+ },
+ "spec": {
+ SchemaProps: spec.SchemaProps{
+ Description: "spec specifies the desired state of the resource.",
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerSpec"),
+ },
+ },
+ "status": {
+ SchemaProps: spec.SchemaProps{
+ Description: "status defines the observed state of the resource.",
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerStatus"),
+ },
+ },
+ },
+ },
+ },
+ Dependencies: []string{
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerFilter(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "LoadBalancerFilter defines an existing resource by its properties",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "name": {
+ SchemaProps: spec.SchemaProps{
+ Description: "name of the existing resource",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "description": {
+ SchemaProps: spec.SchemaProps{
+ Description: "description of the existing resource",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "projectRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "projectRef is a reference to the ORC Project this resource is associated with. Typically, only used by admin.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "vipSubnetRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "vipSubnetRef filters by the subnet on which the load balancer's address is allocated.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "vipNetworkRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "vipNetworkRef filters by the network on which the load balancer's address is allocated.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "vipPortRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "vipPortRef filters by the neutron port used for the VIP.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "availabilityZone": {
+ SchemaProps: spec.SchemaProps{
+ Description: "availabilityZone is the availability zone in which to create the load balancer.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "provider": {
+ SchemaProps: spec.SchemaProps{
+ Description: "provider filters by the name of the load balancer provider.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "vipAddress": {
+ SchemaProps: spec.SchemaProps{
+ Description: "vipAddress filters by the IP address of the load balancer's VIP.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "tags": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "set",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "tags is a list of tags to filter by. If specified, the resource must have all of the tags specified to be included in the result.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "tagsAny": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "set",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "tagsAny is a list of tags to filter by. If specified, the resource must have at least one of the tags specified to be included in the result.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "notTags": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "set",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "notTags is a list of tags to filter by. If specified, resources which contain all of the given tags will be excluded from the result.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "notTagsAny": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "set",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "notTagsAny is a list of tags to filter by. If specified, resources which contain any of the given tags will be excluded from the result.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerImport(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "LoadBalancerImport specifies an existing resource which will be imported instead of creating a new one",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "id": {
+ SchemaProps: spec.SchemaProps{
+ Description: "id contains the unique identifier of an existing OpenStack resource. Note that when specifying an import by ID, the resource MUST already exist. The ORC object will enter an error state if the resource does not exist.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "filter": {
+ SchemaProps: spec.SchemaProps{
+ Description: "filter contains a resource query which is expected to return a single result. The controller will continue to retry if filter returns no results. If filter returns multiple results the controller will set an error state and will not continue to retry.",
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerFilter"),
+ },
+ },
+ },
+ },
+ },
+ Dependencies: []string{
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerFilter"},
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerList(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "LoadBalancerList contains a list of LoadBalancer.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "kind": {
+ SchemaProps: spec.SchemaProps{
+ 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{"string"},
+ Format: "",
+ },
+ },
+ "apiVersion": {
+ SchemaProps: spec.SchemaProps{
+ 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{"string"},
+ Format: "",
+ },
+ },
+ "metadata": {
+ SchemaProps: spec.SchemaProps{
+ Description: "metadata contains the list metadata",
+ Default: map[string]interface{}{},
+ Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
+ },
+ },
+ "items": {
+ SchemaProps: spec.SchemaProps{
+ Description: "items contains a list of LoadBalancer.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancer"),
+ },
+ },
+ },
+ },
+ },
+ },
+ Required: []string{"items"},
+ },
+ },
+ Dependencies: []string{
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancer", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "LoadBalancerResourceSpec contains the desired state of the resource.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "name": {
+ SchemaProps: spec.SchemaProps{
+ Description: "name will be the name of the created resource. If not specified, the name of the ORC object will be used.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "description": {
+ SchemaProps: spec.SchemaProps{
+ Description: "description is a human-readable description for the resource.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "vipSubnetRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "vipSubnetRef is the subnet on which to allocate the load balancer's address.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "vipNetworkRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "vipNetworkRef is the network on which to allocate the load balancer's address.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "vipPortRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "vipPortRef is a reference to a neutron port to use for the VIP. If the port has more than one subnet you must specify either vipSubnetRef or vipAddress to clarify which address should be used for the VIP.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "flavorRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "flavorRef is a reference to the ORC Flavor which this resource is associated with.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "projectRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "projectRef is a reference to the ORC Project which this resource is associated with.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "adminStateUp": {
+ SchemaProps: spec.SchemaProps{
+ Description: "adminStateUp is the administrative state of the load balancer, which is up (true) or down (false)",
+ Type: []string{"boolean"},
+ Format: "",
+ },
+ },
+ "availabilityZone": {
+ SchemaProps: spec.SchemaProps{
+ Description: "availabilityZone is the availability zone in which to create the load balancer.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "provider": {
+ SchemaProps: spec.SchemaProps{
+ Description: "provider is the name of the load balancer provider.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "vipAddress": {
+ SchemaProps: spec.SchemaProps{
+ Description: "vipAddress is the specific IP address to use for the VIP (optional). If not specified, one is allocated automatically from the subnet.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "tags": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "set",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "tags is a list of tags which will be applied to the load balancer.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "LoadBalancerResourceStatus represents the observed state of the resource.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "name": {
+ SchemaProps: spec.SchemaProps{
+ Description: "name is a Human-readable name for the resource. Might not be unique.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "description": {
+ SchemaProps: spec.SchemaProps{
+ Description: "description is a human-readable description for the resource.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "vipSubnetID": {
+ SchemaProps: spec.SchemaProps{
+ Description: "vipSubnetID is the ID of the Subnet to which the resource is associated.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "vipNetworkID": {
+ SchemaProps: spec.SchemaProps{
+ Description: "vipNetworkID is the ID of the Network to which the resource is associated.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "vipPortID": {
+ SchemaProps: spec.SchemaProps{
+ Description: "vipPortID is the ID of the Port to which the resource is associated.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "flavorID": {
+ SchemaProps: spec.SchemaProps{
+ Description: "flavorID is the ID of the Flavor to which the resource is associated.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "projectID": {
+ SchemaProps: spec.SchemaProps{
+ Description: "projectID is the ID of the Project to which the resource is associated.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "adminStateUp": {
+ SchemaProps: spec.SchemaProps{
+ Description: "adminStateUp is the administrative state of the load balancer, which is up (true) or down (false).",
+ Type: []string{"boolean"},
+ Format: "",
+ },
+ },
+ "tags": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "atomic",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "tags is the list of tags on the resource.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "availabilityZone": {
+ SchemaProps: spec.SchemaProps{
+ Description: "availabilityZone is the availability zone where the load balancer is located.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "provisioningStatus": {
+ SchemaProps: spec.SchemaProps{
+ Description: "provisioningStatus is the provisioning status of the load balancer. This value is ACTIVE, PENDING_CREATE or ERROR.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "operatingStatus": {
+ SchemaProps: spec.SchemaProps{
+ Description: "operatingStatus is the operating status of the load balancer, such as ONLINE or OFFLINE.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "provider": {
+ SchemaProps: spec.SchemaProps{
+ Description: "provider is the name of the load balancer provider.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "vipAddress": {
+ SchemaProps: spec.SchemaProps{
+ Description: "vipAddress is the IP address of the load balancer's VIP.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "LoadBalancerSpec defines the desired state of an ORC object.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "import": {
+ SchemaProps: spec.SchemaProps{
+ Description: "import refers to an existing OpenStack resource which will be imported instead of creating a new one.",
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerImport"),
+ },
+ },
+ "resource": {
+ SchemaProps: spec.SchemaProps{
+ Description: "resource specifies the desired state of the resource.\n\nresource may not be specified if the management policy is `unmanaged`.\n\nresource must be specified if the management policy is `managed`.",
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerResourceSpec"),
+ },
+ },
+ "managementPolicy": {
+ SchemaProps: spec.SchemaProps{
+ Description: "managementPolicy defines how ORC will treat the object. Valid values are `managed`: ORC will create, update, and delete the resource; `unmanaged`: ORC will import an existing resource, and will not apply updates to it or delete it.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "managedOptions": {
+ SchemaProps: spec.SchemaProps{
+ Description: "managedOptions specifies options which may be applied to managed objects.",
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"),
+ },
+ },
+ "cloudCredentialsRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials",
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference"),
+ },
+ },
+ },
+ Required: []string{"cloudCredentialsRef"},
+ },
+ },
+ Dependencies: []string{
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"},
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "LoadBalancerStatus defines the observed state of an ORC resource.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "conditions": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-map-keys": []interface{}{
+ "type",
+ },
+ "x-kubernetes-list-type": "map",
+ "x-kubernetes-patch-merge-key": "type",
+ "x-kubernetes-patch-strategy": "merge",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "conditions represents the observed status of the object. Known .status.conditions.type are: \"Available\", \"Progressing\"\n\nAvailable represents the availability of the OpenStack resource. If it is true then the resource is ready for use.\n\nProgressing indicates whether the controller is still attempting to reconcile the current state of the OpenStack resource to the desired state. Progressing will be False either because the desired state has been achieved, or because some terminal error prevents it from ever being achieved and the controller is no longer attempting to reconcile. If Progressing is True, an observer waiting on the resource should continue to wait.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Condition"),
+ },
+ },
+ },
+ },
+ },
+ "id": {
+ SchemaProps: spec.SchemaProps{
+ Description: "id is the unique identifier of the OpenStack resource.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "resource": {
+ SchemaProps: spec.SchemaProps{
+ Description: "resource contains the observed state of the OpenStack resource.",
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerResourceStatus"),
+ },
+ },
+ },
+ },
+ },
+ Dependencies: []string{
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LoadBalancerResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"},
+ }
+}
+
func schema_openstack_resource_controller_v2_api_v1alpha1_ManagedOptions(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go
index 6848b155f..442a37227 100644
--- a/cmd/resource-generator/main.go
+++ b/cmd/resource-generator/main.go
@@ -162,6 +162,12 @@ var resources []templateFields = []templateFields{
{
Name: "Group",
},
+ {
+ Name: "LoadBalancer",
+ },
+ {
+ Name: "Listener",
+ },
}
// These resources won't be generated
diff --git a/cmd/scaffold-controller/data/controller/actuator.go.template b/cmd/scaffold-controller/data/controller/actuator.go.template
index dc083e79d..472405919 100644
--- a/cmd/scaffold-controller/data/controller/actuator.go.template
+++ b/cmd/scaffold-controller/data/controller/actuator.go.template
@@ -25,9 +25,6 @@ import (
"{{ .GophercloudModule }}"
corev1 "k8s.io/api/core/v1"
-{{- if len .ImportDependencies }}
- apierrors "k8s.io/apimachinery/pkg/api/errors"
-{{- end }}
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -37,6 +34,9 @@ import (
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress"
"github.com/k-orc/openstack-resource-controller/v2/internal/logging"
"github.com/k-orc/openstack-resource-controller/v2/internal/osclients"
+{{- if len .ImportDependencies }}
+ "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency"
+{{- end }}
orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors"
)
@@ -106,41 +106,33 @@ func (actuator {{ .PackageName }}Actuator) ListOSResourcesForImport(ctx context.
var reconcileStatus progress.ReconcileStatus
{{- range .ImportDependencies }}
{{ $depNameCamelCase := . | camelCase }}
- {{ $depNameCamelCase }} := &orcv1alpha1.{{ . }}{}
- if filter.{{ . }}Ref != nil {
- {{ $depNameCamelCase }}Key := client.ObjectKey{Name: string(*filter.{{ . }}Ref), Namespace: obj.Namespace}
- if err := actuator.k8sClient.Get(ctx, {{ $depNameCamelCase }}Key, {{ $depNameCamelCase }}); err != nil {
- if apierrors.IsNotFound(err) {
- reconcileStatus = reconcileStatus.WithReconcileStatus(
- progress.WaitingOnObject("{{ . }}", {{ $depNameCamelCase }}Key.Name, progress.WaitingOnCreation))
- } else {
- reconcileStatus = reconcileStatus.WithReconcileStatus(
- progress.WrapError(fmt.Errorf("fetching {{ $depNameCamelCase }} %s: %w", {{ $depNameCamelCase }}Key.Name, err)))
- }
- } else {
- if !orcv1alpha1.IsAvailable({{ $depNameCamelCase }}) || {{ $depNameCamelCase }}.Status.ID == nil {
- reconcileStatus = reconcileStatus.WithReconcileStatus(
- progress.WaitingOnObject("{{ . }}", {{ $depNameCamelCase }}Key.Name, progress.WaitingOnReady))
- }
- }
- }
+ {{ $depNameCamelCase }}, rs := dependency.FetchDependency[*orcv1alpha1.{{ . }}, orcv1alpha1.{{ . }}](
+ ctx, actuator.k8sClient, obj.Namespace,
+ filter.{{ . }}Ref, "{{ . }}",
+ func(dep *orcv1alpha1.{{ . }}) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil },
+ )
+ reconcileStatus = reconcileStatus.WithReconcileStatus(rs)
{{- end }}
- if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
- return nil, reconcileStatus
+ var {{ range $i, $dep := .ImportDependencies }}{{ if $i }}, {{ end }}{{ $dep | camelCase }}ID{{ end }} string
+{{- range .ImportDependencies }}
+{{ $depNameCamelCase := . | camelCase }}
+ if {{ $depNameCamelCase }} != nil {
+ {{ $depNameCamelCase }}ID = ptr.Deref({{ $depNameCamelCase }}.Status.ID, "")
}
+{{- end }}
{{- end }}
listOpts := {{ .GophercloudPackage }}.ListOpts{
Name: string(ptr.Deref(filter.Name, "")),
Description: string(ptr.Deref(filter.Description, "")),
{{- range .ImportDependencies }}
- {{ . }}: ptr.Deref({{ . | camelCase }}.Status.ID, ""),
+ {{ . }}ID: {{ . | camelCase }}ID,
{{- end }}
// TODO(scaffolding): Add more import filters
}
- return actuator.osClient.List{{ .Kind }}s(ctx, listOpts), nil
+ return actuator.osClient.List{{ .Kind }}s(ctx, listOpts), {{ if len .ImportDependencies }}reconcileStatus{{ else }}nil{{ end }}
}
func (actuator {{ .PackageName }}Actuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) {
diff --git a/config/crd/bases/openstack.k-orc.cloud_listeners.yaml b/config/crd/bases/openstack.k-orc.cloud_listeners.yaml
new file mode 100644
index 000000000..75bb29f0c
--- /dev/null
+++ b/config/crd/bases/openstack.k-orc.cloud_listeners.yaml
@@ -0,0 +1,632 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.17.1
+ name: listeners.openstack.k-orc.cloud
+spec:
+ group: openstack.k-orc.cloud
+ names:
+ categories:
+ - openstack
+ kind: Listener
+ listKind: ListenerList
+ plural: listeners
+ singular: listener
+ scope: Namespaced
+ versions:
+ - additionalPrinterColumns:
+ - description: Resource ID
+ jsonPath: .status.id
+ name: ID
+ type: string
+ - description: Availability status of resource
+ jsonPath: .status.conditions[?(@.type=='Available')].status
+ name: Available
+ type: string
+ - description: Message describing current progress status
+ jsonPath: .status.conditions[?(@.type=='Progressing')].message
+ name: Message
+ type: string
+ name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ description: Listener is the Schema for an ORC resource.
+ 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: spec specifies the desired state of the resource.
+ properties:
+ cloudCredentialsRef:
+ description: cloudCredentialsRef points to a secret containing OpenStack
+ credentials
+ properties:
+ cloudName:
+ description: cloudName specifies the name of the entry in the
+ clouds.yaml file to use.
+ maxLength: 256
+ minLength: 1
+ type: string
+ secretName:
+ description: |-
+ secretName is the name of a secret in the same namespace as the resource being provisioned.
+ The secret must contain a key named `clouds.yaml` which contains an OpenStack clouds.yaml file.
+ The secret may optionally contain a key named `cacert` containing a PEM-encoded CA certificate.
+ maxLength: 253
+ minLength: 1
+ type: string
+ required:
+ - cloudName
+ - secretName
+ type: object
+ import:
+ description: |-
+ import refers to an existing OpenStack resource which will be imported instead of
+ creating a new one.
+ maxProperties: 1
+ minProperties: 1
+ properties:
+ filter:
+ description: |-
+ filter contains a resource query which is expected to return a single
+ result. The controller will continue to retry if filter returns no
+ results. If filter returns multiple results the controller will set an
+ error state and will not continue to retry.
+ minProperties: 1
+ properties:
+ description:
+ description: description of the existing resource.
+ maxLength: 255
+ minLength: 1
+ type: string
+ loadBalancerRef:
+ description: loadBalancerRef filters by the LoadBalancer this
+ listener belongs to.
+ maxLength: 253
+ minLength: 1
+ type: string
+ name:
+ description: name of the existing resource.
+ maxLength: 255
+ minLength: 1
+ pattern: ^[^,]+$
+ type: string
+ notTags:
+ description: |-
+ notTags is a list of tags to filter by. If specified, resources which
+ contain all of the given tags will be excluded from the result.
+ items:
+ maxLength: 255
+ minLength: 1
+ type: string
+ maxItems: 64
+ type: array
+ x-kubernetes-list-type: set
+ notTagsAny:
+ description: |-
+ notTagsAny is a list of tags to filter by. If specified, resources
+ which contain any of the given tags will be excluded from the result.
+ items:
+ maxLength: 255
+ minLength: 1
+ type: string
+ maxItems: 64
+ type: array
+ x-kubernetes-list-type: set
+ protocol:
+ description: protocol filters by the protocol used by the
+ listener.
+ enum:
+ - HTTP
+ - HTTPS
+ - SCTP
+ - PROMETHEUS
+ - TCP
+ - TERMINATED_HTTPS
+ - UDP
+ type: string
+ protocolPort:
+ description: protocolPort filters by the port used by the
+ listener.
+ format: int32
+ maximum: 65535
+ minimum: 1
+ type: integer
+ tags:
+ description: |-
+ tags is a list of tags to filter by. If specified, the resource must
+ have all of the tags specified to be included in the result.
+ items:
+ maxLength: 255
+ minLength: 1
+ type: string
+ maxItems: 64
+ type: array
+ x-kubernetes-list-type: set
+ tagsAny:
+ description: |-
+ tagsAny is a list of tags to filter by. If specified, the resource
+ must have at least one of the tags specified to be included in the
+ result.
+ items:
+ maxLength: 255
+ minLength: 1
+ type: string
+ maxItems: 64
+ type: array
+ x-kubernetes-list-type: set
+ type: object
+ id:
+ description: |-
+ id contains the unique identifier of an existing OpenStack resource. Note
+ that when specifying an import by ID, the resource MUST already exist.
+ The ORC object will enter an error state if the resource does not exist.
+ format: uuid
+ type: string
+ type: object
+ managedOptions:
+ description: managedOptions specifies options which may be applied
+ to managed objects.
+ properties:
+ onDelete:
+ default: delete
+ description: |-
+ onDelete specifies the behaviour of the controller when the ORC
+ object is deleted. Options are `delete` - delete the OpenStack resource;
+ `detach` - do not delete the OpenStack resource. If not specified, the
+ default is `delete`.
+ enum:
+ - delete
+ - detach
+ type: string
+ type: object
+ managementPolicy:
+ default: managed
+ description: |-
+ managementPolicy defines how ORC will treat the object. Valid values are
+ `managed`: ORC will create, update, and delete the resource; `unmanaged`:
+ ORC will import an existing resource, and will not apply updates to it or
+ delete it.
+ enum:
+ - managed
+ - unmanaged
+ type: string
+ x-kubernetes-validations:
+ - message: managementPolicy is immutable
+ rule: self == oldSelf
+ resource:
+ description: |-
+ resource specifies the desired state of the resource.
+
+ resource may not be specified if the management policy is `unmanaged`.
+
+ resource must be specified if the management policy is `managed`.
+ properties:
+ adminStateUp:
+ description: adminStateUp is the administrative state of the listener,
+ which is up (true) or down (false).
+ type: boolean
+ allowedCIDRs:
+ description: allowedCIDRs is a list of IPv4/IPv6 CIDRs that are
+ permitted to connect to this listener.
+ items:
+ maxLength: 64
+ type: string
+ maxItems: 256
+ type: array
+ x-kubernetes-list-type: set
+ alpnProtocols:
+ description: alpnProtocols is a list of ALPN protocols for TLS-enabled
+ listeners.
+ items:
+ maxLength: 32
+ type: string
+ maxItems: 10
+ type: array
+ x-kubernetes-list-type: set
+ clientAuthentication:
+ description: clientAuthentication is the TLS client authentication
+ mode.
+ enum:
+ - NONE
+ - OPTIONAL
+ - MANDATORY
+ type: string
+ x-kubernetes-validations:
+ - message: clientAuthentication is immutable
+ rule: self == oldSelf
+ clientCATLSContainerRef:
+ description: |-
+ clientCATLSContainerRef is a reference to a secret containing the CA certificate
+ for client authentication.
+ maxLength: 255
+ type: string
+ x-kubernetes-validations:
+ - message: clientCATLSContainerRef is immutable
+ rule: self == oldSelf
+ clientCRLContainerRef:
+ description: |-
+ clientCRLContainerRef is a reference to a secret containing the CA revocation list
+ for client authentication.
+ maxLength: 255
+ type: string
+ x-kubernetes-validations:
+ - message: clientCRLContainerRef is immutable
+ rule: self == oldSelf
+ connectionLimit:
+ description: |-
+ connectionLimit is the maximum number of connections permitted for this listener.
+ Default value is -1 which represents infinite connections.
+ format: int32
+ minimum: -1
+ type: integer
+ defaultPoolRef:
+ description: defaultPoolRef is a reference to the default Pool
+ for this listener.
+ maxLength: 253
+ minLength: 1
+ type: string
+ x-kubernetes-validations:
+ - message: defaultPoolRef is immutable
+ rule: self == oldSelf
+ defaultTLSContainerRef:
+ description: |-
+ defaultTLSContainerRef is a reference to a secret containing a PKCS12 format
+ certificate/key bundle for TERMINATED_HTTPS listeners.
+ maxLength: 255
+ type: string
+ x-kubernetes-validations:
+ - message: defaultTLSContainerRef is immutable
+ rule: self == oldSelf
+ description:
+ description: description is a human-readable description for the
+ resource.
+ maxLength: 255
+ minLength: 1
+ type: string
+ hsts:
+ description: hsts is the HTTP Strict Transport Security configuration.
+ properties:
+ includeSubDomains:
+ description: includeSubDomains specifies whether this rule
+ applies to all subdomains.
+ type: boolean
+ maxAge:
+ description: |-
+ maxAge is the maximum time in seconds that the browser should remember
+ that this site is only to be accessed using HTTPS.
+ format: int32
+ minimum: 0
+ type: integer
+ preload:
+ description: preload specifies whether the domain should be
+ included in browsers' preload list.
+ type: boolean
+ type: object
+ insertHeaders:
+ additionalProperties:
+ type: string
+ description: |-
+ insertHeaders is a dictionary of optional headers to insert into the request
+ before it is sent to the backend member.
+ type: object
+ loadBalancerRef:
+ description: loadBalancerRef is a reference to the LoadBalancer
+ this listener belongs to.
+ maxLength: 253
+ minLength: 1
+ type: string
+ x-kubernetes-validations:
+ - message: loadBalancerRef is immutable
+ rule: self == oldSelf
+ name:
+ description: |-
+ name will be the name of the created resource. If not specified, the
+ name of the ORC object will be used.
+ maxLength: 255
+ minLength: 1
+ pattern: ^[^,]+$
+ type: string
+ protocol:
+ description: protocol is the protocol the listener will use.
+ enum:
+ - HTTP
+ - HTTPS
+ - SCTP
+ - PROMETHEUS
+ - TCP
+ - TERMINATED_HTTPS
+ - UDP
+ type: string
+ x-kubernetes-validations:
+ - message: protocol is immutable
+ rule: self == oldSelf
+ protocolPort:
+ description: protocolPort is the port on which the listener will
+ accept connections.
+ format: int32
+ maximum: 65535
+ minimum: 1
+ type: integer
+ x-kubernetes-validations:
+ - message: protocolPort is immutable
+ rule: self == oldSelf
+ sniContainerRefs:
+ description: |-
+ sniContainerRefs is a list of references to secrets containing PKCS12 format
+ certificate/key bundles for TERMINATED_HTTPS listeners using SNI.
+ items:
+ maxLength: 255
+ type: string
+ maxItems: 25
+ type: array
+ x-kubernetes-list-type: set
+ tags:
+ description: tags is a list of tags which will be applied to the
+ listener.
+ items:
+ maxLength: 255
+ minLength: 1
+ type: string
+ maxItems: 64
+ type: array
+ x-kubernetes-list-type: set
+ timeoutClientData:
+ description: timeoutClientData is the frontend client inactivity
+ timeout in milliseconds.
+ format: int32
+ minimum: 0
+ type: integer
+ timeoutMemberConnect:
+ description: timeoutMemberConnect is the backend member connection
+ timeout in milliseconds.
+ format: int32
+ minimum: 0
+ type: integer
+ timeoutMemberData:
+ description: timeoutMemberData is the backend member inactivity
+ timeout in milliseconds.
+ format: int32
+ minimum: 0
+ type: integer
+ timeoutTCPInspect:
+ description: |-
+ timeoutTCPInspect is the time in milliseconds to wait for additional TCP packets
+ for content inspection.
+ format: int32
+ minimum: 0
+ type: integer
+ tlsCiphers:
+ description: tlsCiphers is a colon-separated list of ciphers for
+ TLS-terminated listeners.
+ maxLength: 2048
+ type: string
+ tlsVersions:
+ description: tlsVersions is a list of TLS protocol versions to
+ be used by the listener.
+ items:
+ maxLength: 32
+ type: string
+ maxItems: 10
+ type: array
+ x-kubernetes-list-type: set
+ required:
+ - loadBalancerRef
+ - protocol
+ - protocolPort
+ type: object
+ required:
+ - cloudCredentialsRef
+ type: object
+ x-kubernetes-validations:
+ - message: resource must be specified when policy is managed
+ rule: 'self.managementPolicy == ''managed'' ? has(self.resource) : true'
+ - message: import may not be specified when policy is managed
+ rule: 'self.managementPolicy == ''managed'' ? !has(self.__import__)
+ : true'
+ - message: resource may not be specified when policy is unmanaged
+ rule: 'self.managementPolicy == ''unmanaged'' ? !has(self.resource)
+ : true'
+ - message: import must be specified when policy is unmanaged
+ rule: 'self.managementPolicy == ''unmanaged'' ? has(self.__import__)
+ : true'
+ - message: managedOptions may only be provided when policy is managed
+ rule: 'has(self.managedOptions) ? self.managementPolicy == ''managed''
+ : true'
+ status:
+ description: status defines the observed state of the resource.
+ properties:
+ conditions:
+ description: |-
+ conditions represents the observed status of the object.
+ Known .status.conditions.type are: "Available", "Progressing"
+
+ Available represents the availability of the OpenStack resource. If it is
+ true then the resource is ready for use.
+
+ Progressing indicates whether the controller is still attempting to
+ reconcile the current state of the OpenStack resource to the desired
+ state. Progressing will be False either because the desired state has
+ been achieved, or because some terminal error prevents it from ever being
+ achieved and the controller is no longer attempting to reconcile. If
+ Progressing is True, an observer waiting on the resource should continue
+ to wait.
+ items:
+ description: Condition contains details for one aspect of the current
+ state of this API Resource.
+ 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.
+ 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
+ maxItems: 32
+ type: array
+ x-kubernetes-list-map-keys:
+ - type
+ x-kubernetes-list-type: map
+ id:
+ description: id is the unique identifier of the OpenStack resource.
+ type: string
+ resource:
+ description: resource contains the observed state of the OpenStack
+ resource.
+ properties:
+ adminStateUp:
+ description: |-
+ adminStateUp is the administrative state of the listener,
+ which is up (true) or down (false).
+ type: boolean
+ allowedCIDRs:
+ description: allowedCIDRs is the list of CIDRs permitted to connect
+ to this listener.
+ items:
+ maxLength: 64
+ type: string
+ maxItems: 256
+ type: array
+ x-kubernetes-list-type: atomic
+ connectionLimit:
+ description: connectionLimit is the maximum number of connections
+ permitted for this listener.
+ format: int32
+ type: integer
+ defaultPoolID:
+ description: defaultPoolID is the ID of the default pool for this
+ listener.
+ maxLength: 1024
+ type: string
+ description:
+ description: description is a human-readable description for the
+ resource.
+ maxLength: 1024
+ type: string
+ insertHeaders:
+ additionalProperties:
+ type: string
+ description: insertHeaders is a dictionary of headers inserted
+ into the request.
+ type: object
+ loadBalancerID:
+ description: loadBalancerID is the ID of the LoadBalancer this
+ listener belongs to.
+ maxLength: 1024
+ type: string
+ name:
+ description: name is a human-readable name for the resource.
+ maxLength: 1024
+ type: string
+ operatingStatus:
+ description: operatingStatus is the operating status of the listener.
+ maxLength: 1024
+ type: string
+ protocol:
+ description: protocol is the protocol used by the listener.
+ maxLength: 64
+ type: string
+ protocolPort:
+ description: protocolPort is the port used by the listener.
+ format: int32
+ type: integer
+ provisioningStatus:
+ description: provisioningStatus is the provisioning status of
+ the listener.
+ maxLength: 1024
+ type: string
+ tags:
+ description: tags is the list of tags on the resource.
+ items:
+ maxLength: 255
+ type: string
+ maxItems: 64
+ type: array
+ x-kubernetes-list-type: atomic
+ timeoutClientData:
+ description: timeoutClientData is the frontend client inactivity
+ timeout in milliseconds.
+ format: int32
+ type: integer
+ timeoutMemberConnect:
+ description: timeoutMemberConnect is the backend member connection
+ timeout in milliseconds.
+ format: int32
+ type: integer
+ timeoutMemberData:
+ description: timeoutMemberData is the backend member inactivity
+ timeout in milliseconds.
+ format: int32
+ type: integer
+ timeoutTCPInspect:
+ description: timeoutTCPInspect is the time to wait for additional
+ TCP packets in milliseconds.
+ format: int32
+ type: integer
+ type: object
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
diff --git a/config/crd/bases/openstack.k-orc.cloud_loadbalancers.yaml b/config/crd/bases/openstack.k-orc.cloud_loadbalancers.yaml
new file mode 100644
index 000000000..08f8470e2
--- /dev/null
+++ b/config/crd/bases/openstack.k-orc.cloud_loadbalancers.yaml
@@ -0,0 +1,524 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.17.1
+ name: loadbalancers.openstack.k-orc.cloud
+spec:
+ group: openstack.k-orc.cloud
+ names:
+ categories:
+ - openstack
+ kind: LoadBalancer
+ listKind: LoadBalancerList
+ plural: loadbalancers
+ singular: loadbalancer
+ scope: Namespaced
+ versions:
+ - additionalPrinterColumns:
+ - description: Resource ID
+ jsonPath: .status.id
+ name: ID
+ type: string
+ - description: Availability status of resource
+ jsonPath: .status.conditions[?(@.type=='Available')].status
+ name: Available
+ type: string
+ - description: Message describing current progress status
+ jsonPath: .status.conditions[?(@.type=='Progressing')].message
+ name: Message
+ type: string
+ name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ description: LoadBalancer is the Schema for an ORC resource.
+ 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: spec specifies the desired state of the resource.
+ properties:
+ cloudCredentialsRef:
+ description: cloudCredentialsRef points to a secret containing OpenStack
+ credentials
+ properties:
+ cloudName:
+ description: cloudName specifies the name of the entry in the
+ clouds.yaml file to use.
+ maxLength: 256
+ minLength: 1
+ type: string
+ secretName:
+ description: |-
+ secretName is the name of a secret in the same namespace as the resource being provisioned.
+ The secret must contain a key named `clouds.yaml` which contains an OpenStack clouds.yaml file.
+ The secret may optionally contain a key named `cacert` containing a PEM-encoded CA certificate.
+ maxLength: 253
+ minLength: 1
+ type: string
+ required:
+ - cloudName
+ - secretName
+ type: object
+ import:
+ description: |-
+ import refers to an existing OpenStack resource which will be imported instead of
+ creating a new one.
+ maxProperties: 1
+ minProperties: 1
+ properties:
+ filter:
+ description: |-
+ filter contains a resource query which is expected to return a single
+ result. The controller will continue to retry if filter returns no
+ results. If filter returns multiple results the controller will set an
+ error state and will not continue to retry.
+ minProperties: 1
+ properties:
+ availabilityZone:
+ description: availabilityZone is the availability zone in
+ which to create the load balancer.
+ maxLength: 255
+ type: string
+ description:
+ description: description of the existing resource
+ maxLength: 255
+ minLength: 1
+ type: string
+ name:
+ description: name of the existing resource
+ maxLength: 255
+ minLength: 1
+ pattern: ^[^,]+$
+ type: string
+ notTags:
+ description: |-
+ notTags is a list of tags to filter by. If specified, resources which
+ contain all of the given tags will be excluded from the result.
+ items:
+ maxLength: 255
+ minLength: 1
+ type: string
+ maxItems: 64
+ type: array
+ x-kubernetes-list-type: set
+ notTagsAny:
+ description: |-
+ notTagsAny is a list of tags to filter by. If specified, resources
+ which contain any of the given tags will be excluded from the result.
+ items:
+ maxLength: 255
+ minLength: 1
+ type: string
+ maxItems: 64
+ type: array
+ x-kubernetes-list-type: set
+ projectRef:
+ description: |-
+ projectRef is a reference to the ORC Project this resource is associated with.
+ Typically, only used by admin.
+ maxLength: 253
+ minLength: 1
+ type: string
+ provider:
+ description: provider filters by the name of the load balancer
+ provider.
+ maxLength: 255
+ type: string
+ tags:
+ description: |-
+ tags is a list of tags to filter by. If specified, the resource must
+ have all of the tags specified to be included in the result.
+ items:
+ maxLength: 255
+ minLength: 1
+ type: string
+ maxItems: 64
+ type: array
+ x-kubernetes-list-type: set
+ tagsAny:
+ description: |-
+ tagsAny is a list of tags to filter by. If specified, the resource
+ must have at least one of the tags specified to be included in the
+ result.
+ items:
+ maxLength: 255
+ minLength: 1
+ type: string
+ maxItems: 64
+ type: array
+ x-kubernetes-list-type: set
+ vipAddress:
+ description: vipAddress filters by the IP address of the load
+ balancer's VIP.
+ maxLength: 64
+ type: string
+ vipNetworkRef:
+ description: vipNetworkRef filters by the network on which
+ the load balancer's address is allocated.
+ maxLength: 253
+ minLength: 1
+ type: string
+ vipPortRef:
+ description: vipPortRef filters by the neutron port used for
+ the VIP.
+ maxLength: 253
+ minLength: 1
+ type: string
+ vipSubnetRef:
+ description: vipSubnetRef filters by the subnet on which the
+ load balancer's address is allocated.
+ maxLength: 253
+ minLength: 1
+ type: string
+ type: object
+ id:
+ description: |-
+ id contains the unique identifier of an existing OpenStack resource. Note
+ that when specifying an import by ID, the resource MUST already exist.
+ The ORC object will enter an error state if the resource does not exist.
+ format: uuid
+ type: string
+ type: object
+ managedOptions:
+ description: managedOptions specifies options which may be applied
+ to managed objects.
+ properties:
+ onDelete:
+ default: delete
+ description: |-
+ onDelete specifies the behaviour of the controller when the ORC
+ object is deleted. Options are `delete` - delete the OpenStack resource;
+ `detach` - do not delete the OpenStack resource. If not specified, the
+ default is `delete`.
+ enum:
+ - delete
+ - detach
+ type: string
+ type: object
+ managementPolicy:
+ default: managed
+ description: |-
+ managementPolicy defines how ORC will treat the object. Valid values are
+ `managed`: ORC will create, update, and delete the resource; `unmanaged`:
+ ORC will import an existing resource, and will not apply updates to it or
+ delete it.
+ enum:
+ - managed
+ - unmanaged
+ type: string
+ x-kubernetes-validations:
+ - message: managementPolicy is immutable
+ rule: self == oldSelf
+ resource:
+ description: |-
+ resource specifies the desired state of the resource.
+
+ resource may not be specified if the management policy is `unmanaged`.
+
+ resource must be specified if the management policy is `managed`.
+ properties:
+ adminStateUp:
+ description: adminStateUp is the administrative state of the load
+ balancer, which is up (true) or down (false)
+ type: boolean
+ availabilityZone:
+ description: availabilityZone is the availability zone in which
+ to create the load balancer.
+ maxLength: 255
+ type: string
+ x-kubernetes-validations:
+ - message: availabilityZone is immutable
+ rule: self == oldSelf
+ description:
+ description: description is a human-readable description for the
+ resource.
+ maxLength: 255
+ minLength: 1
+ type: string
+ flavorRef:
+ description: flavorRef is a reference to the ORC Flavor which
+ this resource is associated with.
+ maxLength: 253
+ minLength: 1
+ type: string
+ x-kubernetes-validations:
+ - message: flavorRef is immutable
+ rule: self == oldSelf
+ name:
+ description: |-
+ name will be the name of the created resource. If not specified, the
+ name of the ORC object will be used.
+ maxLength: 255
+ minLength: 1
+ pattern: ^[^,]+$
+ type: string
+ projectRef:
+ description: projectRef is a reference to the ORC Project which
+ this resource is associated with.
+ maxLength: 253
+ minLength: 1
+ type: string
+ x-kubernetes-validations:
+ - message: projectRef is immutable
+ rule: self == oldSelf
+ provider:
+ description: provider is the name of the load balancer provider.
+ maxLength: 255
+ type: string
+ x-kubernetes-validations:
+ - message: provider is immutable
+ rule: self == oldSelf
+ tags:
+ description: tags is a list of tags which will be applied to the
+ load balancer.
+ items:
+ maxLength: 255
+ minLength: 1
+ type: string
+ maxItems: 64
+ type: array
+ x-kubernetes-list-type: set
+ vipAddress:
+ description: |-
+ vipAddress is the specific IP address to use for the VIP (optional).
+ If not specified, one is allocated automatically from the subnet.
+ maxLength: 45
+ minLength: 1
+ type: string
+ x-kubernetes-validations:
+ - message: vipAddress is immutable
+ rule: self == oldSelf
+ vipNetworkRef:
+ description: vipNetworkRef is the network on which to allocate
+ the load balancer's address.
+ maxLength: 253
+ minLength: 1
+ type: string
+ x-kubernetes-validations:
+ - message: vipNetworkRef is immutable
+ rule: self == oldSelf
+ vipPortRef:
+ description: |-
+ vipPortRef is a reference to a neutron port to use for the VIP. If the port
+ has more than one subnet you must specify either vipSubnetRef or vipAddress
+ to clarify which address should be used for the VIP.
+ maxLength: 253
+ minLength: 1
+ type: string
+ x-kubernetes-validations:
+ - message: vipPortRef is immutable
+ rule: self == oldSelf
+ vipSubnetRef:
+ description: vipSubnetRef is the subnet on which to allocate the
+ load balancer's address.
+ maxLength: 253
+ minLength: 1
+ type: string
+ x-kubernetes-validations:
+ - message: vipSubnetRef is immutable
+ rule: self == oldSelf
+ type: object
+ x-kubernetes-validations:
+ - message: at least one of vipSubnetRef, vipNetworkRef, or vipPortRef
+ must be specified
+ rule: has(self.vipSubnetRef) || has(self.vipNetworkRef) || has(self.vipPortRef)
+ required:
+ - cloudCredentialsRef
+ type: object
+ x-kubernetes-validations:
+ - message: resource must be specified when policy is managed
+ rule: 'self.managementPolicy == ''managed'' ? has(self.resource) : true'
+ - message: import may not be specified when policy is managed
+ rule: 'self.managementPolicy == ''managed'' ? !has(self.__import__)
+ : true'
+ - message: resource may not be specified when policy is unmanaged
+ rule: 'self.managementPolicy == ''unmanaged'' ? !has(self.resource)
+ : true'
+ - message: import must be specified when policy is unmanaged
+ rule: 'self.managementPolicy == ''unmanaged'' ? has(self.__import__)
+ : true'
+ - message: managedOptions may only be provided when policy is managed
+ rule: 'has(self.managedOptions) ? self.managementPolicy == ''managed''
+ : true'
+ status:
+ description: status defines the observed state of the resource.
+ properties:
+ conditions:
+ description: |-
+ conditions represents the observed status of the object.
+ Known .status.conditions.type are: "Available", "Progressing"
+
+ Available represents the availability of the OpenStack resource. If it is
+ true then the resource is ready for use.
+
+ Progressing indicates whether the controller is still attempting to
+ reconcile the current state of the OpenStack resource to the desired
+ state. Progressing will be False either because the desired state has
+ been achieved, or because some terminal error prevents it from ever being
+ achieved and the controller is no longer attempting to reconcile. If
+ Progressing is True, an observer waiting on the resource should continue
+ to wait.
+ items:
+ description: Condition contains details for one aspect of the current
+ state of this API Resource.
+ 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.
+ 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
+ maxItems: 32
+ type: array
+ x-kubernetes-list-map-keys:
+ - type
+ x-kubernetes-list-type: map
+ id:
+ description: id is the unique identifier of the OpenStack resource.
+ type: string
+ resource:
+ description: resource contains the observed state of the OpenStack
+ resource.
+ properties:
+ adminStateUp:
+ description: |-
+ adminStateUp is the administrative state of the load balancer,
+ which is up (true) or down (false).
+ type: boolean
+ availabilityZone:
+ description: availabilityZone is the availability zone where the
+ load balancer is located.
+ maxLength: 1024
+ type: string
+ description:
+ description: description is a human-readable description for the
+ resource.
+ maxLength: 1024
+ type: string
+ flavorID:
+ description: flavorID is the ID of the Flavor to which the resource
+ is associated.
+ maxLength: 1024
+ type: string
+ name:
+ description: name is a Human-readable name for the resource. Might
+ not be unique.
+ maxLength: 1024
+ type: string
+ operatingStatus:
+ description: |-
+ operatingStatus is the operating status of the load balancer,
+ such as ONLINE or OFFLINE.
+ maxLength: 1024
+ type: string
+ projectID:
+ description: projectID is the ID of the Project to which the resource
+ is associated.
+ maxLength: 1024
+ type: string
+ provider:
+ description: provider is the name of the load balancer provider.
+ maxLength: 1024
+ type: string
+ provisioningStatus:
+ description: |-
+ provisioningStatus is the provisioning status of the load balancer.
+ This value is ACTIVE, PENDING_CREATE or ERROR.
+ maxLength: 1024
+ type: string
+ tags:
+ description: tags is the list of tags on the resource.
+ items:
+ maxLength: 255
+ type: string
+ maxItems: 64
+ type: array
+ x-kubernetes-list-type: atomic
+ vipAddress:
+ description: vipAddress is the IP address of the load balancer's
+ VIP.
+ maxLength: 64
+ type: string
+ vipNetworkID:
+ description: vipNetworkID is the ID of the Network to which the
+ resource is associated.
+ maxLength: 1024
+ type: string
+ vipPortID:
+ description: vipPortID is the ID of the Port to which the resource
+ is associated.
+ maxLength: 1024
+ type: string
+ vipSubnetID:
+ description: vipSubnetID is the ID of the Subnet to which the
+ resource is associated.
+ maxLength: 1024
+ type: string
+ type: object
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml
index 33b8c85e2..bc9e50c1d 100644
--- a/config/crd/kustomization.yaml
+++ b/config/crd/kustomization.yaml
@@ -9,6 +9,8 @@ resources:
- bases/openstack.k-orc.cloud_groups.yaml
- bases/openstack.k-orc.cloud_images.yaml
- bases/openstack.k-orc.cloud_keypairs.yaml
+- bases/openstack.k-orc.cloud_listeners.yaml
+- bases/openstack.k-orc.cloud_loadbalancers.yaml
- bases/openstack.k-orc.cloud_networks.yaml
- bases/openstack.k-orc.cloud_ports.yaml
- bases/openstack.k-orc.cloud_projects.yaml
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index 5a0a7443b..e4afe2e80 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -23,6 +23,8 @@ rules:
- groups
- images
- keypairs
+ - listeners
+ - loadbalancers
- networks
- ports
- projects
@@ -53,6 +55,8 @@ rules:
- groups/status
- images/status
- keypairs/status
+ - listeners/status
+ - loadbalancers/status
- networks/status
- ports/status
- projects/status
diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml
index dac467c69..085af8a7d 100644
--- a/config/samples/kustomization.yaml
+++ b/config/samples/kustomization.yaml
@@ -7,6 +7,8 @@ resources:
- openstack_v1alpha1_group.yaml
- openstack_v1alpha1_image.yaml
- openstack_v1alpha1_keypair.yaml
+- openstack_v1alpha1_listener.yaml
+- openstack_v1alpha1_loadbalancer.yaml
- openstack_v1alpha1_network.yaml
- openstack_v1alpha1_port.yaml
- openstack_v1alpha1_project.yaml
diff --git a/config/samples/openstack_v1alpha1_listener.yaml b/config/samples/openstack_v1alpha1_listener.yaml
new file mode 100644
index 000000000..f3c7081ea
--- /dev/null
+++ b/config/samples/openstack_v1alpha1_listener.yaml
@@ -0,0 +1,17 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-sample
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ name: my-listener
+ description: Sample HTTP Listener
+ loadBalancerRef: loadbalancer-sample
+ protocol: HTTP
+ protocolPort: 80
+ adminStateUp: true
diff --git a/config/samples/openstack_v1alpha1_loadbalancer.yaml b/config/samples/openstack_v1alpha1_loadbalancer.yaml
new file mode 100644
index 000000000..9510e2d0f
--- /dev/null
+++ b/config/samples/openstack_v1alpha1_loadbalancer.yaml
@@ -0,0 +1,24 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-sample
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ name: my-loadbalancer
+ description: Sample LoadBalancer
+ vipSubnetRef: loadbalancer-sample-subnet
+ vipNetworkRef: loadbalancer-sample-network
+ vipPortRef: loadbalancer-sample-port
+ vipAddress: 10.0.0.100
+ projectRef: loadbalancer-sample-project
+ adminStateUp: true
+ availabilityZone: nova
+ provider: amphora
+ tags:
+ - environment:production
+ - team:platform
diff --git a/internal/controllers/listener/actuator.go b/internal/controllers/listener/actuator.go
new file mode 100644
index 000000000..adae49d89
--- /dev/null
+++ b/internal/controllers/listener/actuator.go
@@ -0,0 +1,312 @@
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package listener
+
+import (
+ "context"
+ "iter"
+ "time"
+
+ "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/listeners"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/utils/ptr"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/logging"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/osclients"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency"
+ orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors"
+)
+
+// OpenStack resource types
+type (
+ osResourceT = listeners.Listener
+
+ createResourceActuator = interfaces.CreateResourceActuator[orcObjectPT, orcObjectT, filterT, osResourceT]
+ deleteResourceActuator = interfaces.DeleteResourceActuator[orcObjectPT, orcObjectT, osResourceT]
+ resourceReconciler = interfaces.ResourceReconciler[orcObjectPT, osResourceT]
+ helperFactory = interfaces.ResourceHelperFactory[orcObjectPT, orcObjectT, resourceSpecT, filterT, osResourceT]
+)
+
+// The frequency to poll when waiting for the resource to become available
+const listenerAvailablePollingPeriod = 15 * time.Second
+
+// The frequency to poll when waiting for the resource to be deleted
+const listenerDeletingPollingPeriod = 15 * time.Second
+
+type listenerActuator struct {
+ osClient osclients.ListenerClient
+ k8sClient client.Client
+}
+
+var _ createResourceActuator = listenerActuator{}
+var _ deleteResourceActuator = listenerActuator{}
+
+func (listenerActuator) GetResourceID(osResource *osResourceT) string {
+ return osResource.ID
+}
+
+func (actuator listenerActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) {
+ resource, err := actuator.osClient.GetListener(ctx, id)
+ if err != nil {
+ return nil, progress.WrapError(err)
+ }
+ return resource, nil
+}
+
+func (actuator listenerActuator) ListOSResourcesForAdoption(ctx context.Context, orcObject orcObjectPT) (iter.Seq2[*osResourceT, error], bool) {
+ resourceSpec := orcObject.Spec.Resource
+ if resourceSpec == nil {
+ return nil, false
+ }
+
+ listOpts := listeners.ListOpts{
+ Name: getResourceName(orcObject),
+ }
+
+ return actuator.osClient.ListListeners(ctx, listOpts), true
+}
+
+func (actuator listenerActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) {
+ var reconcileStatus progress.ReconcileStatus
+
+ loadBalancer, rs := dependency.FetchDependency[*orcv1alpha1.LoadBalancer, orcv1alpha1.LoadBalancer](
+ ctx, actuator.k8sClient, obj.Namespace,
+ filter.LoadBalancerRef, "LoadBalancer",
+ func(lb *orcv1alpha1.LoadBalancer) bool { return orcv1alpha1.IsAvailable(lb) && lb.Status.ID != nil },
+ )
+ reconcileStatus = reconcileStatus.WithReconcileStatus(rs)
+
+ var loadBalancerID string
+ if loadBalancer != nil {
+ loadBalancerID = ptr.Deref(loadBalancer.Status.ID, "")
+ }
+
+ listOpts := listeners.ListOpts{
+ Name: string(ptr.Deref(filter.Name, "")),
+ LoadbalancerID: loadBalancerID,
+ }
+
+ return actuator.osClient.ListListeners(ctx, listOpts), reconcileStatus
+}
+
+func (actuator listenerActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) {
+ resource := obj.Spec.Resource
+
+ if resource == nil {
+ // Should have been caught by API validation
+ return nil, progress.WrapError(
+ orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Creation requested, but spec.resource is not set"))
+ }
+ var reconcileStatus progress.ReconcileStatus
+
+ var loadBalancerID string
+ loadBalancer, loadBalancerDepRS := loadBalancerDependency.GetDependency(
+ ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.LoadBalancer) bool {
+ return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
+ },
+ )
+ reconcileStatus = reconcileStatus.WithReconcileStatus(loadBalancerDepRS)
+ if loadBalancer != nil {
+ loadBalancerID = ptr.Deref(loadBalancer.Status.ID, "")
+ }
+
+ if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
+ return nil, reconcileStatus
+ }
+
+ createOpts := listeners.CreateOpts{
+ Name: getResourceName(obj),
+ Description: ptr.Deref(resource.Description, ""),
+ LoadbalancerID: loadBalancerID,
+ Protocol: listeners.Protocol(resource.Protocol),
+ ProtocolPort: int(resource.ProtocolPort),
+ AdminStateUp: resource.AdminStateUp,
+ DefaultTlsContainerRef: ptr.Deref(resource.DefaultTLSContainerRef, ""),
+ }
+
+ if resource.ConnectionLimit != nil {
+ connLimit := int(*resource.ConnectionLimit)
+ createOpts.ConnLimit = &connLimit
+ }
+ if resource.TimeoutClientData != nil {
+ timeout := int(*resource.TimeoutClientData)
+ createOpts.TimeoutClientData = &timeout
+ }
+ if resource.TimeoutMemberConnect != nil {
+ timeout := int(*resource.TimeoutMemberConnect)
+ createOpts.TimeoutMemberConnect = &timeout
+ }
+ if resource.TimeoutMemberData != nil {
+ timeout := int(*resource.TimeoutMemberData)
+ createOpts.TimeoutMemberData = &timeout
+ }
+ if resource.TimeoutTCPInspect != nil {
+ timeout := int(*resource.TimeoutTCPInspect)
+ createOpts.TimeoutTCPInspect = &timeout
+ }
+
+ if len(resource.Tags) > 0 {
+ tags := make([]string, len(resource.Tags))
+ for i := range resource.Tags {
+ tags[i] = string(resource.Tags[i])
+ }
+ createOpts.Tags = tags
+ }
+
+ if len(resource.AllowedCIDRs) > 0 {
+ createOpts.AllowedCIDRs = resource.AllowedCIDRs
+ }
+
+ if resource.InsertHeaders != nil {
+ createOpts.InsertHeaders = resource.InsertHeaders
+ }
+
+ osResource, err := actuator.osClient.CreateListener(ctx, createOpts)
+ if err != nil {
+ // We should require the spec to be updated before retrying a create which returned a conflict
+ if !orcerrors.IsRetryable(err) {
+ err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err)
+ }
+ return nil, progress.WrapError(err)
+ }
+
+ return osResource, nil
+}
+
+func (actuator listenerActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus {
+ if resource.ProvisioningStatus == ListenerProvisioningStatusPendingDelete {
+ return progress.WaitingOnOpenStack(progress.WaitingOnReady, listenerDeletingPollingPeriod)
+ }
+ return progress.WrapError(actuator.osClient.DeleteListener(ctx, resource.ID))
+}
+
+func (actuator listenerActuator) updateResource(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus {
+ log := ctrl.LoggerFrom(ctx)
+ resource := obj.Spec.Resource
+ if resource == nil {
+ // Should have been caught by API validation
+ return progress.WrapError(
+ orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Update requested, but spec.resource is not set"))
+ }
+
+ updateOpts := listeners.UpdateOpts{}
+
+ handleNameUpdate(&updateOpts, obj, osResource)
+ handleDescriptionUpdate(&updateOpts, resource, osResource)
+
+ needsUpdate, err := needsUpdate(updateOpts)
+ if err != nil {
+ return progress.WrapError(
+ orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err))
+ }
+ if !needsUpdate {
+ log.V(logging.Debug).Info("No changes")
+ return nil
+ }
+
+ _, err = actuator.osClient.UpdateListener(ctx, osResource.ID, updateOpts)
+
+ // We should require the spec to be updated before retrying an update which returned a conflict
+ if orcerrors.IsConflict(err) {
+ err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err)
+ }
+
+ if err != nil {
+ return progress.WrapError(err)
+ }
+
+ return progress.NeedsRefresh()
+}
+
+func needsUpdate(updateOpts listeners.UpdateOpts) (bool, error) {
+ updateOptsMap, err := updateOpts.ToListenerUpdateMap()
+ if err != nil {
+ return false, err
+ }
+
+ updateMap, ok := updateOptsMap["listener"].(map[string]any)
+ if !ok {
+ updateMap = make(map[string]any)
+ }
+
+ return len(updateMap) > 0, nil
+}
+
+func handleNameUpdate(updateOpts *listeners.UpdateOpts, obj orcObjectPT, osResource *osResourceT) {
+ name := getResourceName(obj)
+ if osResource.Name != name {
+ updateOpts.Name = &name
+ }
+}
+
+func handleDescriptionUpdate(updateOpts *listeners.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) {
+ description := ptr.Deref(resource.Description, "")
+ if osResource.Description != description {
+ updateOpts.Description = &description
+ }
+}
+
+func (actuator listenerActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) {
+ return []resourceReconciler{
+ actuator.updateResource,
+ }, nil
+}
+
+type listenerHelperFactory struct{}
+
+var _ helperFactory = listenerHelperFactory{}
+
+func newActuator(ctx context.Context, orcObject *orcv1alpha1.Listener, controller interfaces.ResourceController) (listenerActuator, progress.ReconcileStatus) {
+ log := ctrl.LoggerFrom(ctx)
+
+ // Ensure credential secrets exist and have our finalizer
+ _, reconcileStatus := credentialsDependency.GetDependencies(ctx, controller.GetK8sClient(), orcObject, func(*corev1.Secret) bool { return true })
+ if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
+ return listenerActuator{}, reconcileStatus
+ }
+
+ clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject)
+ if err != nil {
+ return listenerActuator{}, progress.WrapError(err)
+ }
+ osClient, err := clientScope.NewListenerClient()
+ if err != nil {
+ return listenerActuator{}, progress.WrapError(err)
+ }
+
+ return listenerActuator{
+ osClient: osClient,
+ k8sClient: controller.GetK8sClient(),
+ }, nil
+}
+
+func (listenerHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI {
+ return listenerAdapter{obj}
+}
+
+func (listenerHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) {
+ return newActuator(ctx, orcObject, controller)
+}
+
+func (listenerHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) {
+ return newActuator(ctx, orcObject, controller)
+}
diff --git a/internal/controllers/listener/actuator_test.go b/internal/controllers/listener/actuator_test.go
new file mode 100644
index 000000000..a95037487
--- /dev/null
+++ b/internal/controllers/listener/actuator_test.go
@@ -0,0 +1,119 @@
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package listener
+
+import (
+ "testing"
+
+ "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/listeners"
+ orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ "k8s.io/utils/ptr"
+)
+
+func TestNeedsUpdate(t *testing.T) {
+ testCases := []struct {
+ name string
+ updateOpts listeners.UpdateOpts
+ expectChange bool
+ }{
+ {
+ name: "Empty base opts",
+ updateOpts: listeners.UpdateOpts{},
+ expectChange: false,
+ },
+ {
+ name: "Updated opts",
+ updateOpts: listeners.UpdateOpts{Name: ptr.To("updated")},
+ expectChange: true,
+ },
+ }
+
+ for _, tt := range testCases {
+ t.Run(tt.name, func(t *testing.T) {
+ got, _ := needsUpdate(tt.updateOpts)
+ if got != tt.expectChange {
+ t.Errorf("Expected change: %v, got: %v", tt.expectChange, got)
+ }
+ })
+ }
+}
+
+func TestHandleNameUpdate(t *testing.T) {
+ ptrToName := ptr.To[orcv1alpha1.OpenStackName]
+ testCases := []struct {
+ name string
+ newValue *orcv1alpha1.OpenStackName
+ existingValue string
+ expectChange bool
+ }{
+ {name: "Identical", newValue: ptrToName("name"), existingValue: "name", expectChange: false},
+ {name: "Different", newValue: ptrToName("new-name"), existingValue: "name", expectChange: true},
+ {name: "No value provided, existing is identical to object name", newValue: nil, existingValue: "object-name", expectChange: false},
+ {name: "No value provided, existing is different from object name", newValue: nil, existingValue: "different-from-object-name", expectChange: true},
+ }
+
+ for _, tt := range testCases {
+ t.Run(tt.name, func(t *testing.T) {
+ resource := &orcv1alpha1.Listener{}
+ resource.Name = "object-name"
+ resource.Spec = orcv1alpha1.ListenerSpec{
+ Resource: &orcv1alpha1.ListenerResourceSpec{Name: tt.newValue},
+ }
+ osResource := &osResourceT{Name: tt.existingValue}
+
+ updateOpts := listeners.UpdateOpts{}
+ handleNameUpdate(&updateOpts, resource, osResource)
+
+ got, _ := needsUpdate(updateOpts)
+ if got != tt.expectChange {
+ t.Errorf("Expected change: %v, got: %v", tt.expectChange, got)
+ }
+ })
+
+ }
+}
+
+func TestHandleDescriptionUpdate(t *testing.T) {
+ ptrToDescription := ptr.To[string]
+ testCases := []struct {
+ name string
+ newValue *string
+ existingValue string
+ expectChange bool
+ }{
+ {name: "Identical", newValue: ptrToDescription("desc"), existingValue: "desc", expectChange: false},
+ {name: "Different", newValue: ptrToDescription("new-desc"), existingValue: "desc", expectChange: true},
+ {name: "No value provided, existing is set", newValue: nil, existingValue: "desc", expectChange: true},
+ {name: "No value provided, existing is empty", newValue: nil, existingValue: "", expectChange: false},
+ }
+
+ for _, tt := range testCases {
+ t.Run(tt.name, func(t *testing.T) {
+ resource := &orcv1alpha1.ListenerResourceSpec{Description: tt.newValue}
+ osResource := &osResourceT{Description: tt.existingValue}
+
+ updateOpts := listeners.UpdateOpts{}
+ handleDescriptionUpdate(&updateOpts, resource, osResource)
+
+ got, _ := needsUpdate(updateOpts)
+ if got != tt.expectChange {
+ t.Errorf("Expected change: %v, got: %v", tt.expectChange, got)
+ }
+ })
+
+ }
+}
diff --git a/internal/controllers/listener/controller.go b/internal/controllers/listener/controller.go
new file mode 100644
index 000000000..a120dba08
--- /dev/null
+++ b/internal/controllers/listener/controller.go
@@ -0,0 +1,114 @@
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package listener
+
+import (
+ "context"
+ "errors"
+
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/builder"
+ "sigs.k8s.io/controller-runtime/pkg/controller"
+
+ orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/reconciler"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/scope"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/util/credentials"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency"
+ "github.com/k-orc/openstack-resource-controller/v2/pkg/predicates"
+)
+
+const controllerName = "listener"
+
+// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=listeners,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=listeners/status,verbs=get;update;patch
+
+type listenerReconcilerConstructor struct {
+ scopeFactory scope.Factory
+}
+
+func New(scopeFactory scope.Factory) interfaces.Controller {
+ return listenerReconcilerConstructor{scopeFactory: scopeFactory}
+}
+
+func (listenerReconcilerConstructor) GetName() string {
+ return controllerName
+}
+
+var loadBalancerDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ListenerList, *orcv1alpha1.LoadBalancer](
+ "spec.resource.loadBalancerRef",
+ func(listener *orcv1alpha1.Listener) []string {
+ resource := listener.Spec.Resource
+ if resource == nil {
+ return nil
+ }
+ return []string{string(resource.LoadBalancerRef)}
+ },
+ finalizer, externalObjectFieldOwner,
+)
+
+var loadBalancerImportDependency = dependency.NewDependency[*orcv1alpha1.ListenerList, *orcv1alpha1.LoadBalancer](
+ "spec.import.filter.loadBalancerRef",
+ func(listener *orcv1alpha1.Listener) []string {
+ resource := listener.Spec.Import
+ if resource == nil || resource.Filter == nil || resource.Filter.LoadBalancerRef == nil {
+ return nil
+ }
+ return []string{string(*resource.Filter.LoadBalancerRef)}
+ },
+)
+
+// SetupWithManager sets up the controller with the Manager.
+func (c listenerReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
+ log := ctrl.LoggerFrom(ctx)
+ k8sClient := mgr.GetClient()
+
+ loadBalancerWatchEventHandler, err := loadBalancerDependency.WatchEventHandler(log, k8sClient)
+ if err != nil {
+ return err
+ }
+
+ loadBalancerImportWatchEventHandler, err := loadBalancerImportDependency.WatchEventHandler(log, k8sClient)
+ if err != nil {
+ return err
+ }
+
+ builder := ctrl.NewControllerManagedBy(mgr).
+ WithOptions(options).
+ Watches(&orcv1alpha1.LoadBalancer{}, loadBalancerWatchEventHandler,
+ builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.LoadBalancer{})),
+ ).
+ // A second watch is necessary because we need a different handler that omits deletion guards
+ Watches(&orcv1alpha1.LoadBalancer{}, loadBalancerImportWatchEventHandler,
+ builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.LoadBalancer{})),
+ ).
+ For(&orcv1alpha1.Listener{})
+
+ if err := errors.Join(
+ loadBalancerDependency.AddToManager(ctx, mgr),
+ loadBalancerImportDependency.AddToManager(ctx, mgr),
+ credentialsDependency.AddToManager(ctx, mgr),
+ credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency),
+ ); err != nil {
+ return err
+ }
+
+ r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, listenerHelperFactory{}, listenerStatusWriter{})
+ return builder.Complete(&r)
+}
diff --git a/internal/controllers/listener/status.go b/internal/controllers/listener/status.go
new file mode 100644
index 000000000..159324162
--- /dev/null
+++ b/internal/controllers/listener/status.go
@@ -0,0 +1,108 @@
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package listener
+
+import (
+ "github.com/go-logr/logr"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress"
+ orcapplyconfigv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1"
+)
+
+// Octavia provisioning status values
+const (
+ ListenerProvisioningStatusActive = "ACTIVE"
+ ListenerProvisioningStatusError = "ERROR"
+ ListenerProvisioningStatusPendingCreate = "PENDING_CREATE"
+ ListenerProvisioningStatusPendingUpdate = "PENDING_UPDATE"
+ ListenerProvisioningStatusPendingDelete = "PENDING_DELETE"
+)
+
+type listenerStatusWriter struct{}
+
+type objectApplyT = orcapplyconfigv1alpha1.ListenerApplyConfiguration
+type statusApplyT = orcapplyconfigv1alpha1.ListenerStatusApplyConfiguration
+
+var _ interfaces.ResourceStatusWriter[*orcv1alpha1.Listener, *osResourceT, *objectApplyT, *statusApplyT] = listenerStatusWriter{}
+
+func (listenerStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT {
+ return orcapplyconfigv1alpha1.Listener(name, namespace)
+}
+
+func (listenerStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.Listener, osResource *osResourceT) (metav1.ConditionStatus, progress.ReconcileStatus) {
+ if osResource == nil {
+ if orcObject.Status.ID == nil {
+ return metav1.ConditionFalse, nil
+ }
+ return metav1.ConditionUnknown, nil
+ }
+
+ switch osResource.ProvisioningStatus {
+ case ListenerProvisioningStatusActive:
+ return metav1.ConditionTrue, nil
+ case ListenerProvisioningStatusError:
+ return metav1.ConditionFalse, nil
+ default:
+ // PENDING_CREATE, PENDING_UPDATE, PENDING_DELETE
+ return metav1.ConditionFalse, progress.WaitingOnOpenStack(progress.WaitingOnReady, listenerAvailablePollingPeriod)
+ }
+}
+
+func (listenerStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) {
+ resourceStatus := orcapplyconfigv1alpha1.ListenerResourceStatus().
+ WithName(osResource.Name).
+ WithProtocol(osResource.Protocol).
+ WithProtocolPort(int32(osResource.ProtocolPort)).
+ WithAdminStateUp(osResource.AdminStateUp).
+ WithConnectionLimit(int32(osResource.ConnLimit)).
+ WithProvisioningStatus(osResource.ProvisioningStatus).
+ WithOperatingStatus(osResource.OperatingStatus).
+ WithTimeoutClientData(int32(osResource.TimeoutClientData)).
+ WithTimeoutMemberConnect(int32(osResource.TimeoutMemberConnect)).
+ WithTimeoutMemberData(int32(osResource.TimeoutMemberData)).
+ WithTimeoutTCPInspect(int32(osResource.TimeoutTCPInspect))
+
+ if osResource.Description != "" {
+ resourceStatus.WithDescription(osResource.Description)
+ }
+
+ if osResource.DefaultPoolID != "" {
+ resourceStatus.WithDefaultPoolID(osResource.DefaultPoolID)
+ }
+
+ // Get the first loadbalancer ID if available
+ if len(osResource.Loadbalancers) > 0 {
+ resourceStatus.WithLoadBalancerID(osResource.Loadbalancers[0].ID)
+ }
+
+ if len(osResource.AllowedCIDRs) > 0 {
+ resourceStatus.WithAllowedCIDRs(osResource.AllowedCIDRs...)
+ }
+
+ if osResource.InsertHeaders != nil {
+ resourceStatus.WithInsertHeaders(osResource.InsertHeaders)
+ }
+
+ if len(osResource.Tags) > 0 {
+ resourceStatus.WithTags(osResource.Tags...)
+ }
+
+ statusApply.WithResource(resourceStatus)
+}
diff --git a/internal/controllers/listener/tests/listener-create-full/00-assert.yaml b/internal/controllers/listener/tests/listener-create-full/00-assert.yaml
new file mode 100644
index 000000000..bfe41cfbb
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-create-full/00-assert.yaml
@@ -0,0 +1,34 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-create-full
+status:
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Listener
+ name: listener-create-full
+ ref: listener
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: LoadBalancer
+ name: listener-create-full
+ ref: loadBalancer
+assertAll:
+ - celExpr: "listener.status.id != ''"
+ - celExpr: "listener.status.resource.name == 'listener-create-full-override'"
+ - celExpr: "listener.status.resource.description == 'Listener from create full test'"
+ - celExpr: "listener.status.resource.loadBalancerID == loadBalancer.status.id"
+ - celExpr: "listener.status.resource.protocol == 'HTTPS'"
+ - celExpr: "listener.status.resource.protocolPort == 443"
+ - celExpr: "listener.status.resource.connectionLimit == 1000"
+ - celExpr: "listener.status.resource.provisioningStatus == 'ACTIVE'"
diff --git a/internal/controllers/listener/tests/listener-create-full/00-create-resource.yaml b/internal/controllers/listener/tests/listener-create-full/00-create-resource.yaml
new file mode 100644
index 000000000..124257b30
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-create-full/00-create-resource.yaml
@@ -0,0 +1,64 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Network
+metadata:
+ name: listener-create-full
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: listener-create-full
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: listener-create-full
+ ipVersion: 4
+ cidr: 10.0.1.0/24
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: listener-create-full
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: listener-create-full
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-create-full
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ name: listener-create-full-override
+ description: Listener from create full test
+ loadBalancerRef: listener-create-full
+ protocol: HTTPS
+ protocolPort: 443
+ adminStateUp: true
+ connectionLimit: 1000
+ timeoutClientData: 50000
+ timeoutMemberConnect: 5000
+ timeoutMemberData: 50000
+ allowedCIDRs:
+ - "10.0.0.0/8"
+ - "192.168.0.0/16"
+ tags:
+ - "test"
+ - "listener"
diff --git a/internal/controllers/listener/tests/listener-create-full/00-secret.yaml b/internal/controllers/listener/tests/listener-create-full/00-secret.yaml
new file mode 100644
index 000000000..045711ee7
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-create-full/00-secret.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
diff --git a/internal/controllers/listener/tests/listener-create-full/README.md b/internal/controllers/listener/tests/listener-create-full/README.md
new file mode 100644
index 000000000..f208be341
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-create-full/README.md
@@ -0,0 +1,11 @@
+# Create a Listener with all the options
+
+## Step 00
+
+Create a Listener using all available fields, and verify that the observed state corresponds to the spec.
+
+Also validate that the OpenStack resource uses the name from the spec when it is specified.
+
+## Reference
+
+https://k-orc.cloud/development/writing-tests/#create-full
diff --git a/internal/controllers/listener/tests/listener-create-minimal/00-assert.yaml b/internal/controllers/listener/tests/listener-create-minimal/00-assert.yaml
new file mode 100644
index 000000000..c85b6fadd
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-create-minimal/00-assert.yaml
@@ -0,0 +1,32 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-create-minimal
+status:
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Listener
+ name: listener-create-minimal
+ ref: listener
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: LoadBalancer
+ name: listener-create-minimal
+ ref: loadBalancer
+assertAll:
+ - celExpr: "listener.status.id != ''"
+ - celExpr: "listener.status.resource.name == 'listener-create-minimal'"
+ - celExpr: "listener.status.resource.loadBalancerID == loadBalancer.status.id"
+ - celExpr: "listener.status.resource.protocol == 'HTTP'"
+ - celExpr: "listener.status.resource.protocolPort == 80"
+ - celExpr: "listener.status.resource.provisioningStatus == 'ACTIVE'"
diff --git a/internal/controllers/listener/tests/listener-create-minimal/00-create-resource.yaml b/internal/controllers/listener/tests/listener-create-minimal/00-create-resource.yaml
new file mode 100644
index 000000000..e06345a85
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-create-minimal/00-create-resource.yaml
@@ -0,0 +1,51 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Network
+metadata:
+ name: listener-create-minimal
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: listener-create-minimal
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: listener-create-minimal
+ ipVersion: 4
+ cidr: 10.0.0.0/24
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: listener-create-minimal
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: listener-create-minimal
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-create-minimal
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ loadBalancerRef: listener-create-minimal
+ protocol: HTTP
+ protocolPort: 80
diff --git a/internal/controllers/listener/tests/listener-create-minimal/00-secret.yaml b/internal/controllers/listener/tests/listener-create-minimal/00-secret.yaml
new file mode 100644
index 000000000..045711ee7
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-create-minimal/00-secret.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
diff --git a/internal/controllers/listener/tests/listener-create-minimal/01-assert.yaml b/internal/controllers/listener/tests/listener-create-minimal/01-assert.yaml
new file mode 100644
index 000000000..d7c78a794
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-create-minimal/01-assert.yaml
@@ -0,0 +1,11 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: v1
+ kind: Secret
+ name: openstack-clouds
+ ref: secret
+assertAll:
+ - celExpr: "secret.metadata.deletionTimestamp != 0"
+ - celExpr: "'openstack.k-orc.cloud/listener' in secret.metadata.finalizers"
diff --git a/internal/controllers/listener/tests/listener-create-minimal/01-delete-secret.yaml b/internal/controllers/listener/tests/listener-create-minimal/01-delete-secret.yaml
new file mode 100644
index 000000000..1620791b9
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-create-minimal/01-delete-secret.yaml
@@ -0,0 +1,7 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ # We expect the deletion to hang due to the finalizer, so use --wait=false
+ - command: kubectl delete secret openstack-clouds --wait=false
+ namespaced: true
diff --git a/internal/controllers/listener/tests/listener-create-minimal/README.md b/internal/controllers/listener/tests/listener-create-minimal/README.md
new file mode 100644
index 000000000..16cad70aa
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-create-minimal/README.md
@@ -0,0 +1,15 @@
+# Create a Listener with the minimum options
+
+## Step 00
+
+Create a minimal Listener, that sets only the required fields, and verify that the observed state corresponds to the spec.
+
+Also validate that the OpenStack resource uses the name of the ORC object when no name is explicitly specified.
+
+## Step 01
+
+Try deleting the secret and ensure that it is not deleted thanks to the finalizer.
+
+## Reference
+
+https://k-orc.cloud/development/writing-tests/#create-minimal
diff --git a/internal/controllers/listener/tests/listener-dependency/00-assert.yaml b/internal/controllers/listener/tests/listener-dependency/00-assert.yaml
new file mode 100644
index 000000000..d7fd1357c
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-dependency/00-assert.yaml
@@ -0,0 +1,30 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-dependency-no-secret
+status:
+ conditions:
+ - type: Available
+ message: Waiting for Secret/listener-dependency to be created
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: Waiting for Secret/listener-dependency to be created
+ status: "True"
+ reason: Progressing
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-dependency-no-loadbalancer
+status:
+ conditions:
+ - type: Available
+ message: Waiting for LoadBalancer/listener-dependency-pending to be created
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: Waiting for LoadBalancer/listener-dependency-pending to be created
+ status: "True"
+ reason: Progressing
diff --git a/internal/controllers/listener/tests/listener-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/listener/tests/listener-dependency/00-create-resources-missing-deps.yaml
new file mode 100644
index 000000000..f2c20272f
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-dependency/00-create-resources-missing-deps.yaml
@@ -0,0 +1,65 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Network
+metadata:
+ name: listener-dependency
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: listener-dependency
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: listener-dependency
+ ipVersion: 4
+ cidr: 10.0.3.0/24
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: listener-dependency
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: listener-dependency
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-dependency-no-loadbalancer
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ loadBalancerRef: listener-dependency-pending
+ protocol: HTTP
+ protocolPort: 80
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-dependency-no-secret
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: listener-dependency
+ managementPolicy: managed
+ resource:
+ loadBalancerRef: listener-dependency
+ protocol: HTTP
+ protocolPort: 81
diff --git a/internal/controllers/listener/tests/listener-dependency/00-secret.yaml b/internal/controllers/listener/tests/listener-dependency/00-secret.yaml
new file mode 100644
index 000000000..045711ee7
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-dependency/00-secret.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
diff --git a/internal/controllers/listener/tests/listener-dependency/01-assert.yaml b/internal/controllers/listener/tests/listener-dependency/01-assert.yaml
new file mode 100644
index 000000000..f88bb904d
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-dependency/01-assert.yaml
@@ -0,0 +1,30 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-dependency-no-secret
+status:
+ conditions:
+ - type: Available
+ message: OpenStack resource is available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ message: OpenStack resource is up to date
+ status: "False"
+ reason: Success
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-dependency-no-loadbalancer
+status:
+ conditions:
+ - type: Available
+ message: OpenStack resource is available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ message: OpenStack resource is up to date
+ status: "False"
+ reason: Success
diff --git a/internal/controllers/listener/tests/listener-dependency/01-create-dependencies.yaml b/internal/controllers/listener/tests/listener-dependency/01-create-dependencies.yaml
new file mode 100644
index 000000000..f6ddcf702
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-dependency/01-create-dependencies.yaml
@@ -0,0 +1,43 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic listener-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Network
+metadata:
+ name: listener-dependency-pending
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: listener-dependency-pending
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: listener-dependency-pending
+ ipVersion: 4
+ cidr: 10.0.4.0/24
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: listener-dependency-pending
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: listener-dependency-pending
diff --git a/internal/controllers/listener/tests/listener-dependency/02-assert.yaml b/internal/controllers/listener/tests/listener-dependency/02-assert.yaml
new file mode 100644
index 000000000..912a8ef97
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-dependency/02-assert.yaml
@@ -0,0 +1,17 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: LoadBalancer
+ name: listener-dependency
+ ref: loadBalancer
+ - apiVersion: v1
+ kind: Secret
+ name: listener-dependency
+ ref: secret
+assertAll:
+ - celExpr: "loadBalancer.metadata.deletionTimestamp != 0"
+ - celExpr: "'openstack.k-orc.cloud/listener' in loadBalancer.metadata.finalizers"
+ - celExpr: "secret.metadata.deletionTimestamp != 0"
+ - celExpr: "'openstack.k-orc.cloud/listener' in secret.metadata.finalizers"
diff --git a/internal/controllers/listener/tests/listener-dependency/02-delete-dependencies.yaml b/internal/controllers/listener/tests/listener-dependency/02-delete-dependencies.yaml
new file mode 100644
index 000000000..3790fb04d
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-dependency/02-delete-dependencies.yaml
@@ -0,0 +1,9 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ # We expect the deletion to hang due to the finalizer, so use --wait=false
+ - command: kubectl delete loadbalancer listener-dependency --wait=false
+ namespaced: true
+ - command: kubectl delete secret listener-dependency --wait=false
+ namespaced: true
diff --git a/internal/controllers/listener/tests/listener-dependency/03-assert.yaml b/internal/controllers/listener/tests/listener-dependency/03-assert.yaml
new file mode 100644
index 000000000..803c4a6a6
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-dependency/03-assert.yaml
@@ -0,0 +1,9 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+commands:
+# Dependencies that were prevented deletion before should now be gone
+- script: "! kubectl get loadbalancer listener-dependency --namespace $NAMESPACE"
+ skipLogOutput: true
+- script: "! kubectl get secret listener-dependency --namespace $NAMESPACE"
+ skipLogOutput: true
diff --git a/internal/controllers/listener/tests/listener-dependency/03-delete-resources.yaml b/internal/controllers/listener/tests/listener-dependency/03-delete-resources.yaml
new file mode 100644
index 000000000..7b812616d
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-dependency/03-delete-resources.yaml
@@ -0,0 +1,10 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+delete:
+- apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Listener
+ name: listener-dependency-no-secret
+- apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Listener
+ name: listener-dependency-no-loadbalancer
diff --git a/internal/controllers/listener/tests/listener-dependency/README.md b/internal/controllers/listener/tests/listener-dependency/README.md
new file mode 100644
index 000000000..cff5a360c
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-dependency/README.md
@@ -0,0 +1,21 @@
+# Creation and deletion dependencies
+
+## Step 00
+
+Create Listeners referencing non-existing resources. Each Listener is dependent on other non-existing resource. Verify that the Listeners are waiting for the needed resources to be created externally.
+
+## Step 01
+
+Create the missing dependencies and verify all the Listeners are available.
+
+## Step 02
+
+Delete all the dependencies and check that ORC prevents deletion since there is still a resource that depends on them.
+
+## Step 03
+
+Delete the Listeners and validate that all resources are gone.
+
+## Reference
+
+https://k-orc.cloud/development/writing-tests/#dependency
diff --git a/internal/controllers/listener/tests/listener-import-dependency/00-assert.yaml b/internal/controllers/listener/tests/listener-import-dependency/00-assert.yaml
new file mode 100644
index 000000000..51f437854
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-dependency/00-assert.yaml
@@ -0,0 +1,17 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import-dependency
+status:
+ conditions:
+ - type: Available
+ message: |-
+ Waiting for LoadBalancer/listener-import-dependency to be ready
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: |-
+ Waiting for LoadBalancer/listener-import-dependency to be ready
+ status: "True"
+ reason: Progressing
diff --git a/internal/controllers/listener/tests/listener-import-dependency/00-import-resource.yaml b/internal/controllers/listener/tests/listener-import-dependency/00-import-resource.yaml
new file mode 100644
index 000000000..06d36cc74
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-dependency/00-import-resource.yaml
@@ -0,0 +1,26 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: listener-import-dependency
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: unmanaged
+ import:
+ filter:
+ name: listener-import-dependency-external
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import-dependency
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: unmanaged
+ import:
+ filter:
+ loadBalancerRef: listener-import-dependency
diff --git a/internal/controllers/listener/tests/listener-import-dependency/00-secret.yaml b/internal/controllers/listener/tests/listener-import-dependency/00-secret.yaml
new file mode 100644
index 000000000..045711ee7
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-dependency/00-secret.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
diff --git a/internal/controllers/listener/tests/listener-import-dependency/01-assert.yaml b/internal/controllers/listener/tests/listener-import-dependency/01-assert.yaml
new file mode 100644
index 000000000..989d8c225
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-dependency/01-assert.yaml
@@ -0,0 +1,32 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import-dependency-not-this-one
+status:
+ conditions:
+ - type: Available
+ message: OpenStack resource is available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ message: OpenStack resource is up to date
+ status: "False"
+ reason: Success
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import-dependency
+status:
+ conditions:
+ - type: Available
+ message: |-
+ Waiting for LoadBalancer/listener-import-dependency to be ready
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: |-
+ Waiting for LoadBalancer/listener-import-dependency to be ready
+ status: "True"
+ reason: Progressing
diff --git a/internal/controllers/listener/tests/listener-import-dependency/01-create-trap-resource.yaml b/internal/controllers/listener/tests/listener-import-dependency/01-create-trap-resource.yaml
new file mode 100644
index 000000000..8f68fd027
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-dependency/01-create-trap-resource.yaml
@@ -0,0 +1,52 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Network
+metadata:
+ name: listener-import-dependency-not-this-one
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: listener-import-dependency-not-this-one
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: listener-import-dependency-not-this-one
+ ipVersion: 4
+ cidr: 10.0.8.0/24
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: listener-import-dependency-not-this-one
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: listener-import-dependency-not-this-one
+---
+# This `listener-import-dependency-not-this-one` should not be picked by the import filter
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import-dependency-not-this-one
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ loadBalancerRef: listener-import-dependency-not-this-one
+ protocol: HTTP
+ protocolPort: 80
diff --git a/internal/controllers/listener/tests/listener-import-dependency/02-assert.yaml b/internal/controllers/listener/tests/listener-import-dependency/02-assert.yaml
new file mode 100644
index 000000000..48fd24a83
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-dependency/02-assert.yaml
@@ -0,0 +1,34 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Listener
+ name: listener-import-dependency
+ ref: listener1
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Listener
+ name: listener-import-dependency-not-this-one
+ ref: listener2
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: LoadBalancer
+ name: listener-import-dependency
+ ref: loadBalancer
+assertAll:
+ - celExpr: "listener1.status.id != listener2.status.id"
+ - celExpr: "listener1.status.resource.loadBalancerID == loadBalancer.status.id"
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import-dependency
+status:
+ conditions:
+ - type: Available
+ message: OpenStack resource is available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ message: OpenStack resource is up to date
+ status: "False"
+ reason: Success
diff --git a/internal/controllers/listener/tests/listener-import-dependency/02-create-resource.yaml b/internal/controllers/listener/tests/listener-import-dependency/02-create-resource.yaml
new file mode 100644
index 000000000..628afa93f
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-dependency/02-create-resource.yaml
@@ -0,0 +1,51 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Network
+metadata:
+ name: listener-import-dependency-external
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: listener-import-dependency-external
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: listener-import-dependency-external
+ ipVersion: 4
+ cidr: 10.0.9.0/24
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: listener-import-dependency-external
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: listener-import-dependency-external
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import-dependency-external
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ loadBalancerRef: listener-import-dependency-external
+ protocol: HTTP
+ protocolPort: 80
diff --git a/internal/controllers/listener/tests/listener-import-dependency/03-assert.yaml b/internal/controllers/listener/tests/listener-import-dependency/03-assert.yaml
new file mode 100644
index 000000000..ef8f49234
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-dependency/03-assert.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+commands:
+- script: "! kubectl get loadbalancer listener-import-dependency --namespace $NAMESPACE"
+ skipLogOutput: true
diff --git a/internal/controllers/listener/tests/listener-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/listener/tests/listener-import-dependency/03-delete-import-dependencies.yaml
new file mode 100644
index 000000000..072a3ec15
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-dependency/03-delete-import-dependencies.yaml
@@ -0,0 +1,7 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ # We should be able to delete the import dependencies
+ - command: kubectl delete loadbalancer listener-import-dependency
+ namespaced: true
diff --git a/internal/controllers/listener/tests/listener-import-dependency/04-assert.yaml b/internal/controllers/listener/tests/listener-import-dependency/04-assert.yaml
new file mode 100644
index 000000000..395312ed7
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-dependency/04-assert.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+commands:
+- script: "! kubectl get listener listener-import-dependency --namespace $NAMESPACE"
+ skipLogOutput: true
diff --git a/internal/controllers/listener/tests/listener-import-dependency/04-delete-resource.yaml b/internal/controllers/listener/tests/listener-import-dependency/04-delete-resource.yaml
new file mode 100644
index 000000000..f2a5c653f
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-dependency/04-delete-resource.yaml
@@ -0,0 +1,7 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+delete:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Listener
+ name: listener-import-dependency
diff --git a/internal/controllers/listener/tests/listener-import-dependency/README.md b/internal/controllers/listener/tests/listener-import-dependency/README.md
new file mode 100644
index 000000000..9af528e43
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-dependency/README.md
@@ -0,0 +1,29 @@
+# Check dependency handling for imported Listener
+
+## Step 00
+
+Import a Listener that references other imported resources. The referenced imported resources have no matching resources yet.
+Verify the Listener is waiting for the dependency to be ready.
+
+## Step 01
+
+Create a Listener matching the import filter, except for referenced resources, and verify that it's not being imported.
+
+## Step 02
+
+Create the referenced resources and a Listener matching the import filters.
+
+Verify that the observed status on the imported Listener corresponds to the spec of the created Listener.
+
+## Step 03
+
+Delete the referenced resources and check that ORC does not prevent deletion. The OpenStack resources still exist because they
+were imported resources and we only deleted the ORC representation of it.
+
+## Step 04
+
+Delete the Listener and validate that all resources are gone.
+
+## Reference
+
+https://k-orc.cloud/development/writing-tests/#import-dependency
diff --git a/internal/controllers/listener/tests/listener-import-error/00-assert.yaml b/internal/controllers/listener/tests/listener-import-error/00-assert.yaml
new file mode 100644
index 000000000..46649904e
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-error/00-assert.yaml
@@ -0,0 +1,30 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import-error-external-1
+status:
+ conditions:
+ - type: Available
+ message: OpenStack resource is available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ message: OpenStack resource is up to date
+ status: "False"
+ reason: Success
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import-error-external-2
+status:
+ conditions:
+ - type: Available
+ message: OpenStack resource is available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ message: OpenStack resource is up to date
+ status: "False"
+ reason: Success
diff --git a/internal/controllers/listener/tests/listener-import-error/00-create-resources.yaml b/internal/controllers/listener/tests/listener-import-error/00-create-resources.yaml
new file mode 100644
index 000000000..3a4ccdca0
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-error/00-create-resources.yaml
@@ -0,0 +1,67 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Network
+metadata:
+ name: listener-import-error
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: listener-import-error
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: listener-import-error
+ ipVersion: 4
+ cidr: 10.0.7.0/24
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: listener-import-error
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: listener-import-error
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import-error-external-1
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ name: listener-import-error-match
+ loadBalancerRef: listener-import-error
+ protocol: HTTP
+ protocolPort: 80
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import-error-external-2
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ name: listener-import-error-match
+ loadBalancerRef: listener-import-error
+ protocol: HTTP
+ protocolPort: 81
diff --git a/internal/controllers/listener/tests/listener-import-error/00-secret.yaml b/internal/controllers/listener/tests/listener-import-error/00-secret.yaml
new file mode 100644
index 000000000..045711ee7
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-error/00-secret.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
diff --git a/internal/controllers/listener/tests/listener-import-error/01-assert.yaml b/internal/controllers/listener/tests/listener-import-error/01-assert.yaml
new file mode 100644
index 000000000..2dcb9dc23
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-error/01-assert.yaml
@@ -0,0 +1,15 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import-error
+status:
+ conditions:
+ - type: Available
+ message: found more than one matching OpenStack resource during import
+ status: "False"
+ reason: InvalidConfiguration
+ - type: Progressing
+ message: found more than one matching OpenStack resource during import
+ status: "False"
+ reason: InvalidConfiguration
diff --git a/internal/controllers/listener/tests/listener-import-error/01-import-resource.yaml b/internal/controllers/listener/tests/listener-import-error/01-import-resource.yaml
new file mode 100644
index 000000000..f01bb2bf7
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-error/01-import-resource.yaml
@@ -0,0 +1,13 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import-error
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: unmanaged
+ import:
+ filter:
+ name: listener-import-error-match
diff --git a/internal/controllers/listener/tests/listener-import-error/README.md b/internal/controllers/listener/tests/listener-import-error/README.md
new file mode 100644
index 000000000..5e40fed66
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import-error/README.md
@@ -0,0 +1,13 @@
+# Import Listener with more than one matching resources
+
+## Step 00
+
+Create two Listeners with identical specs.
+
+## Step 01
+
+Ensure that an imported Listener with a filter matching the resources returns an error.
+
+## Reference
+
+https://k-orc.cloud/development/writing-tests/#import-error
diff --git a/internal/controllers/listener/tests/listener-import/00-assert.yaml b/internal/controllers/listener/tests/listener-import/00-assert.yaml
new file mode 100644
index 000000000..a6b5f4dea
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import/00-assert.yaml
@@ -0,0 +1,15 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import
+status:
+ conditions:
+ - type: Available
+ message: Waiting for OpenStack resource to be created externally
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: Waiting for OpenStack resource to be created externally
+ status: "True"
+ reason: Progressing
diff --git a/internal/controllers/listener/tests/listener-import/00-import-resource.yaml b/internal/controllers/listener/tests/listener-import/00-import-resource.yaml
new file mode 100644
index 000000000..b3a0b8cc3
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import/00-import-resource.yaml
@@ -0,0 +1,13 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: unmanaged
+ import:
+ filter:
+ name: listener-import-external
diff --git a/internal/controllers/listener/tests/listener-import/00-secret.yaml b/internal/controllers/listener/tests/listener-import/00-secret.yaml
new file mode 100644
index 000000000..045711ee7
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import/00-secret.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
diff --git a/internal/controllers/listener/tests/listener-import/01-assert.yaml b/internal/controllers/listener/tests/listener-import/01-assert.yaml
new file mode 100644
index 000000000..87a8ab42c
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import/01-assert.yaml
@@ -0,0 +1,32 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import-external-not-this-one
+status:
+ conditions:
+ - type: Available
+ message: OpenStack resource is available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ message: OpenStack resource is up to date
+ status: "False"
+ reason: Success
+ resource:
+ name: listener-import-external-not-this-one
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import
+status:
+ conditions:
+ - type: Available
+ message: Waiting for OpenStack resource to be created externally
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: Waiting for OpenStack resource to be created externally
+ status: "True"
+ reason: Progressing
diff --git a/internal/controllers/listener/tests/listener-import/01-create-trap-resource.yaml b/internal/controllers/listener/tests/listener-import/01-create-trap-resource.yaml
new file mode 100644
index 000000000..e55296211
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import/01-create-trap-resource.yaml
@@ -0,0 +1,54 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Network
+metadata:
+ name: listener-import-not-this-one
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: listener-import-not-this-one
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: listener-import-not-this-one
+ ipVersion: 4
+ cidr: 10.0.5.0/24
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: listener-import-external-not-this-one
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: listener-import-not-this-one
+---
+# This `listener-import-external-not-this-one` resource serves two purposes:
+# - ensure that we can successfully create another resource which name is a substring of it (i.e. it's not being adopted)
+# - ensure that importing a resource which name is a substring of it will not pick this one.
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import-external-not-this-one
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ loadBalancerRef: listener-import-external-not-this-one
+ protocol: HTTP
+ protocolPort: 80
diff --git a/internal/controllers/listener/tests/listener-import/02-assert.yaml b/internal/controllers/listener/tests/listener-import/02-assert.yaml
new file mode 100644
index 000000000..9dd988f28
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import/02-assert.yaml
@@ -0,0 +1,31 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Listener
+ name: listener-import-external
+ ref: listener1
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Listener
+ name: listener-import-external-not-this-one
+ ref: listener2
+assertAll:
+ - celExpr: "listener1.status.id != listener2.status.id"
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import
+status:
+ conditions:
+ - type: Available
+ message: OpenStack resource is available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ message: OpenStack resource is up to date
+ status: "False"
+ reason: Success
+ resource:
+ name: listener-import-external
diff --git a/internal/controllers/listener/tests/listener-import/02-create-resource.yaml b/internal/controllers/listener/tests/listener-import/02-create-resource.yaml
new file mode 100644
index 000000000..6c907e539
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import/02-create-resource.yaml
@@ -0,0 +1,51 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Network
+metadata:
+ name: listener-import
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: listener-import
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: listener-import
+ ipVersion: 4
+ cidr: 10.0.6.0/24
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: listener-import
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: listener-import
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-import-external
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ loadBalancerRef: listener-import
+ protocol: HTTP
+ protocolPort: 80
diff --git a/internal/controllers/listener/tests/listener-import/README.md b/internal/controllers/listener/tests/listener-import/README.md
new file mode 100644
index 000000000..190eaba6f
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-import/README.md
@@ -0,0 +1,18 @@
+# Import Listener
+
+## Step 00
+
+Import a listener that matches all fields in the filter, and verify it is waiting for the external resource to be created.
+
+## Step 01
+
+Create a listener whose name is a superstring of the one specified in the import filter, otherwise matching the filter, and verify that it's not being imported.
+
+## Step 02
+
+Create a listener matching the filter and verify that the observed status on the imported listener corresponds to the spec of the created listener.
+Also, confirm that it does not adopt any listener whose name is a superstring of its own.
+
+## Reference
+
+https://k-orc.cloud/development/writing-tests/#import
diff --git a/internal/controllers/listener/tests/listener-update/00-assert.yaml b/internal/controllers/listener/tests/listener-update/00-assert.yaml
new file mode 100644
index 000000000..0fc1812c7
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-update/00-assert.yaml
@@ -0,0 +1,27 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-update
+status:
+ resource:
+ name: listener-update
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Listener
+ name: listener-update
+ ref: listener
+assertAll:
+ - celExpr: "!has(listener.status.resource.description) || listener.status.resource.description == ''"
+ - celExpr: "listener.status.resource.protocol == 'HTTP'"
+ - celExpr: "listener.status.resource.protocolPort == 8080"
diff --git a/internal/controllers/listener/tests/listener-update/00-minimal-resource.yaml b/internal/controllers/listener/tests/listener-update/00-minimal-resource.yaml
new file mode 100644
index 000000000..ef75fc181
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-update/00-minimal-resource.yaml
@@ -0,0 +1,51 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Network
+metadata:
+ name: listener-update
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: listener-update
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: listener-update
+ ipVersion: 4
+ cidr: 10.0.2.0/24
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: listener-update
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: listener-update
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-update
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ loadBalancerRef: listener-update
+ protocol: HTTP
+ protocolPort: 8080
diff --git a/internal/controllers/listener/tests/listener-update/00-prerequisites.yaml b/internal/controllers/listener/tests/listener-update/00-prerequisites.yaml
new file mode 100644
index 000000000..045711ee7
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-update/00-prerequisites.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
diff --git a/internal/controllers/listener/tests/listener-update/01-assert.yaml b/internal/controllers/listener/tests/listener-update/01-assert.yaml
new file mode 100644
index 000000000..0c466cc21
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-update/01-assert.yaml
@@ -0,0 +1,16 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-update
+status:
+ resource:
+ name: listener-update-updated
+ description: listener-update-updated
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
diff --git a/internal/controllers/listener/tests/listener-update/01-updated-resource.yaml b/internal/controllers/listener/tests/listener-update/01-updated-resource.yaml
new file mode 100644
index 000000000..17e3f59e8
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-update/01-updated-resource.yaml
@@ -0,0 +1,12 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-update
+spec:
+ resource:
+ name: listener-update-updated
+ description: listener-update-updated
+ adminStateUp: false
+ tags:
+ - "updated"
diff --git a/internal/controllers/listener/tests/listener-update/02-assert.yaml b/internal/controllers/listener/tests/listener-update/02-assert.yaml
new file mode 100644
index 000000000..883711357
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-update/02-assert.yaml
@@ -0,0 +1,25 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Listener
+ name: listener-update
+ ref: listener
+assertAll:
+ - celExpr: "!has(listener.status.resource.description) || listener.status.resource.description == ''"
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Listener
+metadata:
+ name: listener-update
+status:
+ resource:
+ name: listener-update
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
diff --git a/internal/controllers/listener/tests/listener-update/02-reverted-resource.yaml b/internal/controllers/listener/tests/listener-update/02-reverted-resource.yaml
new file mode 100644
index 000000000..2c6c253ff
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-update/02-reverted-resource.yaml
@@ -0,0 +1,7 @@
+# NOTE: kuttl only does patch updates, which means we can't delete a field.
+# We have to use a kubectl apply command instead.
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl replace -f 00-minimal-resource.yaml
+ namespaced: true
diff --git a/internal/controllers/listener/tests/listener-update/README.md b/internal/controllers/listener/tests/listener-update/README.md
new file mode 100644
index 000000000..d9d45390c
--- /dev/null
+++ b/internal/controllers/listener/tests/listener-update/README.md
@@ -0,0 +1,17 @@
+# Update Listener
+
+## Step 00
+
+Create a Listener using only mandatory fields.
+
+## Step 01
+
+Update all mutable fields.
+
+## Step 02
+
+Revert the resource to its original value and verify that the resulting object matches its state when first created.
+
+## Reference
+
+https://k-orc.cloud/development/writing-tests/#update
diff --git a/internal/controllers/listener/zz_generated.adapter.go b/internal/controllers/listener/zz_generated.adapter.go
new file mode 100644
index 000000000..0dc68dc43
--- /dev/null
+++ b/internal/controllers/listener/zz_generated.adapter.go
@@ -0,0 +1,88 @@
+// Code generated by resource-generator. DO NOT EDIT.
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package listener
+
+import (
+ orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces"
+)
+
+// Fundamental types
+type (
+ orcObjectT = orcv1alpha1.Listener
+ orcObjectListT = orcv1alpha1.ListenerList
+ resourceSpecT = orcv1alpha1.ListenerResourceSpec
+ filterT = orcv1alpha1.ListenerFilter
+)
+
+// Derived types
+type (
+ orcObjectPT = *orcObjectT
+ adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT]
+ adapterT = listenerAdapter
+)
+
+type listenerAdapter struct {
+ *orcv1alpha1.Listener
+}
+
+var _ adapterI = &adapterT{}
+
+func (f adapterT) GetObject() orcObjectPT {
+ return f.Listener
+}
+
+func (f adapterT) GetManagementPolicy() orcv1alpha1.ManagementPolicy {
+ return f.Spec.ManagementPolicy
+}
+
+func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions {
+ return f.Spec.ManagedOptions
+}
+
+func (f adapterT) GetStatusID() *string {
+ return f.Status.ID
+}
+
+func (f adapterT) GetResourceSpec() *resourceSpecT {
+ return f.Spec.Resource
+}
+
+func (f adapterT) GetImportID() *string {
+ if f.Spec.Import == nil {
+ return nil
+ }
+ return f.Spec.Import.ID
+}
+
+func (f adapterT) GetImportFilter() *filterT {
+ if f.Spec.Import == nil {
+ return nil
+ }
+ return f.Spec.Import.Filter
+}
+
+// getResourceName returns the name of the OpenStack resource we should use.
+// This method is not implemented as part of APIObjectAdapter as it is intended
+// to be used by resource actuators, which don't use the adapter.
+func getResourceName(orcObject orcObjectPT) string {
+ if orcObject.Spec.Resource.Name != nil {
+ return string(*orcObject.Spec.Resource.Name)
+ }
+ return orcObject.Name
+}
diff --git a/internal/controllers/listener/zz_generated.controller.go b/internal/controllers/listener/zz_generated.controller.go
new file mode 100644
index 000000000..e1fdd68e4
--- /dev/null
+++ b/internal/controllers/listener/zz_generated.controller.go
@@ -0,0 +1,45 @@
+// Code generated by resource-generator. DO NOT EDIT.
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package listener
+
+import (
+ corev1 "k8s.io/api/core/v1"
+
+ "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency"
+ orcstrings "github.com/k-orc/openstack-resource-controller/v2/internal/util/strings"
+)
+
+var (
+ // NOTE: controllerName must be defined in any controller using this template
+
+ // finalizer is the string this controller adds to an object's Finalizers
+ finalizer = orcstrings.GetFinalizerName(controllerName)
+
+ // externalObjectFieldOwner is the field owner we use when using
+ // server-side-apply on objects we don't control
+ externalObjectFieldOwner = orcstrings.GetSSAFieldOwner(controllerName)
+
+ credentialsDependency = dependency.NewDeletionGuardDependency[*orcObjectListT, *corev1.Secret](
+ "spec.cloudCredentialsRef.secretName",
+ func(obj orcObjectPT) []string {
+ return []string{obj.Spec.CloudCredentialsRef.SecretName}
+ },
+ finalizer, externalObjectFieldOwner,
+ dependency.OverrideDependencyName("credentials"),
+ )
+)
diff --git a/internal/controllers/loadbalancer/actuator.go b/internal/controllers/loadbalancer/actuator.go
new file mode 100644
index 000000000..62714ec99
--- /dev/null
+++ b/internal/controllers/loadbalancer/actuator.go
@@ -0,0 +1,397 @@
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package loadbalancer
+
+import (
+ "context"
+ "iter"
+ "slices"
+ "time"
+
+ "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/loadbalancers"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/utils/ptr"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/logging"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/osclients"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency"
+ orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors"
+)
+
+const (
+ // The frequency to poll when waiting for the load balancer to become ACTIVE
+ loadbalancerActivePollingPeriod = 15 * time.Second
+)
+
+// OpenStack resource types
+type (
+ osResourceT = loadbalancers.LoadBalancer
+
+ createResourceActuator = interfaces.CreateResourceActuator[orcObjectPT, orcObjectT, filterT, osResourceT]
+ deleteResourceActuator = interfaces.DeleteResourceActuator[orcObjectPT, orcObjectT, osResourceT]
+ resourceReconciler = interfaces.ResourceReconciler[orcObjectPT, osResourceT]
+ helperFactory = interfaces.ResourceHelperFactory[orcObjectPT, orcObjectT, resourceSpecT, filterT, osResourceT]
+)
+
+type loadbalancerActuator struct {
+ osClient osclients.LoadBalancerClient
+ k8sClient client.Client
+}
+
+var _ createResourceActuator = loadbalancerActuator{}
+var _ deleteResourceActuator = loadbalancerActuator{}
+
+func (loadbalancerActuator) GetResourceID(osResource *osResourceT) string {
+ return osResource.ID
+}
+
+func (actuator loadbalancerActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) {
+ resource, err := actuator.osClient.GetLoadBalancer(ctx, id)
+ if err != nil {
+ return nil, progress.WrapError(err)
+ }
+ return resource, nil
+}
+
+func (actuator loadbalancerActuator) ListOSResourcesForAdoption(ctx context.Context, orcObject orcObjectPT) (iter.Seq2[*osResourceT, error], bool) {
+ resourceSpec := orcObject.Spec.Resource
+ if resourceSpec == nil {
+ return nil, false
+ }
+
+ listOpts := loadbalancers.ListOpts{
+ Name: getResourceName(orcObject),
+ }
+
+ return actuator.osClient.ListLoadBalancers(ctx, listOpts), true
+}
+
+func (actuator loadbalancerActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) {
+ var reconcileStatus progress.ReconcileStatus
+
+ vipNetwork, rs := dependency.FetchDependency[*orcv1alpha1.Network, orcv1alpha1.Network](
+ ctx, actuator.k8sClient, obj.Namespace,
+ filter.VipNetworkRef, "Network",
+ func(n *orcv1alpha1.Network) bool { return orcv1alpha1.IsAvailable(n) && n.Status.ID != nil },
+ )
+ reconcileStatus = reconcileStatus.WithReconcileStatus(rs)
+
+ project, rs := dependency.FetchDependency[*orcv1alpha1.Project, orcv1alpha1.Project](
+ ctx, actuator.k8sClient, obj.Namespace,
+ filter.ProjectRef, "Project",
+ func(n *orcv1alpha1.Project) bool { return orcv1alpha1.IsAvailable(n) && n.Status.ID != nil },
+ )
+ reconcileStatus = reconcileStatus.WithReconcileStatus(rs)
+
+ vipSubnet, rs := dependency.FetchDependency[*orcv1alpha1.Subnet, orcv1alpha1.Subnet](
+ ctx, actuator.k8sClient, obj.Namespace,
+ filter.VipSubnetRef, "Subnet",
+ func(n *orcv1alpha1.Subnet) bool { return orcv1alpha1.IsAvailable(n) && n.Status.ID != nil },
+ )
+ reconcileStatus = reconcileStatus.WithReconcileStatus(rs)
+
+ vipPort, rs := dependency.FetchDependency[*orcv1alpha1.Port, orcv1alpha1.Port](
+ ctx, actuator.k8sClient, obj.Namespace,
+ filter.VipPortRef, "Port",
+ func(n *orcv1alpha1.Port) bool { return orcv1alpha1.IsAvailable(n) && n.Status.ID != nil },
+ )
+ reconcileStatus = reconcileStatus.WithReconcileStatus(rs)
+
+ var vipNetworkID, projectID, vipSubnetID, vipPortID string
+ if vipNetwork != nil {
+ vipNetworkID = ptr.Deref(vipNetwork.Status.ID, "")
+ }
+ if project != nil {
+ projectID = ptr.Deref(project.Status.ID, "")
+ }
+ if vipSubnet != nil {
+ vipSubnetID = ptr.Deref(vipSubnet.Status.ID, "")
+ }
+ if vipPort != nil {
+ vipPortID = ptr.Deref(vipPort.Status.ID, "")
+ }
+
+ listOpts := loadbalancers.ListOpts{
+ Name: string(ptr.Deref(filter.Name, "")),
+ Description: ptr.Deref(filter.Description, ""),
+ AvailabilityZone: filter.AvailabilityZone,
+ Provider: filter.Provider,
+ VipAddress: filter.VipAddress,
+ VipNetworkID: vipNetworkID,
+ ProjectID: projectID,
+ VipSubnetID: vipSubnetID,
+ VipPortID: vipPortID,
+ }
+
+ return actuator.osClient.ListLoadBalancers(ctx, listOpts), reconcileStatus
+}
+
+func (actuator loadbalancerActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) {
+ resource := obj.Spec.Resource
+
+ if resource == nil {
+ // Should have been caught by API validation
+ return nil, progress.WrapError(
+ orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Creation requested, but spec.resource is not set"))
+ }
+ var reconcileStatus progress.ReconcileStatus
+
+ var vipSubnetID string
+ if resource.VipSubnetRef != nil {
+ subnet, subnetDepRS := subnetDependency.GetDependency(
+ ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Subnet) bool {
+ return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
+ },
+ )
+ reconcileStatus = reconcileStatus.WithReconcileStatus(subnetDepRS)
+ if subnet != nil {
+ vipSubnetID = ptr.Deref(subnet.Status.ID, "")
+ }
+ }
+
+ var vipNetworkID string
+ if resource.VipNetworkRef != nil {
+ network, networkDepRS := networkDependency.GetDependency(
+ ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Network) bool {
+ return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
+ },
+ )
+ reconcileStatus = reconcileStatus.WithReconcileStatus(networkDepRS)
+ if network != nil {
+ vipNetworkID = ptr.Deref(network.Status.ID, "")
+ }
+ }
+
+ var vipPortID string
+ if resource.VipPortRef != nil {
+ port, portDepRS := portDependency.GetDependency(
+ ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Port) bool {
+ return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
+ },
+ )
+ reconcileStatus = reconcileStatus.WithReconcileStatus(portDepRS)
+ if port != nil {
+ vipPortID = ptr.Deref(port.Status.ID, "")
+ }
+ }
+
+ var flavorID string
+ if resource.FlavorRef != nil {
+ flavor, flavorDepRS := flavorDependency.GetDependency(
+ ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Flavor) bool {
+ return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
+ },
+ )
+ reconcileStatus = reconcileStatus.WithReconcileStatus(flavorDepRS)
+ if flavor != nil {
+ flavorID = ptr.Deref(flavor.Status.ID, "")
+ }
+ }
+
+ var projectID string
+ if resource.ProjectRef != nil {
+ project, projectDepRS := projectDependency.GetDependency(
+ ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Project) bool {
+ return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
+ },
+ )
+ reconcileStatus = reconcileStatus.WithReconcileStatus(projectDepRS)
+ if project != nil {
+ projectID = ptr.Deref(project.Status.ID, "")
+ }
+ }
+ if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
+ return nil, reconcileStatus
+ }
+
+ tags := make([]string, len(resource.Tags))
+ for i := range resource.Tags {
+ tags[i] = string(resource.Tags[i])
+ }
+ // Sort tags before creation to simplify comparisons
+ slices.Sort(tags)
+
+ createOpts := loadbalancers.CreateOpts{
+ Name: getResourceName(obj),
+ Description: ptr.Deref(resource.Description, ""),
+ VipSubnetID: vipSubnetID,
+ VipNetworkID: vipNetworkID,
+ VipPortID: vipPortID,
+ FlavorID: flavorID,
+ ProjectID: projectID,
+ AdminStateUp: resource.AdminStateUp,
+ AvailabilityZone: resource.AvailabilityZone,
+ Provider: resource.Provider,
+ VipAddress: string(ptr.Deref(resource.VipAddress, "")),
+ Tags: tags,
+ }
+
+ osResource, err := actuator.osClient.CreateLoadBalancer(ctx, createOpts)
+ if err != nil {
+ // We should require the spec to be updated before retrying a create which returned a conflict
+ if !orcerrors.IsRetryable(err) {
+ err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err)
+ }
+ return nil, progress.WrapError(err)
+ }
+
+ return osResource, nil
+}
+
+func (actuator loadbalancerActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus {
+ return progress.WrapError(actuator.osClient.DeleteLoadBalancer(ctx, resource.ID))
+}
+
+func (actuator loadbalancerActuator) updateResource(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus {
+ log := ctrl.LoggerFrom(ctx)
+ resource := obj.Spec.Resource
+ if resource == nil {
+ // Should have been caught by API validation
+ return progress.WrapError(
+ orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Update requested, but spec.resource is not set"))
+ }
+
+ updateOpts := loadbalancers.UpdateOpts{}
+
+ handleNameUpdate(&updateOpts, obj, osResource)
+ handleDescriptionUpdate(&updateOpts, resource, osResource)
+ handleAdminStateUpdate(&updateOpts, resource, osResource)
+ handleTagsUpdate(&updateOpts, resource, osResource)
+
+ needsUpdate, err := needsUpdate(updateOpts)
+ if err != nil {
+ return progress.WrapError(
+ orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err))
+ }
+ if !needsUpdate {
+ log.V(logging.Debug).Info("No changes")
+ return nil
+ }
+
+ _, err = actuator.osClient.UpdateLoadBalancer(ctx, osResource.ID, updateOpts)
+
+ // We should require the spec to be updated before retrying an update which returned a conflict
+ if orcerrors.IsConflict(err) {
+ err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err)
+ }
+
+ if err != nil {
+ return progress.WrapError(err)
+ }
+
+ return progress.NeedsRefresh()
+}
+
+func needsUpdate(updateOpts loadbalancers.UpdateOpts) (bool, error) {
+ updateOptsMap, err := updateOpts.ToLoadBalancerUpdateMap()
+ if err != nil {
+ return false, err
+ }
+
+ updateMap, ok := updateOptsMap["load_balancer"].(map[string]any)
+ if !ok {
+ updateMap = make(map[string]any)
+ }
+
+ return len(updateMap) > 0, nil
+}
+
+func handleNameUpdate(updateOpts *loadbalancers.UpdateOpts, obj orcObjectPT, osResource *osResourceT) {
+ name := getResourceName(obj)
+ if osResource.Name != name {
+ updateOpts.Name = &name
+ }
+}
+
+func handleDescriptionUpdate(updateOpts *loadbalancers.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) {
+ description := ptr.Deref(resource.Description, "")
+ if osResource.Description != description {
+ updateOpts.Description = &description
+ }
+}
+
+func handleAdminStateUpdate(updateOpts *loadbalancers.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) {
+ AdminStateUp := *resource.AdminStateUp
+ if osResource.AdminStateUp != AdminStateUp {
+ updateOpts.AdminStateUp = &AdminStateUp
+ }
+}
+
+func handleTagsUpdate(updateOpts *loadbalancers.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) {
+ desiredTags := make([]string, len(resource.Tags))
+ for i, tag := range resource.Tags {
+ desiredTags[i] = string(tag)
+ }
+
+ slices.Sort(desiredTags)
+ slices.Sort(osResource.Tags)
+
+ if !slices.Equal(desiredTags, osResource.Tags) {
+ updateOpts.Tags = &desiredTags
+ }
+}
+
+func (actuator loadbalancerActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) {
+ return []resourceReconciler{
+ actuator.updateResource,
+ }, nil
+}
+
+type loadbalancerHelperFactory struct{}
+
+var _ helperFactory = loadbalancerHelperFactory{}
+
+func newActuator(ctx context.Context, orcObject *orcv1alpha1.LoadBalancer, controller interfaces.ResourceController) (loadbalancerActuator, progress.ReconcileStatus) {
+ log := ctrl.LoggerFrom(ctx)
+
+ // Ensure credential secrets exist and have our finalizer
+ _, reconcileStatus := credentialsDependency.GetDependencies(ctx, controller.GetK8sClient(), orcObject, func(*corev1.Secret) bool { return true })
+ if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
+ return loadbalancerActuator{}, reconcileStatus
+ }
+
+ clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject)
+ if err != nil {
+ return loadbalancerActuator{}, progress.WrapError(err)
+ }
+ osClient, err := clientScope.NewLoadBalancerClient()
+ if err != nil {
+ return loadbalancerActuator{}, progress.WrapError(err)
+ }
+
+ return loadbalancerActuator{
+ osClient: osClient,
+ k8sClient: controller.GetK8sClient(),
+ }, nil
+}
+
+func (loadbalancerHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI {
+ return loadbalancerAdapter{obj}
+}
+
+func (loadbalancerHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) {
+ return newActuator(ctx, orcObject, controller)
+}
+
+func (loadbalancerHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) {
+ return newActuator(ctx, orcObject, controller)
+}
diff --git a/internal/controllers/loadbalancer/actuator_test.go b/internal/controllers/loadbalancer/actuator_test.go
new file mode 100644
index 000000000..99f5fc76f
--- /dev/null
+++ b/internal/controllers/loadbalancer/actuator_test.go
@@ -0,0 +1,119 @@
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package loadbalancer
+
+import (
+ "testing"
+
+ "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/loadbalancers"
+ orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ "k8s.io/utils/ptr"
+)
+
+func TestNeedsUpdate(t *testing.T) {
+ testCases := []struct {
+ name string
+ updateOpts loadbalancers.UpdateOpts
+ expectChange bool
+ }{
+ {
+ name: "Empty base opts",
+ updateOpts: loadbalancers.UpdateOpts{},
+ expectChange: false,
+ },
+ {
+ name: "Updated opts",
+ updateOpts: loadbalancers.UpdateOpts{Name: ptr.To("updated")},
+ expectChange: true,
+ },
+ }
+
+ for _, tt := range testCases {
+ t.Run(tt.name, func(t *testing.T) {
+ got, _ := needsUpdate(tt.updateOpts)
+ if got != tt.expectChange {
+ t.Errorf("Expected change: %v, got: %v", tt.expectChange, got)
+ }
+ })
+ }
+}
+
+func TestHandleNameUpdate(t *testing.T) {
+ ptrToName := ptr.To[orcv1alpha1.OpenStackName]
+ testCases := []struct {
+ name string
+ newValue *orcv1alpha1.OpenStackName
+ existingValue string
+ expectChange bool
+ }{
+ {name: "Identical", newValue: ptrToName("name"), existingValue: "name", expectChange: false},
+ {name: "Different", newValue: ptrToName("new-name"), existingValue: "name", expectChange: true},
+ {name: "No value provided, existing is identical to object name", newValue: nil, existingValue: "object-name", expectChange: false},
+ {name: "No value provided, existing is different from object name", newValue: nil, existingValue: "different-from-object-name", expectChange: true},
+ }
+
+ for _, tt := range testCases {
+ t.Run(tt.name, func(t *testing.T) {
+ resource := &orcv1alpha1.LoadBalancer{}
+ resource.Name = "object-name"
+ resource.Spec = orcv1alpha1.LoadBalancerSpec{
+ Resource: &orcv1alpha1.LoadBalancerResourceSpec{Name: tt.newValue},
+ }
+ osResource := &osResourceT{Name: tt.existingValue}
+
+ updateOpts := loadbalancers.UpdateOpts{}
+ handleNameUpdate(&updateOpts, resource, osResource)
+
+ got, _ := needsUpdate(updateOpts)
+ if got != tt.expectChange {
+ t.Errorf("Expected change: %v, got: %v", tt.expectChange, got)
+ }
+ })
+
+ }
+}
+
+func TestHandleDescriptionUpdate(t *testing.T) {
+ ptrToDescription := ptr.To[string]
+ testCases := []struct {
+ name string
+ newValue *string
+ existingValue string
+ expectChange bool
+ }{
+ {name: "Identical", newValue: ptrToDescription("desc"), existingValue: "desc", expectChange: false},
+ {name: "Different", newValue: ptrToDescription("new-desc"), existingValue: "desc", expectChange: true},
+ {name: "No value provided, existing is set", newValue: nil, existingValue: "desc", expectChange: true},
+ {name: "No value provided, existing is empty", newValue: nil, existingValue: "", expectChange: false},
+ }
+
+ for _, tt := range testCases {
+ t.Run(tt.name, func(t *testing.T) {
+ resource := &orcv1alpha1.LoadBalancerResourceSpec{Description: tt.newValue}
+ osResource := &osResourceT{Description: tt.existingValue}
+
+ updateOpts := loadbalancers.UpdateOpts{}
+ handleDescriptionUpdate(&updateOpts, resource, osResource)
+
+ got, _ := needsUpdate(updateOpts)
+ if got != tt.expectChange {
+ t.Errorf("Expected change: %v, got: %v", tt.expectChange, got)
+ }
+ })
+
+ }
+}
diff --git a/internal/controllers/loadbalancer/controller.go b/internal/controllers/loadbalancer/controller.go
new file mode 100644
index 000000000..93d031c84
--- /dev/null
+++ b/internal/controllers/loadbalancer/controller.go
@@ -0,0 +1,177 @@
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package loadbalancer
+
+import (
+ "context"
+ "errors"
+
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/builder"
+ "sigs.k8s.io/controller-runtime/pkg/controller"
+
+ orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/reconciler"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/scope"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/util/credentials"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency"
+ "github.com/k-orc/openstack-resource-controller/v2/pkg/predicates"
+)
+
+const controllerName = "loadbalancer"
+
+// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=loadbalancers,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=loadbalancers/status,verbs=get;update;patch
+
+type loadbalancerReconcilerConstructor struct {
+ scopeFactory scope.Factory
+}
+
+func New(scopeFactory scope.Factory) interfaces.Controller {
+ return loadbalancerReconcilerConstructor{scopeFactory: scopeFactory}
+}
+
+func (loadbalancerReconcilerConstructor) GetName() string {
+ return controllerName
+}
+
+var subnetDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.LoadBalancerList, *orcv1alpha1.Subnet](
+ "spec.resource.subnetRef",
+ func(loadbalancer *orcv1alpha1.LoadBalancer) []string {
+ resource := loadbalancer.Spec.Resource
+ if resource == nil || resource.VipSubnetRef == nil {
+ return nil
+ }
+ return []string{string(*resource.VipSubnetRef)}
+ },
+ finalizer, externalObjectFieldOwner,
+)
+
+var networkDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.LoadBalancerList, *orcv1alpha1.Network](
+ "spec.resource.networkRef",
+ func(loadbalancer *orcv1alpha1.LoadBalancer) []string {
+ resource := loadbalancer.Spec.Resource
+ if resource == nil || resource.VipNetworkRef == nil {
+ return nil
+ }
+ return []string{string(*resource.VipNetworkRef)}
+ },
+ finalizer, externalObjectFieldOwner,
+)
+
+var portDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.LoadBalancerList, *orcv1alpha1.Port](
+ "spec.resource.portRef",
+ func(loadbalancer *orcv1alpha1.LoadBalancer) []string {
+ resource := loadbalancer.Spec.Resource
+ if resource == nil || resource.VipPortRef == nil {
+ return nil
+ }
+ return []string{string(*resource.VipPortRef)}
+ },
+ finalizer, externalObjectFieldOwner,
+)
+
+var flavorDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.LoadBalancerList, *orcv1alpha1.Flavor](
+ "spec.resource.flavorRef",
+ func(loadbalancer *orcv1alpha1.LoadBalancer) []string {
+ resource := loadbalancer.Spec.Resource
+ if resource == nil || resource.FlavorRef == nil {
+ return nil
+ }
+ return []string{string(*resource.FlavorRef)}
+ },
+ finalizer, externalObjectFieldOwner,
+)
+
+var projectDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.LoadBalancerList, *orcv1alpha1.Project](
+ "spec.resource.projectRef",
+ func(loadbalancer *orcv1alpha1.LoadBalancer) []string {
+ resource := loadbalancer.Spec.Resource
+ if resource == nil || resource.ProjectRef == nil {
+ return nil
+ }
+ return []string{string(*resource.ProjectRef)}
+ },
+ finalizer, externalObjectFieldOwner,
+)
+
+// SetupWithManager sets up the controller with the Manager.
+func (c loadbalancerReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
+ log := ctrl.LoggerFrom(ctx)
+ k8sClient := mgr.GetClient()
+
+ subnetWatchEventHandler, err := subnetDependency.WatchEventHandler(log, k8sClient)
+ if err != nil {
+ return err
+ }
+
+ networkWatchEventHandler, err := networkDependency.WatchEventHandler(log, k8sClient)
+ if err != nil {
+ return err
+ }
+
+ portWatchEventHandler, err := portDependency.WatchEventHandler(log, k8sClient)
+ if err != nil {
+ return err
+ }
+
+ flavorWatchEventHandler, err := flavorDependency.WatchEventHandler(log, k8sClient)
+ if err != nil {
+ return err
+ }
+
+ projectWatchEventHandler, err := projectDependency.WatchEventHandler(log, k8sClient)
+ if err != nil {
+ return err
+ }
+
+ builder := ctrl.NewControllerManagedBy(mgr).
+ WithOptions(options).
+ Watches(&orcv1alpha1.Subnet{}, subnetWatchEventHandler,
+ builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Subnet{})),
+ ).
+ Watches(&orcv1alpha1.Network{}, networkWatchEventHandler,
+ builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Network{})),
+ ).
+ Watches(&orcv1alpha1.Port{}, portWatchEventHandler,
+ builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Port{})),
+ ).
+ Watches(&orcv1alpha1.Flavor{}, flavorWatchEventHandler,
+ builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Flavor{})),
+ ).
+ Watches(&orcv1alpha1.Project{}, projectWatchEventHandler,
+ builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})),
+ ).
+ For(&orcv1alpha1.LoadBalancer{})
+
+ if err := errors.Join(
+ subnetDependency.AddToManager(ctx, mgr),
+ networkDependency.AddToManager(ctx, mgr),
+ portDependency.AddToManager(ctx, mgr),
+ flavorDependency.AddToManager(ctx, mgr),
+ projectDependency.AddToManager(ctx, mgr),
+ credentialsDependency.AddToManager(ctx, mgr),
+ credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency),
+ ); err != nil {
+ return err
+ }
+
+ r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, loadbalancerHelperFactory{}, loadbalancerStatusWriter{})
+ return builder.Complete(&r)
+}
diff --git a/internal/controllers/loadbalancer/status.go b/internal/controllers/loadbalancer/status.go
new file mode 100644
index 000000000..60b414a94
--- /dev/null
+++ b/internal/controllers/loadbalancer/status.go
@@ -0,0 +1,77 @@
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package loadbalancer
+
+import (
+ "github.com/go-logr/logr"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress"
+ orcapplyconfigv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1"
+)
+
+type loadbalancerStatusWriter struct{}
+
+type objectApplyT = orcapplyconfigv1alpha1.LoadBalancerApplyConfiguration
+type statusApplyT = orcapplyconfigv1alpha1.LoadBalancerStatusApplyConfiguration
+
+var _ interfaces.ResourceStatusWriter[*orcv1alpha1.LoadBalancer, *osResourceT, *objectApplyT, *statusApplyT] = loadbalancerStatusWriter{}
+
+func (loadbalancerStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT {
+ return orcapplyconfigv1alpha1.LoadBalancer(name, namespace)
+}
+
+func (loadbalancerStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.LoadBalancer, osResource *osResourceT) (metav1.ConditionStatus, progress.ReconcileStatus) {
+ if osResource == nil {
+ if orcObject.Status.ID == nil {
+ return metav1.ConditionFalse, nil
+ }
+ return metav1.ConditionUnknown, nil
+ }
+
+ switch osResource.ProvisioningStatus {
+ case "ACTIVE":
+ return metav1.ConditionTrue, nil
+ case "ERROR":
+ return metav1.ConditionFalse, nil
+ default:
+ // PENDING_CREATE, PENDING_UPDATE, PENDING_DELETE
+ return metav1.ConditionFalse, progress.WaitingOnOpenStack(progress.WaitingOnReady, loadbalancerActivePollingPeriod)
+ }
+}
+
+func (loadbalancerStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) {
+ resourceStatus := orcapplyconfigv1alpha1.LoadBalancerResourceStatus().
+ WithName(osResource.Name).
+ WithDescription(osResource.Description).
+ WithVipSubnetID(osResource.VipSubnetID).
+ WithVipNetworkID(osResource.VipNetworkID).
+ WithVipPortID(osResource.VipPortID).
+ WithVipAddress(osResource.VipAddress).
+ WithFlavorID(osResource.FlavorID).
+ WithProjectID(osResource.ProjectID).
+ WithAdminStateUp(osResource.AdminStateUp).
+ WithProvider(osResource.Provider).
+ WithAvailabilityZone(osResource.AvailabilityZone).
+ WithProvisioningStatus(osResource.ProvisioningStatus).
+ WithOperatingStatus(osResource.OperatingStatus).
+ WithTags(osResource.Tags...)
+
+ statusApply.WithResource(resourceStatus)
+}
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-assert.yaml
new file mode 100644
index 000000000..8f1586922
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-assert.yaml
@@ -0,0 +1,52 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-create-full
+status:
+ resource:
+ name: loadbalancer-create-full-override
+ description: LoadBalancer from "create full" test
+ adminStateUp: true
+ tags:
+ - tag1
+ - tag2
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: LoadBalancer
+ name: loadbalancer-create-full
+ ref: loadbalancer
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Subnet
+ name: loadbalancer-create-full
+ ref: subnet
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Network
+ name: loadbalancer-create-full
+ ref: network
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Port
+ name: loadbalancer-create-full
+ ref: port
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Project
+ name: loadbalancer-create-full
+ ref: project
+assertAll:
+ - celExpr: "loadbalancer.status.id != ''"
+ - celExpr: "loadbalancer.status.resource.vipSubnetID == subnet.status.id"
+ - celExpr: "loadbalancer.status.resource.vipNetworkID == network.status.id"
+ - celExpr: "loadbalancer.status.resource.vipPortID == port.status.id"
+ - celExpr: "loadbalancer.status.resource.projectID == project.status.id"
+ - celExpr: "loadbalancer.status.resource.vipAddress != ''"
+ - celExpr: "loadbalancer.status.resource.provisioningStatus == 'ACTIVE'"
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-create-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-create-resource.yaml
new file mode 100644
index 000000000..66605a006
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-create-resource.yaml
@@ -0,0 +1,72 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Project
+metadata:
+ name: loadbalancer-create-full
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Network
+metadata:
+ name: loadbalancer-create-full
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: loadbalancer-create-full
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: loadbalancer-create-full
+ ipVersion: 4
+ cidr: 10.0.0.0/24
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Port
+metadata:
+ name: loadbalancer-create-full
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: loadbalancer-create-full
+ addresses:
+ - subnetRef: loadbalancer-create-full
+ ip: 10.0.0.10
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-create-full
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ name: loadbalancer-create-full-override
+ description: LoadBalancer from "create full" test
+ vipSubnetRef: loadbalancer-create-full
+ vipNetworkRef: loadbalancer-create-full
+ vipPortRef: loadbalancer-create-full
+ projectRef: loadbalancer-create-full
+ adminStateUp: true
+ tags:
+ - tag1
+ - tag2
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-secret.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-secret.yaml
new file mode 100644
index 000000000..045711ee7
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-secret.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-create-full/README.md b/internal/controllers/loadbalancer/tests/loadbalancer-create-full/README.md
new file mode 100644
index 000000000..44bd88dfc
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-full/README.md
@@ -0,0 +1,11 @@
+# Create a LoadBalancer with all the options
+
+## Step 00
+
+Create a LoadBalancer using all available fields, and verify that the observed state corresponds to the spec.
+
+Also validate that the OpenStack resource uses the name from the spec when it is specified.
+
+## Reference
+
+https://k-orc.cloud/development/writing-tests/#create-full
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-assert.yaml
new file mode 100644
index 000000000..9e9ef45f8
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-assert.yaml
@@ -0,0 +1,31 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-create-minimal
+status:
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: LoadBalancer
+ name: loadbalancer-create-minimal
+ ref: loadbalancer
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Subnet
+ name: loadbalancer-create-minimal
+ ref: subnet
+assertAll:
+ - celExpr: "loadbalancer.status.id != ''"
+ - celExpr: "loadbalancer.status.resource.name == 'loadbalancer-create-minimal'"
+ - celExpr: "loadbalancer.status.resource.vipSubnetID == subnet.status.id"
+ - celExpr: "loadbalancer.status.resource.vipAddress != ''"
+ - celExpr: "loadbalancer.status.resource.provisioningStatus == 'ACTIVE'"
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-create-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-create-resource.yaml
new file mode 100644
index 000000000..d527aa280
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-create-resource.yaml
@@ -0,0 +1,37 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Network
+metadata:
+ name: loadbalancer-create-minimal
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: loadbalancer-create-minimal
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: loadbalancer-create-minimal
+ ipVersion: 4
+ cidr: 10.0.0.0/24
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-create-minimal
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: loadbalancer-create-minimal
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-secret.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-secret.yaml
new file mode 100644
index 000000000..045711ee7
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-secret.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/01-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/01-assert.yaml
new file mode 100644
index 000000000..c116ae4b8
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/01-assert.yaml
@@ -0,0 +1,11 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: v1
+ kind: Secret
+ name: openstack-clouds
+ ref: secret
+assertAll:
+ - celExpr: "secret.metadata.deletionTimestamp != 0"
+ - celExpr: "'openstack.k-orc.cloud/loadbalancer' in secret.metadata.finalizers"
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/01-delete-secret.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/01-delete-secret.yaml
new file mode 100644
index 000000000..1620791b9
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/01-delete-secret.yaml
@@ -0,0 +1,7 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ # We expect the deletion to hang due to the finalizer, so use --wait=false
+ - command: kubectl delete secret openstack-clouds --wait=false
+ namespaced: true
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/README.md b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/README.md
new file mode 100644
index 000000000..07e0ca5b6
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/README.md
@@ -0,0 +1,15 @@
+# Create a LoadBalancer with the minimum options
+
+## Step 00
+
+Create a minimal LoadBalancer, that sets only the required fields, and verify that the observed state corresponds to the spec.
+
+Also validate that the OpenStack resource uses the name of the ORC object when no name is explicitly specified.
+
+## Step 01
+
+Try deleting the secret and ensure that it is not deleted thanks to the finalizer.
+
+## Reference
+
+https://k-orc.cloud/development/writing-tests/#create-minimal
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-assert.yaml
new file mode 100644
index 000000000..50720584f
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-assert.yaml
@@ -0,0 +1,75 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-dependency-no-secret
+status:
+ conditions:
+ - type: Available
+ message: Waiting for Secret/loadbalancer-dependency to be created
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: Waiting for Secret/loadbalancer-dependency to be created
+ status: "True"
+ reason: Progressing
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-dependency-no-subnet
+status:
+ conditions:
+ - type: Available
+ message: Waiting for Subnet/loadbalancer-dependency to be created
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: Waiting for Subnet/loadbalancer-dependency to be created
+ status: "True"
+ reason: Progressing
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-dependency-no-network
+status:
+ conditions:
+ - type: Available
+ message: Waiting for Network/loadbalancer-dependency to be created
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: Waiting for Network/loadbalancer-dependency to be created
+ status: "True"
+ reason: Progressing
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-dependency-no-port
+status:
+ conditions:
+ - type: Available
+ message: Waiting for Port/loadbalancer-dependency to be created
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: Waiting for Port/loadbalancer-dependency to be created
+ status: "True"
+ reason: Progressing
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-dependency-no-project
+status:
+ conditions:
+ - type: Available
+ message: Waiting for Project/loadbalancer-dependency to be created
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: Waiting for Project/loadbalancer-dependency to be created
+ status: "True"
+ reason: Progressing
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-create-resources-missing-deps.yaml
new file mode 100644
index 000000000..f2ee54961
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-create-resources-missing-deps.yaml
@@ -0,0 +1,61 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-dependency-no-subnet
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: loadbalancer-dependency
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-dependency-no-network
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipNetworkRef: loadbalancer-dependency
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-dependency-no-port
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipPortRef: loadbalancer-dependency
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-dependency-no-project
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: loadbalancer-dependency-subnet
+ projectRef: loadbalancer-dependency
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-dependency-no-secret
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: loadbalancer-dependency
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: loadbalancer-dependency-subnet
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-secret.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-secret.yaml
new file mode 100644
index 000000000..045711ee7
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-secret.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/01-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/01-assert.yaml
new file mode 100644
index 000000000..ec74c08e9
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/01-assert.yaml
@@ -0,0 +1,65 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-dependency-no-secret
+status:
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-dependency-no-subnet
+status:
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-dependency-no-network
+status:
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-dependency-no-port
+status:
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-dependency-no-project
+status:
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/01-create-dependencies.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/01-create-dependencies.yaml
new file mode 100644
index 000000000..4bd2487e4
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/01-create-dependencies.yaml
@@ -0,0 +1,71 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic loadbalancer-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Project
+metadata:
+ name: loadbalancer-dependency
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Network
+metadata:
+ name: loadbalancer-dependency
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: loadbalancer-dependency
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: loadbalancer-dependency
+ ipVersion: 4
+ cidr: 10.0.0.0/24
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: loadbalancer-dependency-subnet
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: loadbalancer-dependency
+ ipVersion: 4
+ cidr: 10.0.1.0/24
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Port
+metadata:
+ name: loadbalancer-dependency
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: loadbalancer-dependency
+ addresses:
+ - subnetRef: loadbalancer-dependency
+ ip: 10.0.0.10
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/02-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/02-assert.yaml
new file mode 100644
index 000000000..850000719
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/02-assert.yaml
@@ -0,0 +1,41 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Subnet
+ name: loadbalancer-dependency
+ ref: subnet
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Subnet
+ name: loadbalancer-dependency-subnet
+ ref: subnet2
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Network
+ name: loadbalancer-dependency
+ ref: network
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Port
+ name: loadbalancer-dependency
+ ref: port
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Project
+ name: loadbalancer-dependency
+ ref: project
+ - apiVersion: v1
+ kind: Secret
+ name: loadbalancer-dependency
+ ref: secret
+assertAll:
+ - celExpr: "subnet.metadata.deletionTimestamp != 0"
+ - celExpr: "'openstack.k-orc.cloud/loadbalancer' in subnet.metadata.finalizers"
+ - celExpr: "subnet2.metadata.deletionTimestamp != 0"
+ - celExpr: "'openstack.k-orc.cloud/loadbalancer' in subnet2.metadata.finalizers"
+ - celExpr: "network.metadata.deletionTimestamp != 0"
+ - celExpr: "'openstack.k-orc.cloud/loadbalancer' in network.metadata.finalizers"
+ - celExpr: "port.metadata.deletionTimestamp != 0"
+ - celExpr: "'openstack.k-orc.cloud/loadbalancer' in port.metadata.finalizers"
+ - celExpr: "project.metadata.deletionTimestamp != 0"
+ - celExpr: "'openstack.k-orc.cloud/loadbalancer' in project.metadata.finalizers"
+ - celExpr: "secret.metadata.deletionTimestamp != 0"
+ - celExpr: "'openstack.k-orc.cloud/loadbalancer' in secret.metadata.finalizers"
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/02-delete-dependencies.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/02-delete-dependencies.yaml
new file mode 100644
index 000000000..ab613419e
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/02-delete-dependencies.yaml
@@ -0,0 +1,17 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ # We expect the deletion to hang due to the finalizer, so use --wait=false
+ - command: kubectl delete subnet loadbalancer-dependency --wait=false
+ namespaced: true
+ - command: kubectl delete subnet loadbalancer-dependency-subnet --wait=false
+ namespaced: true
+ - command: kubectl delete network loadbalancer-dependency --wait=false
+ namespaced: true
+ - command: kubectl delete port loadbalancer-dependency --wait=false
+ namespaced: true
+ - command: kubectl delete project loadbalancer-dependency --wait=false
+ namespaced: true
+ - command: kubectl delete secret loadbalancer-dependency --wait=false
+ namespaced: true
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/03-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/03-assert.yaml
new file mode 100644
index 000000000..26f0781b8
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/03-assert.yaml
@@ -0,0 +1,17 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+commands:
+# Dependencies that were prevented deletion before should now be gone
+- script: "! kubectl get subnet loadbalancer-dependency --namespace $NAMESPACE"
+ skipLogOutput: true
+- script: "! kubectl get subnet loadbalancer-dependency-subnet --namespace $NAMESPACE"
+ skipLogOutput: true
+- script: "! kubectl get network loadbalancer-dependency --namespace $NAMESPACE"
+ skipLogOutput: true
+- script: "! kubectl get port loadbalancer-dependency --namespace $NAMESPACE"
+ skipLogOutput: true
+- script: "! kubectl get project loadbalancer-dependency --namespace $NAMESPACE"
+ skipLogOutput: true
+- script: "! kubectl get secret loadbalancer-dependency --namespace $NAMESPACE"
+ skipLogOutput: true
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/03-delete-resources.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/03-delete-resources.yaml
new file mode 100644
index 000000000..b4eb49b0c
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/03-delete-resources.yaml
@@ -0,0 +1,19 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+delete:
+- apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: LoadBalancer
+ name: loadbalancer-dependency-no-secret
+- apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: LoadBalancer
+ name: loadbalancer-dependency-no-subnet
+- apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: LoadBalancer
+ name: loadbalancer-dependency-no-network
+- apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: LoadBalancer
+ name: loadbalancer-dependency-no-port
+- apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: LoadBalancer
+ name: loadbalancer-dependency-no-project
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/README.md b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/README.md
new file mode 100644
index 000000000..b1a05802f
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/README.md
@@ -0,0 +1,21 @@
+# Creation and deletion dependencies
+
+## Step 00
+
+Create LoadBalancers referencing non-existing resources. Each LoadBalancer is dependent on other non-existing resource. Verify that the LoadBalancers are waiting for the needed resources to be created externally.
+
+## Step 01
+
+Create the missing dependencies and verify all the LoadBalancers are available.
+
+## Step 02
+
+Delete all the dependencies and check that ORC prevents deletion since there is still a resource that depends on them.
+
+## Step 03
+
+Delete the LoadBalancers and validate that all resources are gone.
+
+## Reference
+
+https://k-orc.cloud/development/writing-tests/#dependency
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-assert.yaml
new file mode 100644
index 000000000..3f41cc651
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-assert.yaml
@@ -0,0 +1,26 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-import-error-external-1
+status:
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-import-error-external-2
+status:
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-create-resources.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-create-resources.yaml
new file mode 100644
index 000000000..94cf2dd6b
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-create-resources.yaml
@@ -0,0 +1,51 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Network
+metadata:
+ name: loadbalancer-import-error
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: loadbalancer-import-error
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: loadbalancer-import-error
+ ipVersion: 4
+ cidr: 10.0.0.0/24
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-import-error-external-1
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: loadbalancer-import-error
+ description: LoadBalancer from "import error" test
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-import-error-external-2
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: loadbalancer-import-error
+ description: LoadBalancer from "import error" test
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-secret.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-secret.yaml
new file mode 100644
index 000000000..045711ee7
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-secret.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-error/01-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/01-assert.yaml
new file mode 100644
index 000000000..1629ec940
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/01-assert.yaml
@@ -0,0 +1,15 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-import-error
+status:
+ conditions:
+ - type: Available
+ message: found more than one matching OpenStack resource during import
+ status: "False"
+ reason: InvalidConfiguration
+ - type: Progressing
+ message: found more than one matching OpenStack resource during import
+ status: "False"
+ reason: InvalidConfiguration
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-error/01-import-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/01-import-resource.yaml
new file mode 100644
index 000000000..23918c0b0
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/01-import-resource.yaml
@@ -0,0 +1,13 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-import-error
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: unmanaged
+ import:
+ filter:
+ description: LoadBalancer from "import error" test
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-error/README.md b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/README.md
new file mode 100644
index 000000000..3f9d4cf84
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/README.md
@@ -0,0 +1,13 @@
+# Import LoadBalancer with more than one matching resources
+
+## Step 00
+
+Create two LoadBalancers with identical specs.
+
+## Step 01
+
+Ensure that an imported LoadBalancer with a filter matching the resources returns an error.
+
+## Reference
+
+https://k-orc.cloud/development/writing-tests/#import-error
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import/00-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import/00-assert.yaml
new file mode 100644
index 000000000..86f108f68
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/00-assert.yaml
@@ -0,0 +1,15 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-import
+status:
+ conditions:
+ - type: Available
+ message: Waiting for OpenStack resource to be created externally
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: Waiting for OpenStack resource to be created externally
+ status: "True"
+ reason: Progressing
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import/00-import-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import/00-import-resource.yaml
new file mode 100644
index 000000000..93c311aeb
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/00-import-resource.yaml
@@ -0,0 +1,39 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Network
+metadata:
+ name: loadbalancer-import
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: loadbalancer-import
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: loadbalancer-import
+ ipVersion: 4
+ cidr: 10.0.0.0/24
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-import
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: unmanaged
+ import:
+ filter:
+ name: loadbalancer-import-external
+ description: LoadBalancer loadbalancer-import-external from "loadbalancer-import" test
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import/00-secret.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import/00-secret.yaml
new file mode 100644
index 000000000..045711ee7
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/00-secret.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import/01-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import/01-assert.yaml
new file mode 100644
index 000000000..d7d034bd0
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/01-assert.yaml
@@ -0,0 +1,31 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-import-external-not-this-one
+status:
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
+ resource:
+ name: loadbalancer-import-external-not-this-one
+ description: LoadBalancer loadbalancer-import-external from "loadbalancer-import" test
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-import
+status:
+ conditions:
+ - type: Available
+ message: Waiting for OpenStack resource to be created externally
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: Waiting for OpenStack resource to be created externally
+ status: "True"
+ reason: Progressing
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import/01-create-trap-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import/01-create-trap-resource.yaml
new file mode 100644
index 000000000..6b4f82a35
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/01-create-trap-resource.yaml
@@ -0,0 +1,16 @@
+---
+# This `loadbalancer-import-external-not-this-one` resource serves two purposes:
+# - ensure that we can successfully create another resource which name is a substring of it (i.e. it's not being adopted)
+# - ensure that importing a resource which name is a substring of it will not pick this one.
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-import-external-not-this-one
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: loadbalancer-import
+ description: LoadBalancer loadbalancer-import-external from "loadbalancer-import" test
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import/02-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import/02-assert.yaml
new file mode 100644
index 000000000..17e021a11
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/02-assert.yaml
@@ -0,0 +1,35 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: LoadBalancer
+ name: loadbalancer-import-external
+ ref: loadbalancer1
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: LoadBalancer
+ name: loadbalancer-import-external-not-this-one
+ ref: loadbalancer2
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: LoadBalancer
+ name: loadbalancer-import
+ ref: loadbalancerImport
+assertAll:
+ - celExpr: "loadbalancer1.status.id != loadbalancer2.status.id"
+ - celExpr: "loadbalancerImport.status.id == loadbalancer1.status.id"
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-import
+status:
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
+ resource:
+ name: loadbalancer-import-external
+ description: LoadBalancer loadbalancer-import-external from "loadbalancer-import" test
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import/02-create-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import/02-create-resource.yaml
new file mode 100644
index 000000000..a0ca7b241
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/02-create-resource.yaml
@@ -0,0 +1,13 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-import-external
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: loadbalancer-import
+ description: LoadBalancer loadbalancer-import-external from "loadbalancer-import" test
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import/README.md b/internal/controllers/loadbalancer/tests/loadbalancer-import/README.md
new file mode 100644
index 000000000..090224429
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/README.md
@@ -0,0 +1,18 @@
+# Import LoadBalancer
+
+## Step 00
+
+Import a loadbalancer that matches all fields in the filter, and verify it is waiting for the external resource to be created.
+
+## Step 01
+
+Create a loadbalancer whose name is a superstring of the one specified in the import filter, otherwise matching the filter, and verify that it's not being imported.
+
+## Step 02
+
+Create a loadbalancer matching the filter and verify that the observed status on the imported loadbalancer corresponds to the spec of the created loadbalancer.
+Also, confirm that it does not adopt any loadbalancer whose name is a superstring of its own.
+
+## Reference
+
+https://k-orc.cloud/development/writing-tests/#import
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-update/00-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-update/00-assert.yaml
new file mode 100644
index 000000000..554c1157d
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/00-assert.yaml
@@ -0,0 +1,25 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: LoadBalancer
+ name: loadbalancer-update
+ ref: loadbalancer
+assertAll:
+ - celExpr: "loadbalancer.status.resource.description == ''"
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-update
+status:
+ resource:
+ name: loadbalancer-update
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-update/00-minimal-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-update/00-minimal-resource.yaml
new file mode 100644
index 000000000..cfa04301b
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/00-minimal-resource.yaml
@@ -0,0 +1,12 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-update
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ vipSubnetRef: loadbalancer-update
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-update/00-prerequisites.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-update/00-prerequisites.yaml
new file mode 100644
index 000000000..5d74c652d
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/00-prerequisites.yaml
@@ -0,0 +1,31 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Network
+metadata:
+ name: loadbalancer-update
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: loadbalancer-update
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: loadbalancer-update
+ ipVersion: 4
+ cidr: 10.0.0.0/24
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-update/01-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-update/01-assert.yaml
new file mode 100644
index 000000000..bdfe8f077
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/01-assert.yaml
@@ -0,0 +1,19 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-update
+status:
+ resource:
+ name: loadbalancer-update-updated
+ description: loadbalancer-update-updated
+ adminStateUp: false
+ tags:
+ - updated-tag
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-update/01-updated-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-update/01-updated-resource.yaml
new file mode 100644
index 000000000..b8fc612de
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/01-updated-resource.yaml
@@ -0,0 +1,13 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-update
+spec:
+ resource:
+ vipSubnetRef: loadbalancer-update
+ name: loadbalancer-update-updated
+ description: loadbalancer-update-updated
+ adminStateUp: false
+ tags:
+ - updated-tag
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-update/02-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-update/02-assert.yaml
new file mode 100644
index 000000000..ea257400d
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/02-assert.yaml
@@ -0,0 +1,26 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: LoadBalancer
+ name: loadbalancer-update
+ ref: loadbalancer
+assertAll:
+ - celExpr: "loadbalancer.status.resource.description == ''"
+ - celExpr: "loadbalancer.status.resource.tags.size() == 0"
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: LoadBalancer
+metadata:
+ name: loadbalancer-update
+status:
+ resource:
+ name: loadbalancer-update
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-update/02-reverted-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-update/02-reverted-resource.yaml
new file mode 100644
index 000000000..2c6c253ff
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/02-reverted-resource.yaml
@@ -0,0 +1,7 @@
+# NOTE: kuttl only does patch updates, which means we can't delete a field.
+# We have to use a kubectl apply command instead.
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl replace -f 00-minimal-resource.yaml
+ namespaced: true
diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-update/README.md b/internal/controllers/loadbalancer/tests/loadbalancer-update/README.md
new file mode 100644
index 000000000..9dafb19c4
--- /dev/null
+++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/README.md
@@ -0,0 +1,17 @@
+# Update LoadBalancer
+
+## Step 00
+
+Create a LoadBalancer using only mandatory fields.
+
+## Step 01
+
+Update all mutable fields.
+
+## Step 02
+
+Revert the resource to its original value and verify that the resulting object matches its state when first created.
+
+## Reference
+
+https://k-orc.cloud/development/writing-tests/#update
diff --git a/internal/controllers/loadbalancer/zz_generated.adapter.go b/internal/controllers/loadbalancer/zz_generated.adapter.go
new file mode 100644
index 000000000..d9ca9b0ff
--- /dev/null
+++ b/internal/controllers/loadbalancer/zz_generated.adapter.go
@@ -0,0 +1,88 @@
+// Code generated by resource-generator. DO NOT EDIT.
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package loadbalancer
+
+import (
+ orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces"
+)
+
+// Fundamental types
+type (
+ orcObjectT = orcv1alpha1.LoadBalancer
+ orcObjectListT = orcv1alpha1.LoadBalancerList
+ resourceSpecT = orcv1alpha1.LoadBalancerResourceSpec
+ filterT = orcv1alpha1.LoadBalancerFilter
+)
+
+// Derived types
+type (
+ orcObjectPT = *orcObjectT
+ adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT]
+ adapterT = loadbalancerAdapter
+)
+
+type loadbalancerAdapter struct {
+ *orcv1alpha1.LoadBalancer
+}
+
+var _ adapterI = &adapterT{}
+
+func (f adapterT) GetObject() orcObjectPT {
+ return f.LoadBalancer
+}
+
+func (f adapterT) GetManagementPolicy() orcv1alpha1.ManagementPolicy {
+ return f.Spec.ManagementPolicy
+}
+
+func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions {
+ return f.Spec.ManagedOptions
+}
+
+func (f adapterT) GetStatusID() *string {
+ return f.Status.ID
+}
+
+func (f adapterT) GetResourceSpec() *resourceSpecT {
+ return f.Spec.Resource
+}
+
+func (f adapterT) GetImportID() *string {
+ if f.Spec.Import == nil {
+ return nil
+ }
+ return f.Spec.Import.ID
+}
+
+func (f adapterT) GetImportFilter() *filterT {
+ if f.Spec.Import == nil {
+ return nil
+ }
+ return f.Spec.Import.Filter
+}
+
+// getResourceName returns the name of the OpenStack resource we should use.
+// This method is not implemented as part of APIObjectAdapter as it is intended
+// to be used by resource actuators, which don't use the adapter.
+func getResourceName(orcObject orcObjectPT) string {
+ if orcObject.Spec.Resource.Name != nil {
+ return string(*orcObject.Spec.Resource.Name)
+ }
+ return orcObject.Name
+}
diff --git a/internal/controllers/loadbalancer/zz_generated.controller.go b/internal/controllers/loadbalancer/zz_generated.controller.go
new file mode 100644
index 000000000..beb29a768
--- /dev/null
+++ b/internal/controllers/loadbalancer/zz_generated.controller.go
@@ -0,0 +1,45 @@
+// Code generated by resource-generator. DO NOT EDIT.
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package loadbalancer
+
+import (
+ corev1 "k8s.io/api/core/v1"
+
+ "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency"
+ orcstrings "github.com/k-orc/openstack-resource-controller/v2/internal/util/strings"
+)
+
+var (
+ // NOTE: controllerName must be defined in any controller using this template
+
+ // finalizer is the string this controller adds to an object's Finalizers
+ finalizer = orcstrings.GetFinalizerName(controllerName)
+
+ // externalObjectFieldOwner is the field owner we use when using
+ // server-side-apply on objects we don't control
+ externalObjectFieldOwner = orcstrings.GetSSAFieldOwner(controllerName)
+
+ credentialsDependency = dependency.NewDeletionGuardDependency[*orcObjectListT, *corev1.Secret](
+ "spec.cloudCredentialsRef.secretName",
+ func(obj orcObjectPT) []string {
+ return []string{obj.Spec.CloudCredentialsRef.SecretName}
+ },
+ finalizer, externalObjectFieldOwner,
+ dependency.OverrideDependencyName("credentials"),
+ )
+)
diff --git a/internal/osclients/listener.go b/internal/osclients/listener.go
new file mode 100644
index 000000000..848e93a21
--- /dev/null
+++ b/internal/osclients/listener.go
@@ -0,0 +1,104 @@
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package osclients
+
+import (
+ "context"
+ "fmt"
+ "iter"
+
+ "github.com/gophercloud/gophercloud/v2"
+ "github.com/gophercloud/gophercloud/v2/openstack"
+ "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/listeners"
+ "github.com/gophercloud/utils/v2/openstack/clientconfig"
+)
+
+type ListenerClient interface {
+ ListListeners(ctx context.Context, listOpts listeners.ListOptsBuilder) iter.Seq2[*listeners.Listener, error]
+ CreateListener(ctx context.Context, opts listeners.CreateOptsBuilder) (*listeners.Listener, error)
+ DeleteListener(ctx context.Context, resourceID string) error
+ GetListener(ctx context.Context, resourceID string) (*listeners.Listener, error)
+ UpdateListener(ctx context.Context, id string, opts listeners.UpdateOptsBuilder) (*listeners.Listener, error)
+}
+
+type listenerClient struct{ client *gophercloud.ServiceClient }
+
+// NewListenerClient returns a new OpenStack client.
+func NewListenerClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (ListenerClient, error) {
+ client, err := openstack.NewLoadBalancerV2(providerClient, gophercloud.EndpointOpts{
+ Region: providerClientOpts.RegionName,
+ Availability: clientconfig.GetEndpointType(providerClientOpts.EndpointType),
+ })
+
+ if err != nil {
+ return nil, fmt.Errorf("failed to create listener service client: %v", err)
+ }
+
+ return &listenerClient{client}, nil
+}
+
+func (c listenerClient) ListListeners(ctx context.Context, listOpts listeners.ListOptsBuilder) iter.Seq2[*listeners.Listener, error] {
+ pager := listeners.List(c.client, listOpts)
+ return func(yield func(*listeners.Listener, error) bool) {
+ _ = pager.EachPage(ctx, yieldPage(listeners.ExtractListeners, yield))
+ }
+}
+
+func (c listenerClient) CreateListener(ctx context.Context, opts listeners.CreateOptsBuilder) (*listeners.Listener, error) {
+ return listeners.Create(ctx, c.client, opts).Extract()
+}
+
+func (c listenerClient) DeleteListener(ctx context.Context, resourceID string) error {
+ return listeners.Delete(ctx, c.client, resourceID).ExtractErr()
+}
+
+func (c listenerClient) GetListener(ctx context.Context, resourceID string) (*listeners.Listener, error) {
+ return listeners.Get(ctx, c.client, resourceID).Extract()
+}
+
+func (c listenerClient) UpdateListener(ctx context.Context, id string, opts listeners.UpdateOptsBuilder) (*listeners.Listener, error) {
+ return listeners.Update(ctx, c.client, id, opts).Extract()
+}
+
+type listenerErrorClient struct{ error }
+
+// NewListenerErrorClient returns a ListenerClient in which every method returns the given error.
+func NewListenerErrorClient(e error) ListenerClient {
+ return listenerErrorClient{e}
+}
+
+func (e listenerErrorClient) ListListeners(_ context.Context, _ listeners.ListOptsBuilder) iter.Seq2[*listeners.Listener, error] {
+ return func(yield func(*listeners.Listener, error) bool) {
+ yield(nil, e.error)
+ }
+}
+
+func (e listenerErrorClient) CreateListener(_ context.Context, _ listeners.CreateOptsBuilder) (*listeners.Listener, error) {
+ return nil, e.error
+}
+
+func (e listenerErrorClient) DeleteListener(_ context.Context, _ string) error {
+ return e.error
+}
+
+func (e listenerErrorClient) GetListener(_ context.Context, _ string) (*listeners.Listener, error) {
+ return nil, e.error
+}
+
+func (e listenerErrorClient) UpdateListener(_ context.Context, _ string, _ listeners.UpdateOptsBuilder) (*listeners.Listener, error) {
+ return nil, e.error
+}
diff --git a/internal/osclients/loadbalancer.go b/internal/osclients/loadbalancer.go
new file mode 100644
index 000000000..7d26da8f8
--- /dev/null
+++ b/internal/osclients/loadbalancer.go
@@ -0,0 +1,104 @@
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package osclients
+
+import (
+ "context"
+ "fmt"
+ "iter"
+
+ "github.com/gophercloud/gophercloud/v2"
+ "github.com/gophercloud/gophercloud/v2/openstack"
+ "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/loadbalancers"
+ "github.com/gophercloud/utils/v2/openstack/clientconfig"
+)
+
+type LoadBalancerClient interface {
+ ListLoadBalancers(ctx context.Context, listOpts loadbalancers.ListOptsBuilder) iter.Seq2[*loadbalancers.LoadBalancer, error]
+ CreateLoadBalancer(ctx context.Context, opts loadbalancers.CreateOptsBuilder) (*loadbalancers.LoadBalancer, error)
+ DeleteLoadBalancer(ctx context.Context, resourceID string) error
+ GetLoadBalancer(ctx context.Context, resourceID string) (*loadbalancers.LoadBalancer, error)
+ UpdateLoadBalancer(ctx context.Context, id string, opts loadbalancers.UpdateOptsBuilder) (*loadbalancers.LoadBalancer, error)
+}
+
+type loadbalancerClient struct{ client *gophercloud.ServiceClient }
+
+// NewLoadBalancerClient returns a new OpenStack client.
+func NewLoadBalancerClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (LoadBalancerClient, error) {
+ client, err := openstack.NewLoadBalancerV2(providerClient, gophercloud.EndpointOpts{
+ Region: providerClientOpts.RegionName,
+ Availability: clientconfig.GetEndpointType(providerClientOpts.EndpointType),
+ })
+
+ if err != nil {
+ return nil, fmt.Errorf("failed to create loadbalancer service client: %v", err)
+ }
+
+ return &loadbalancerClient{client}, nil
+}
+
+func (c loadbalancerClient) ListLoadBalancers(ctx context.Context, listOpts loadbalancers.ListOptsBuilder) iter.Seq2[*loadbalancers.LoadBalancer, error] {
+ pager := loadbalancers.List(c.client, listOpts)
+ return func(yield func(*loadbalancers.LoadBalancer, error) bool) {
+ _ = pager.EachPage(ctx, yieldPage(loadbalancers.ExtractLoadBalancers, yield))
+ }
+}
+
+func (c loadbalancerClient) CreateLoadBalancer(ctx context.Context, opts loadbalancers.CreateOptsBuilder) (*loadbalancers.LoadBalancer, error) {
+ return loadbalancers.Create(ctx, c.client, opts).Extract()
+}
+
+func (c loadbalancerClient) DeleteLoadBalancer(ctx context.Context, resourceID string) error {
+ return loadbalancers.Delete(ctx, c.client, resourceID, nil).ExtractErr()
+}
+
+func (c loadbalancerClient) GetLoadBalancer(ctx context.Context, resourceID string) (*loadbalancers.LoadBalancer, error) {
+ return loadbalancers.Get(ctx, c.client, resourceID).Extract()
+}
+
+func (c loadbalancerClient) UpdateLoadBalancer(ctx context.Context, id string, opts loadbalancers.UpdateOptsBuilder) (*loadbalancers.LoadBalancer, error) {
+ return loadbalancers.Update(ctx, c.client, id, opts).Extract()
+}
+
+type loadbalancerErrorClient struct{ error }
+
+// NewLoadBalancerErrorClient returns a LoadBalancerClient in which every method returns the given error.
+func NewLoadBalancerErrorClient(e error) LoadBalancerClient {
+ return loadbalancerErrorClient{e}
+}
+
+func (e loadbalancerErrorClient) ListLoadBalancers(_ context.Context, _ loadbalancers.ListOptsBuilder) iter.Seq2[*loadbalancers.LoadBalancer, error] {
+ return func(yield func(*loadbalancers.LoadBalancer, error) bool) {
+ yield(nil, e.error)
+ }
+}
+
+func (e loadbalancerErrorClient) CreateLoadBalancer(_ context.Context, _ loadbalancers.CreateOptsBuilder) (*loadbalancers.LoadBalancer, error) {
+ return nil, e.error
+}
+
+func (e loadbalancerErrorClient) DeleteLoadBalancer(_ context.Context, _ string) error {
+ return e.error
+}
+
+func (e loadbalancerErrorClient) GetLoadBalancer(_ context.Context, _ string) (*loadbalancers.LoadBalancer, error) {
+ return nil, e.error
+}
+
+func (e loadbalancerErrorClient) UpdateLoadBalancer(_ context.Context, _ string, _ loadbalancers.UpdateOptsBuilder) (*loadbalancers.LoadBalancer, error) {
+ return nil, e.error
+}
diff --git a/internal/osclients/mock/doc.go b/internal/osclients/mock/doc.go
index 47292b65f..e28fcd062 100644
--- a/internal/osclients/mock/doc.go
+++ b/internal/osclients/mock/doc.go
@@ -44,6 +44,12 @@ import (
//go:generate mockgen -package mock -destination=keypair.go -source=../keypair.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock KeyPairClient
//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt keypair.go > _keypair.go && mv _keypair.go keypair.go"
+//go:generate mockgen -package mock -destination=listener.go -source=../listener.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock ListenerClient
+//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt listener.go > _listener.go && mv _listener.go listener.go"
+
+//go:generate mockgen -package mock -destination=loadbalancer.go -source=../loadbalancer.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock LoadBalancerClient
+//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt loadbalancer.go > _loadbalancer.go && mv _loadbalancer.go loadbalancer.go"
+
//go:generate mockgen -package mock -destination=role.go -source=../role.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock RoleClient
//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt role.go > _role.go && mv _role.go role.go"
diff --git a/internal/osclients/mock/listener.go b/internal/osclients/mock/listener.go
new file mode 100644
index 000000000..e9ab390e0
--- /dev/null
+++ b/internal/osclients/mock/listener.go
@@ -0,0 +1,131 @@
+/*
+Copyright 2025 The ORC 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 MockGen. DO NOT EDIT.
+// Source: ../listener.go
+//
+// Generated by this command:
+//
+// mockgen -package mock -destination=listener.go -source=../listener.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock ListenerClient
+//
+
+// Package mock is a generated GoMock package.
+package mock
+
+import (
+ context "context"
+ iter "iter"
+ reflect "reflect"
+
+ listeners "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/listeners"
+ gomock "go.uber.org/mock/gomock"
+)
+
+// MockListenerClient is a mock of ListenerClient interface.
+type MockListenerClient struct {
+ ctrl *gomock.Controller
+ recorder *MockListenerClientMockRecorder
+ isgomock struct{}
+}
+
+// MockListenerClientMockRecorder is the mock recorder for MockListenerClient.
+type MockListenerClientMockRecorder struct {
+ mock *MockListenerClient
+}
+
+// NewMockListenerClient creates a new mock instance.
+func NewMockListenerClient(ctrl *gomock.Controller) *MockListenerClient {
+ mock := &MockListenerClient{ctrl: ctrl}
+ mock.recorder = &MockListenerClientMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockListenerClient) EXPECT() *MockListenerClientMockRecorder {
+ return m.recorder
+}
+
+// CreateListener mocks base method.
+func (m *MockListenerClient) CreateListener(ctx context.Context, opts listeners.CreateOptsBuilder) (*listeners.Listener, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "CreateListener", ctx, opts)
+ ret0, _ := ret[0].(*listeners.Listener)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// CreateListener indicates an expected call of CreateListener.
+func (mr *MockListenerClientMockRecorder) CreateListener(ctx, opts any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateListener", reflect.TypeOf((*MockListenerClient)(nil).CreateListener), ctx, opts)
+}
+
+// DeleteListener mocks base method.
+func (m *MockListenerClient) DeleteListener(ctx context.Context, resourceID string) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "DeleteListener", ctx, resourceID)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// DeleteListener indicates an expected call of DeleteListener.
+func (mr *MockListenerClientMockRecorder) DeleteListener(ctx, resourceID any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteListener", reflect.TypeOf((*MockListenerClient)(nil).DeleteListener), ctx, resourceID)
+}
+
+// GetListener mocks base method.
+func (m *MockListenerClient) GetListener(ctx context.Context, resourceID string) (*listeners.Listener, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetListener", ctx, resourceID)
+ ret0, _ := ret[0].(*listeners.Listener)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetListener indicates an expected call of GetListener.
+func (mr *MockListenerClientMockRecorder) GetListener(ctx, resourceID any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetListener", reflect.TypeOf((*MockListenerClient)(nil).GetListener), ctx, resourceID)
+}
+
+// ListListeners mocks base method.
+func (m *MockListenerClient) ListListeners(ctx context.Context, listOpts listeners.ListOptsBuilder) iter.Seq2[*listeners.Listener, error] {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "ListListeners", ctx, listOpts)
+ ret0, _ := ret[0].(iter.Seq2[*listeners.Listener, error])
+ return ret0
+}
+
+// ListListeners indicates an expected call of ListListeners.
+func (mr *MockListenerClientMockRecorder) ListListeners(ctx, listOpts any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListListeners", reflect.TypeOf((*MockListenerClient)(nil).ListListeners), ctx, listOpts)
+}
+
+// UpdateListener mocks base method.
+func (m *MockListenerClient) UpdateListener(ctx context.Context, id string, opts listeners.UpdateOptsBuilder) (*listeners.Listener, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "UpdateListener", ctx, id, opts)
+ ret0, _ := ret[0].(*listeners.Listener)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// UpdateListener indicates an expected call of UpdateListener.
+func (mr *MockListenerClientMockRecorder) UpdateListener(ctx, id, opts any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateListener", reflect.TypeOf((*MockListenerClient)(nil).UpdateListener), ctx, id, opts)
+}
diff --git a/internal/osclients/mock/loadbalancer.go b/internal/osclients/mock/loadbalancer.go
new file mode 100644
index 000000000..20afabd63
--- /dev/null
+++ b/internal/osclients/mock/loadbalancer.go
@@ -0,0 +1,131 @@
+/*
+Copyright 2025 The ORC 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 MockGen. DO NOT EDIT.
+// Source: ../loadbalancer.go
+//
+// Generated by this command:
+//
+// mockgen -package mock -destination=loadbalancer.go -source=../loadbalancer.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock LoadBalancerClient
+//
+
+// Package mock is a generated GoMock package.
+package mock
+
+import (
+ context "context"
+ iter "iter"
+ reflect "reflect"
+
+ loadbalancers "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/loadbalancers"
+ gomock "go.uber.org/mock/gomock"
+)
+
+// MockLoadBalancerClient is a mock of LoadBalancerClient interface.
+type MockLoadBalancerClient struct {
+ ctrl *gomock.Controller
+ recorder *MockLoadBalancerClientMockRecorder
+ isgomock struct{}
+}
+
+// MockLoadBalancerClientMockRecorder is the mock recorder for MockLoadBalancerClient.
+type MockLoadBalancerClientMockRecorder struct {
+ mock *MockLoadBalancerClient
+}
+
+// NewMockLoadBalancerClient creates a new mock instance.
+func NewMockLoadBalancerClient(ctrl *gomock.Controller) *MockLoadBalancerClient {
+ mock := &MockLoadBalancerClient{ctrl: ctrl}
+ mock.recorder = &MockLoadBalancerClientMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockLoadBalancerClient) EXPECT() *MockLoadBalancerClientMockRecorder {
+ return m.recorder
+}
+
+// CreateLoadBalancer mocks base method.
+func (m *MockLoadBalancerClient) CreateLoadBalancer(ctx context.Context, opts loadbalancers.CreateOptsBuilder) (*loadbalancers.LoadBalancer, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "CreateLoadBalancer", ctx, opts)
+ ret0, _ := ret[0].(*loadbalancers.LoadBalancer)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// CreateLoadBalancer indicates an expected call of CreateLoadBalancer.
+func (mr *MockLoadBalancerClientMockRecorder) CreateLoadBalancer(ctx, opts any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLoadBalancer", reflect.TypeOf((*MockLoadBalancerClient)(nil).CreateLoadBalancer), ctx, opts)
+}
+
+// DeleteLoadBalancer mocks base method.
+func (m *MockLoadBalancerClient) DeleteLoadBalancer(ctx context.Context, resourceID string) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "DeleteLoadBalancer", ctx, resourceID)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// DeleteLoadBalancer indicates an expected call of DeleteLoadBalancer.
+func (mr *MockLoadBalancerClientMockRecorder) DeleteLoadBalancer(ctx, resourceID any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLoadBalancer", reflect.TypeOf((*MockLoadBalancerClient)(nil).DeleteLoadBalancer), ctx, resourceID)
+}
+
+// GetLoadBalancer mocks base method.
+func (m *MockLoadBalancerClient) GetLoadBalancer(ctx context.Context, resourceID string) (*loadbalancers.LoadBalancer, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetLoadBalancer", ctx, resourceID)
+ ret0, _ := ret[0].(*loadbalancers.LoadBalancer)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetLoadBalancer indicates an expected call of GetLoadBalancer.
+func (mr *MockLoadBalancerClientMockRecorder) GetLoadBalancer(ctx, resourceID any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLoadBalancer", reflect.TypeOf((*MockLoadBalancerClient)(nil).GetLoadBalancer), ctx, resourceID)
+}
+
+// ListLoadBalancers mocks base method.
+func (m *MockLoadBalancerClient) ListLoadBalancers(ctx context.Context, listOpts loadbalancers.ListOptsBuilder) iter.Seq2[*loadbalancers.LoadBalancer, error] {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "ListLoadBalancers", ctx, listOpts)
+ ret0, _ := ret[0].(iter.Seq2[*loadbalancers.LoadBalancer, error])
+ return ret0
+}
+
+// ListLoadBalancers indicates an expected call of ListLoadBalancers.
+func (mr *MockLoadBalancerClientMockRecorder) ListLoadBalancers(ctx, listOpts any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLoadBalancers", reflect.TypeOf((*MockLoadBalancerClient)(nil).ListLoadBalancers), ctx, listOpts)
+}
+
+// UpdateLoadBalancer mocks base method.
+func (m *MockLoadBalancerClient) UpdateLoadBalancer(ctx context.Context, id string, opts loadbalancers.UpdateOptsBuilder) (*loadbalancers.LoadBalancer, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "UpdateLoadBalancer", ctx, id, opts)
+ ret0, _ := ret[0].(*loadbalancers.LoadBalancer)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// UpdateLoadBalancer indicates an expected call of UpdateLoadBalancer.
+func (mr *MockLoadBalancerClientMockRecorder) UpdateLoadBalancer(ctx, id, opts any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLoadBalancer", reflect.TypeOf((*MockLoadBalancerClient)(nil).UpdateLoadBalancer), ctx, id, opts)
+}
diff --git a/internal/scope/mock.go b/internal/scope/mock.go
index ef959fae5..e36cbc95f 100644
--- a/internal/scope/mock.go
+++ b/internal/scope/mock.go
@@ -34,17 +34,19 @@ import (
// MockScopeFactory implements both the ScopeFactory and ClientScope interfaces. It can be used in place of the default ProviderScopeFactory
// when we want to use mocked service clients which do not attempt to connect to a running OpenStack cloud.
type MockScopeFactory struct {
- ComputeClient *mock.MockComputeClient
- DomainClient *mock.MockDomainClient
- GroupClient *mock.MockGroupClient
- IdentityClient *mock.MockIdentityClient
- ImageClient *mock.MockImageClient
- KeyPairClient *mock.MockKeyPairClient
- NetworkClient *mock.MockNetworkClient
- RoleClient *mock.MockRoleClient
- ServiceClient *mock.MockServiceClient
- VolumeClient *mock.MockVolumeClient
- VolumeTypeClient *mock.MockVolumeTypeClient
+ ComputeClient *mock.MockComputeClient
+ DomainClient *mock.MockDomainClient
+ GroupClient *mock.MockGroupClient
+ IdentityClient *mock.MockIdentityClient
+ ImageClient *mock.MockImageClient
+ KeyPairClient *mock.MockKeyPairClient
+ ListenerClient *mock.MockListenerClient
+ LoadBalancerClient *mock.MockLoadBalancerClient
+ NetworkClient *mock.MockNetworkClient
+ RoleClient *mock.MockRoleClient
+ ServiceClient *mock.MockServiceClient
+ VolumeClient *mock.MockVolumeClient
+ VolumeTypeClient *mock.MockVolumeTypeClient
clientScopeCreateError error
}
@@ -56,24 +58,28 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory {
identityClient := mock.NewMockIdentityClient(mockCtrl)
imageClient := mock.NewMockImageClient(mockCtrl)
keypairClient := mock.NewMockKeyPairClient(mockCtrl)
+ listenerClient := mock.NewMockListenerClient(mockCtrl)
networkClient := mock.NewMockNetworkClient(mockCtrl)
roleClient := mock.NewMockRoleClient(mockCtrl)
serviceClient := mock.NewMockServiceClient(mockCtrl)
volumeClient := mock.NewMockVolumeClient(mockCtrl)
volumetypeClient := mock.NewMockVolumeTypeClient(mockCtrl)
+ loadBalancerClient := mock.NewMockLoadBalancerClient(mockCtrl)
return &MockScopeFactory{
- ComputeClient: computeClient,
- DomainClient: domainClient,
- GroupClient: groupClient,
- IdentityClient: identityClient,
- ImageClient: imageClient,
- KeyPairClient: keypairClient,
- NetworkClient: networkClient,
- RoleClient: roleClient,
- ServiceClient: serviceClient,
- VolumeClient: volumeClient,
- VolumeTypeClient: volumetypeClient,
+ ComputeClient: computeClient,
+ DomainClient: domainClient,
+ GroupClient: groupClient,
+ IdentityClient: identityClient,
+ ImageClient: imageClient,
+ KeyPairClient: keypairClient,
+ ListenerClient: listenerClient,
+ LoadBalancerClient: loadBalancerClient,
+ NetworkClient: networkClient,
+ RoleClient: roleClient,
+ ServiceClient: serviceClient,
+ VolumeClient: volumeClient,
+ VolumeTypeClient: volumetypeClient,
}
}
@@ -132,6 +138,14 @@ func (f *MockScopeFactory) NewRoleClient() (osclients.RoleClient, error) {
return f.RoleClient, nil
}
+func (f *MockScopeFactory) NewListenerClient() (osclients.ListenerClient, error) {
+ return f.ListenerClient, nil
+}
+
+func (f *MockScopeFactory) NewLoadBalancerClient() (osclients.LoadBalancerClient, error) {
+ return f.LoadBalancerClient, nil
+}
+
func (f *MockScopeFactory) ExtractToken() (*tokens.Token, error) {
return &tokens.Token{ExpiresAt: time.Now().Add(24 * time.Hour)}, nil
}
diff --git a/internal/scope/provider.go b/internal/scope/provider.go
index 65670ba60..85500f662 100644
--- a/internal/scope/provider.go
+++ b/internal/scope/provider.go
@@ -181,6 +181,14 @@ func (s *providerScope) NewRoleClient() (clients.RoleClient, error) {
return clients.NewRoleClient(s.providerClient, s.providerClientOpts)
}
+func (s *providerScope) NewListenerClient() (clients.ListenerClient, error) {
+ return clients.NewListenerClient(s.providerClient, s.providerClientOpts)
+}
+
+func (s *providerScope) NewLoadBalancerClient() (clients.LoadBalancerClient, error) {
+ return clients.NewLoadBalancerClient(s.providerClient, s.providerClientOpts)
+}
+
func (s *providerScope) ExtractToken() (*tokens.Token, error) {
client, err := openstack.NewIdentityV3(s.providerClient, gophercloud.EndpointOpts{})
if err != nil {
diff --git a/internal/scope/scope.go b/internal/scope/scope.go
index 7da50dc8f..736bd21ef 100644
--- a/internal/scope/scope.go
+++ b/internal/scope/scope.go
@@ -59,6 +59,8 @@ type Scope interface {
NewServiceClient() (osclients.ServiceClient, error)
NewVolumeClient() (osclients.VolumeClient, error)
NewVolumeTypeClient() (osclients.VolumeTypeClient, error)
+ NewListenerClient() (osclients.ListenerClient, error)
+ NewLoadBalancerClient() (osclients.LoadBalancerClient, error)
ExtractToken() (*tokens.Token, error)
}
diff --git a/internal/util/dependency/helpers.go b/internal/util/dependency/helpers.go
new file mode 100644
index 000000000..7d4da0484
--- /dev/null
+++ b/internal/util/dependency/helpers.go
@@ -0,0 +1,66 @@
+/*
+Copyright 2025 The ORC 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.
+*/
+
+package dependency
+
+import (
+ "context"
+ "fmt"
+
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress"
+)
+
+// FetchDependency fetches a resource by name and checks if it's ready.
+// Unlike GetDependency on DeletionGuardDependency, this doesn't add finalizers
+// and is suitable for one-off lookups like resolving refs in import filters.
+//
+// If name is empty, returns (nil, nil) - no fetch is performed.
+//
+// Returns:
+// - The fetched object (nil if name is empty, not found, or not ready)
+// - ReconcileStatus indicating wait state or error (nil if name is empty)
+func FetchDependency[TP DependencyType[T], T any](
+ ctx context.Context,
+ k8sClient client.Client,
+ namespace string,
+ name *orcv1alpha1.KubernetesNameRef,
+ kind string,
+ isReady func(TP) bool,
+) (TP, progress.ReconcileStatus) {
+ if name == nil {
+ return nil, nil
+ }
+
+ var obj TP = new(T)
+ objectKey := client.ObjectKey{Name: string(*name), Namespace: namespace}
+
+ if err := k8sClient.Get(ctx, objectKey, obj); err != nil {
+ if apierrors.IsNotFound(err) {
+ return nil, progress.NewReconcileStatus().WaitingOnObject(kind, string(*name), progress.WaitingOnCreation)
+ }
+ return nil, progress.WrapError(fmt.Errorf("fetching %s %s: %w", kind, string(*name), err))
+ }
+
+ if !isReady(obj) {
+ return nil, progress.NewReconcileStatus().WaitingOnObject(kind, string(*name), progress.WaitingOnReady)
+ }
+
+ return obj, nil
+}
diff --git a/kuttl-test.yaml b/kuttl-test.yaml
index d499782e6..44deb3f05 100644
--- a/kuttl-test.yaml
+++ b/kuttl-test.yaml
@@ -8,6 +8,8 @@ testDirs:
- ./internal/controllers/group/tests/
- ./internal/controllers/image/tests/
- ./internal/controllers/keypair/tests/
+- ./internal/controllers/listener/tests/
+- ./internal/controllers/loadbalancer/tests/
- ./internal/controllers/network/tests/
- ./internal/controllers/port/tests/
- ./internal/controllers/project/tests/
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/listener.go b/pkg/clients/applyconfiguration/api/v1alpha1/listener.go
new file mode 100644
index 000000000..1ec1d6a2d
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/listener.go
@@ -0,0 +1,281 @@
+/*
+Copyright 2025 The ORC 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ internal "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/internal"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ types "k8s.io/apimachinery/pkg/types"
+ managedfields "k8s.io/apimachinery/pkg/util/managedfields"
+ v1 "k8s.io/client-go/applyconfigurations/meta/v1"
+)
+
+// ListenerApplyConfiguration represents a declarative configuration of the Listener type for use
+// with apply.
+type ListenerApplyConfiguration struct {
+ v1.TypeMetaApplyConfiguration `json:",inline"`
+ *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"`
+ Spec *ListenerSpecApplyConfiguration `json:"spec,omitempty"`
+ Status *ListenerStatusApplyConfiguration `json:"status,omitempty"`
+}
+
+// Listener constructs a declarative configuration of the Listener type for use with
+// apply.
+func Listener(name, namespace string) *ListenerApplyConfiguration {
+ b := &ListenerApplyConfiguration{}
+ b.WithName(name)
+ b.WithNamespace(namespace)
+ b.WithKind("Listener")
+ b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1")
+ return b
+}
+
+// ExtractListener extracts the applied configuration owned by fieldManager from
+// listener. If no managedFields are found in listener for fieldManager, a
+// ListenerApplyConfiguration is returned with only the Name, Namespace (if applicable),
+// APIVersion and Kind populated. It is possible that no managed fields were found for because other
+// field managers have taken ownership of all the fields previously owned by fieldManager, or because
+// the fieldManager never owned fields any fields.
+// listener must be a unmodified Listener API object that was retrieved from the Kubernetes API.
+// ExtractListener provides a way to perform a extract/modify-in-place/apply workflow.
+// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously
+// applied if another fieldManager has updated or force applied any of the previously applied fields.
+// Experimental!
+func ExtractListener(listener *apiv1alpha1.Listener, fieldManager string) (*ListenerApplyConfiguration, error) {
+ return extractListener(listener, fieldManager, "")
+}
+
+// ExtractListenerStatus is the same as ExtractListener except
+// that it extracts the status subresource applied configuration.
+// Experimental!
+func ExtractListenerStatus(listener *apiv1alpha1.Listener, fieldManager string) (*ListenerApplyConfiguration, error) {
+ return extractListener(listener, fieldManager, "status")
+}
+
+func extractListener(listener *apiv1alpha1.Listener, fieldManager string, subresource string) (*ListenerApplyConfiguration, error) {
+ b := &ListenerApplyConfiguration{}
+ err := managedfields.ExtractInto(listener, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Listener"), fieldManager, b, subresource)
+ if err != nil {
+ return nil, err
+ }
+ b.WithName(listener.Name)
+ b.WithNamespace(listener.Namespace)
+
+ b.WithKind("Listener")
+ b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1")
+ return b, nil
+}
+func (b ListenerApplyConfiguration) IsApplyConfiguration() {}
+
+// WithKind sets the Kind field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Kind field is set to the value of the last call.
+func (b *ListenerApplyConfiguration) WithKind(value string) *ListenerApplyConfiguration {
+ b.TypeMetaApplyConfiguration.Kind = &value
+ return b
+}
+
+// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the APIVersion field is set to the value of the last call.
+func (b *ListenerApplyConfiguration) WithAPIVersion(value string) *ListenerApplyConfiguration {
+ b.TypeMetaApplyConfiguration.APIVersion = &value
+ return b
+}
+
+// WithName sets the Name field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Name field is set to the value of the last call.
+func (b *ListenerApplyConfiguration) WithName(value string) *ListenerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.Name = &value
+ return b
+}
+
+// WithGenerateName sets the GenerateName field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the GenerateName field is set to the value of the last call.
+func (b *ListenerApplyConfiguration) WithGenerateName(value string) *ListenerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.GenerateName = &value
+ return b
+}
+
+// WithNamespace sets the Namespace field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Namespace field is set to the value of the last call.
+func (b *ListenerApplyConfiguration) WithNamespace(value string) *ListenerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.Namespace = &value
+ return b
+}
+
+// WithUID sets the UID field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the UID field is set to the value of the last call.
+func (b *ListenerApplyConfiguration) WithUID(value types.UID) *ListenerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.UID = &value
+ return b
+}
+
+// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ResourceVersion field is set to the value of the last call.
+func (b *ListenerApplyConfiguration) WithResourceVersion(value string) *ListenerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.ResourceVersion = &value
+ return b
+}
+
+// WithGeneration sets the Generation field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Generation field is set to the value of the last call.
+func (b *ListenerApplyConfiguration) WithGeneration(value int64) *ListenerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.Generation = &value
+ return b
+}
+
+// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the CreationTimestamp field is set to the value of the last call.
+func (b *ListenerApplyConfiguration) WithCreationTimestamp(value metav1.Time) *ListenerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.CreationTimestamp = &value
+ return b
+}
+
+// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the DeletionTimestamp field is set to the value of the last call.
+func (b *ListenerApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *ListenerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value
+ return b
+}
+
+// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call.
+func (b *ListenerApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *ListenerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value
+ return b
+}
+
+// WithLabels puts the entries into the Labels field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, the entries provided by each call will be put on the Labels field,
+// overwriting an existing map entries in Labels field with the same key.
+func (b *ListenerApplyConfiguration) WithLabels(entries map[string]string) *ListenerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 {
+ b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries))
+ }
+ for k, v := range entries {
+ b.ObjectMetaApplyConfiguration.Labels[k] = v
+ }
+ return b
+}
+
+// WithAnnotations puts the entries into the Annotations field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, the entries provided by each call will be put on the Annotations field,
+// overwriting an existing map entries in Annotations field with the same key.
+func (b *ListenerApplyConfiguration) WithAnnotations(entries map[string]string) *ListenerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 {
+ b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries))
+ }
+ for k, v := range entries {
+ b.ObjectMetaApplyConfiguration.Annotations[k] = v
+ }
+ return b
+}
+
+// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the OwnerReferences field.
+func (b *ListenerApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *ListenerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ for i := range values {
+ if values[i] == nil {
+ panic("nil value passed to WithOwnerReferences")
+ }
+ b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i])
+ }
+ return b
+}
+
+// WithFinalizers adds the given value to the Finalizers field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the Finalizers field.
+func (b *ListenerApplyConfiguration) WithFinalizers(values ...string) *ListenerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ for i := range values {
+ b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i])
+ }
+ return b
+}
+
+func (b *ListenerApplyConfiguration) ensureObjectMetaApplyConfigurationExists() {
+ if b.ObjectMetaApplyConfiguration == nil {
+ b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{}
+ }
+}
+
+// WithSpec sets the Spec field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Spec field is set to the value of the last call.
+func (b *ListenerApplyConfiguration) WithSpec(value *ListenerSpecApplyConfiguration) *ListenerApplyConfiguration {
+ b.Spec = value
+ return b
+}
+
+// WithStatus sets the Status field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Status field is set to the value of the last call.
+func (b *ListenerApplyConfiguration) WithStatus(value *ListenerStatusApplyConfiguration) *ListenerApplyConfiguration {
+ b.Status = value
+ return b
+}
+
+// GetKind retrieves the value of the Kind field in the declarative configuration.
+func (b *ListenerApplyConfiguration) GetKind() *string {
+ return b.TypeMetaApplyConfiguration.Kind
+}
+
+// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration.
+func (b *ListenerApplyConfiguration) GetAPIVersion() *string {
+ return b.TypeMetaApplyConfiguration.APIVersion
+}
+
+// GetName retrieves the value of the Name field in the declarative configuration.
+func (b *ListenerApplyConfiguration) GetName() *string {
+ b.ensureObjectMetaApplyConfigurationExists()
+ return b.ObjectMetaApplyConfiguration.Name
+}
+
+// GetNamespace retrieves the value of the Namespace field in the declarative configuration.
+func (b *ListenerApplyConfiguration) GetNamespace() *string {
+ b.ensureObjectMetaApplyConfigurationExists()
+ return b.ObjectMetaApplyConfiguration.Namespace
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/listenerfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/listenerfilter.go
new file mode 100644
index 000000000..3d4296faa
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/listenerfilter.go
@@ -0,0 +1,123 @@
+/*
+Copyright 2025 The ORC 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+)
+
+// ListenerFilterApplyConfiguration represents a declarative configuration of the ListenerFilter type for use
+// with apply.
+type ListenerFilterApplyConfiguration struct {
+ Name *apiv1alpha1.OpenStackName `json:"name,omitempty"`
+ Description *string `json:"description,omitempty"`
+ LoadBalancerRef *apiv1alpha1.KubernetesNameRef `json:"loadBalancerRef,omitempty"`
+ Protocol *apiv1alpha1.ListenerProtocol `json:"protocol,omitempty"`
+ ProtocolPort *int32 `json:"protocolPort,omitempty"`
+ Tags []apiv1alpha1.ListenerTag `json:"tags,omitempty"`
+ TagsAny []apiv1alpha1.ListenerTag `json:"tagsAny,omitempty"`
+ NotTags []apiv1alpha1.ListenerTag `json:"notTags,omitempty"`
+ NotTagsAny []apiv1alpha1.ListenerTag `json:"notTagsAny,omitempty"`
+}
+
+// ListenerFilterApplyConfiguration constructs a declarative configuration of the ListenerFilter type for use with
+// apply.
+func ListenerFilter() *ListenerFilterApplyConfiguration {
+ return &ListenerFilterApplyConfiguration{}
+}
+
+// WithName sets the Name field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Name field is set to the value of the last call.
+func (b *ListenerFilterApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *ListenerFilterApplyConfiguration {
+ b.Name = &value
+ return b
+}
+
+// WithDescription sets the Description field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Description field is set to the value of the last call.
+func (b *ListenerFilterApplyConfiguration) WithDescription(value string) *ListenerFilterApplyConfiguration {
+ b.Description = &value
+ return b
+}
+
+// WithLoadBalancerRef sets the LoadBalancerRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the LoadBalancerRef field is set to the value of the last call.
+func (b *ListenerFilterApplyConfiguration) WithLoadBalancerRef(value apiv1alpha1.KubernetesNameRef) *ListenerFilterApplyConfiguration {
+ b.LoadBalancerRef = &value
+ return b
+}
+
+// WithProtocol sets the Protocol field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Protocol field is set to the value of the last call.
+func (b *ListenerFilterApplyConfiguration) WithProtocol(value apiv1alpha1.ListenerProtocol) *ListenerFilterApplyConfiguration {
+ b.Protocol = &value
+ return b
+}
+
+// WithProtocolPort sets the ProtocolPort field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ProtocolPort field is set to the value of the last call.
+func (b *ListenerFilterApplyConfiguration) WithProtocolPort(value int32) *ListenerFilterApplyConfiguration {
+ b.ProtocolPort = &value
+ return b
+}
+
+// WithTags adds the given value to the Tags field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the Tags field.
+func (b *ListenerFilterApplyConfiguration) WithTags(values ...apiv1alpha1.ListenerTag) *ListenerFilterApplyConfiguration {
+ for i := range values {
+ b.Tags = append(b.Tags, values[i])
+ }
+ return b
+}
+
+// WithTagsAny adds the given value to the TagsAny field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the TagsAny field.
+func (b *ListenerFilterApplyConfiguration) WithTagsAny(values ...apiv1alpha1.ListenerTag) *ListenerFilterApplyConfiguration {
+ for i := range values {
+ b.TagsAny = append(b.TagsAny, values[i])
+ }
+ return b
+}
+
+// WithNotTags adds the given value to the NotTags field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the NotTags field.
+func (b *ListenerFilterApplyConfiguration) WithNotTags(values ...apiv1alpha1.ListenerTag) *ListenerFilterApplyConfiguration {
+ for i := range values {
+ b.NotTags = append(b.NotTags, values[i])
+ }
+ return b
+}
+
+// WithNotTagsAny adds the given value to the NotTagsAny field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the NotTagsAny field.
+func (b *ListenerFilterApplyConfiguration) WithNotTagsAny(values ...apiv1alpha1.ListenerTag) *ListenerFilterApplyConfiguration {
+ for i := range values {
+ b.NotTagsAny = append(b.NotTagsAny, values[i])
+ }
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/listenerhsts.go b/pkg/clients/applyconfiguration/api/v1alpha1/listenerhsts.go
new file mode 100644
index 000000000..4a38377ff
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/listenerhsts.go
@@ -0,0 +1,57 @@
+/*
+Copyright 2025 The ORC 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+// ListenerHSTSApplyConfiguration represents a declarative configuration of the ListenerHSTS type for use
+// with apply.
+type ListenerHSTSApplyConfiguration struct {
+ MaxAge *int32 `json:"maxAge,omitempty"`
+ IncludeSubDomains *bool `json:"includeSubDomains,omitempty"`
+ Preload *bool `json:"preload,omitempty"`
+}
+
+// ListenerHSTSApplyConfiguration constructs a declarative configuration of the ListenerHSTS type for use with
+// apply.
+func ListenerHSTS() *ListenerHSTSApplyConfiguration {
+ return &ListenerHSTSApplyConfiguration{}
+}
+
+// WithMaxAge sets the MaxAge field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the MaxAge field is set to the value of the last call.
+func (b *ListenerHSTSApplyConfiguration) WithMaxAge(value int32) *ListenerHSTSApplyConfiguration {
+ b.MaxAge = &value
+ return b
+}
+
+// WithIncludeSubDomains sets the IncludeSubDomains field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the IncludeSubDomains field is set to the value of the last call.
+func (b *ListenerHSTSApplyConfiguration) WithIncludeSubDomains(value bool) *ListenerHSTSApplyConfiguration {
+ b.IncludeSubDomains = &value
+ return b
+}
+
+// WithPreload sets the Preload field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Preload field is set to the value of the last call.
+func (b *ListenerHSTSApplyConfiguration) WithPreload(value bool) *ListenerHSTSApplyConfiguration {
+ b.Preload = &value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/listenerimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/listenerimport.go
new file mode 100644
index 000000000..52e1091cf
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/listenerimport.go
@@ -0,0 +1,48 @@
+/*
+Copyright 2025 The ORC 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+// ListenerImportApplyConfiguration represents a declarative configuration of the ListenerImport type for use
+// with apply.
+type ListenerImportApplyConfiguration struct {
+ ID *string `json:"id,omitempty"`
+ Filter *ListenerFilterApplyConfiguration `json:"filter,omitempty"`
+}
+
+// ListenerImportApplyConfiguration constructs a declarative configuration of the ListenerImport type for use with
+// apply.
+func ListenerImport() *ListenerImportApplyConfiguration {
+ return &ListenerImportApplyConfiguration{}
+}
+
+// WithID sets the ID field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ID field is set to the value of the last call.
+func (b *ListenerImportApplyConfiguration) WithID(value string) *ListenerImportApplyConfiguration {
+ b.ID = &value
+ return b
+}
+
+// WithFilter sets the Filter field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Filter field is set to the value of the last call.
+func (b *ListenerImportApplyConfiguration) WithFilter(value *ListenerFilterApplyConfiguration) *ListenerImportApplyConfiguration {
+ b.Filter = value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/listenerresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/listenerresourcespec.go
new file mode 100644
index 000000000..af297dc18
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/listenerresourcespec.go
@@ -0,0 +1,266 @@
+/*
+Copyright 2025 The ORC 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+)
+
+// ListenerResourceSpecApplyConfiguration represents a declarative configuration of the ListenerResourceSpec type for use
+// with apply.
+type ListenerResourceSpecApplyConfiguration struct {
+ Name *apiv1alpha1.OpenStackName `json:"name,omitempty"`
+ Description *string `json:"description,omitempty"`
+ LoadBalancerRef *apiv1alpha1.KubernetesNameRef `json:"loadBalancerRef,omitempty"`
+ Protocol *apiv1alpha1.ListenerProtocol `json:"protocol,omitempty"`
+ ProtocolPort *int32 `json:"protocolPort,omitempty"`
+ AdminStateUp *bool `json:"adminStateUp,omitempty"`
+ ConnectionLimit *int32 `json:"connectionLimit,omitempty"`
+ DefaultTLSContainerRef *string `json:"defaultTLSContainerRef,omitempty"`
+ SNIContainerRefs []string `json:"sniContainerRefs,omitempty"`
+ DefaultPoolRef *apiv1alpha1.KubernetesNameRef `json:"defaultPoolRef,omitempty"`
+ InsertHeaders map[string]string `json:"insertHeaders,omitempty"`
+ TimeoutClientData *int32 `json:"timeoutClientData,omitempty"`
+ TimeoutMemberConnect *int32 `json:"timeoutMemberConnect,omitempty"`
+ TimeoutMemberData *int32 `json:"timeoutMemberData,omitempty"`
+ TimeoutTCPInspect *int32 `json:"timeoutTCPInspect,omitempty"`
+ AllowedCIDRs []string `json:"allowedCIDRs,omitempty"`
+ TLSCiphers *string `json:"tlsCiphers,omitempty"`
+ TLSVersions []string `json:"tlsVersions,omitempty"`
+ ALPNProtocols []string `json:"alpnProtocols,omitempty"`
+ ClientAuthentication *apiv1alpha1.ListenerClientAuthentication `json:"clientAuthentication,omitempty"`
+ ClientCATLSContainerRef *string `json:"clientCATLSContainerRef,omitempty"`
+ ClientCRLContainerRef *string `json:"clientCRLContainerRef,omitempty"`
+ HSTS *ListenerHSTSApplyConfiguration `json:"hsts,omitempty"`
+ Tags []apiv1alpha1.ListenerTag `json:"tags,omitempty"`
+}
+
+// ListenerResourceSpecApplyConfiguration constructs a declarative configuration of the ListenerResourceSpec type for use with
+// apply.
+func ListenerResourceSpec() *ListenerResourceSpecApplyConfiguration {
+ return &ListenerResourceSpecApplyConfiguration{}
+}
+
+// WithName sets the Name field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Name field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *ListenerResourceSpecApplyConfiguration {
+ b.Name = &value
+ return b
+}
+
+// WithDescription sets the Description field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Description field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithDescription(value string) *ListenerResourceSpecApplyConfiguration {
+ b.Description = &value
+ return b
+}
+
+// WithLoadBalancerRef sets the LoadBalancerRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the LoadBalancerRef field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithLoadBalancerRef(value apiv1alpha1.KubernetesNameRef) *ListenerResourceSpecApplyConfiguration {
+ b.LoadBalancerRef = &value
+ return b
+}
+
+// WithProtocol sets the Protocol field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Protocol field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithProtocol(value apiv1alpha1.ListenerProtocol) *ListenerResourceSpecApplyConfiguration {
+ b.Protocol = &value
+ return b
+}
+
+// WithProtocolPort sets the ProtocolPort field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ProtocolPort field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithProtocolPort(value int32) *ListenerResourceSpecApplyConfiguration {
+ b.ProtocolPort = &value
+ return b
+}
+
+// WithAdminStateUp sets the AdminStateUp field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the AdminStateUp field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithAdminStateUp(value bool) *ListenerResourceSpecApplyConfiguration {
+ b.AdminStateUp = &value
+ return b
+}
+
+// WithConnectionLimit sets the ConnectionLimit field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ConnectionLimit field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithConnectionLimit(value int32) *ListenerResourceSpecApplyConfiguration {
+ b.ConnectionLimit = &value
+ return b
+}
+
+// WithDefaultTLSContainerRef sets the DefaultTLSContainerRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the DefaultTLSContainerRef field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithDefaultTLSContainerRef(value string) *ListenerResourceSpecApplyConfiguration {
+ b.DefaultTLSContainerRef = &value
+ return b
+}
+
+// WithSNIContainerRefs adds the given value to the SNIContainerRefs field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the SNIContainerRefs field.
+func (b *ListenerResourceSpecApplyConfiguration) WithSNIContainerRefs(values ...string) *ListenerResourceSpecApplyConfiguration {
+ for i := range values {
+ b.SNIContainerRefs = append(b.SNIContainerRefs, values[i])
+ }
+ return b
+}
+
+// WithDefaultPoolRef sets the DefaultPoolRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the DefaultPoolRef field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithDefaultPoolRef(value apiv1alpha1.KubernetesNameRef) *ListenerResourceSpecApplyConfiguration {
+ b.DefaultPoolRef = &value
+ return b
+}
+
+// WithInsertHeaders puts the entries into the InsertHeaders field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, the entries provided by each call will be put on the InsertHeaders field,
+// overwriting an existing map entries in InsertHeaders field with the same key.
+func (b *ListenerResourceSpecApplyConfiguration) WithInsertHeaders(entries map[string]string) *ListenerResourceSpecApplyConfiguration {
+ if b.InsertHeaders == nil && len(entries) > 0 {
+ b.InsertHeaders = make(map[string]string, len(entries))
+ }
+ for k, v := range entries {
+ b.InsertHeaders[k] = v
+ }
+ return b
+}
+
+// WithTimeoutClientData sets the TimeoutClientData field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the TimeoutClientData field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithTimeoutClientData(value int32) *ListenerResourceSpecApplyConfiguration {
+ b.TimeoutClientData = &value
+ return b
+}
+
+// WithTimeoutMemberConnect sets the TimeoutMemberConnect field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the TimeoutMemberConnect field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithTimeoutMemberConnect(value int32) *ListenerResourceSpecApplyConfiguration {
+ b.TimeoutMemberConnect = &value
+ return b
+}
+
+// WithTimeoutMemberData sets the TimeoutMemberData field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the TimeoutMemberData field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithTimeoutMemberData(value int32) *ListenerResourceSpecApplyConfiguration {
+ b.TimeoutMemberData = &value
+ return b
+}
+
+// WithTimeoutTCPInspect sets the TimeoutTCPInspect field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the TimeoutTCPInspect field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithTimeoutTCPInspect(value int32) *ListenerResourceSpecApplyConfiguration {
+ b.TimeoutTCPInspect = &value
+ return b
+}
+
+// WithAllowedCIDRs adds the given value to the AllowedCIDRs field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the AllowedCIDRs field.
+func (b *ListenerResourceSpecApplyConfiguration) WithAllowedCIDRs(values ...string) *ListenerResourceSpecApplyConfiguration {
+ for i := range values {
+ b.AllowedCIDRs = append(b.AllowedCIDRs, values[i])
+ }
+ return b
+}
+
+// WithTLSCiphers sets the TLSCiphers field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the TLSCiphers field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithTLSCiphers(value string) *ListenerResourceSpecApplyConfiguration {
+ b.TLSCiphers = &value
+ return b
+}
+
+// WithTLSVersions adds the given value to the TLSVersions field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the TLSVersions field.
+func (b *ListenerResourceSpecApplyConfiguration) WithTLSVersions(values ...string) *ListenerResourceSpecApplyConfiguration {
+ for i := range values {
+ b.TLSVersions = append(b.TLSVersions, values[i])
+ }
+ return b
+}
+
+// WithALPNProtocols adds the given value to the ALPNProtocols field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the ALPNProtocols field.
+func (b *ListenerResourceSpecApplyConfiguration) WithALPNProtocols(values ...string) *ListenerResourceSpecApplyConfiguration {
+ for i := range values {
+ b.ALPNProtocols = append(b.ALPNProtocols, values[i])
+ }
+ return b
+}
+
+// WithClientAuthentication sets the ClientAuthentication field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ClientAuthentication field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithClientAuthentication(value apiv1alpha1.ListenerClientAuthentication) *ListenerResourceSpecApplyConfiguration {
+ b.ClientAuthentication = &value
+ return b
+}
+
+// WithClientCATLSContainerRef sets the ClientCATLSContainerRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ClientCATLSContainerRef field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithClientCATLSContainerRef(value string) *ListenerResourceSpecApplyConfiguration {
+ b.ClientCATLSContainerRef = &value
+ return b
+}
+
+// WithClientCRLContainerRef sets the ClientCRLContainerRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ClientCRLContainerRef field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithClientCRLContainerRef(value string) *ListenerResourceSpecApplyConfiguration {
+ b.ClientCRLContainerRef = &value
+ return b
+}
+
+// WithHSTS sets the HSTS field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the HSTS field is set to the value of the last call.
+func (b *ListenerResourceSpecApplyConfiguration) WithHSTS(value *ListenerHSTSApplyConfiguration) *ListenerResourceSpecApplyConfiguration {
+ b.HSTS = value
+ return b
+}
+
+// WithTags adds the given value to the Tags field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the Tags field.
+func (b *ListenerResourceSpecApplyConfiguration) WithTags(values ...apiv1alpha1.ListenerTag) *ListenerResourceSpecApplyConfiguration {
+ for i := range values {
+ b.Tags = append(b.Tags, values[i])
+ }
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/listenerresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/listenerresourcestatus.go
new file mode 100644
index 000000000..4e29ae997
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/listenerresourcestatus.go
@@ -0,0 +1,193 @@
+/*
+Copyright 2025 The ORC 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+// ListenerResourceStatusApplyConfiguration represents a declarative configuration of the ListenerResourceStatus type for use
+// with apply.
+type ListenerResourceStatusApplyConfiguration struct {
+ Name *string `json:"name,omitempty"`
+ Description *string `json:"description,omitempty"`
+ LoadBalancerID *string `json:"loadBalancerID,omitempty"`
+ Protocol *string `json:"protocol,omitempty"`
+ ProtocolPort *int32 `json:"protocolPort,omitempty"`
+ AdminStateUp *bool `json:"adminStateUp,omitempty"`
+ ConnectionLimit *int32 `json:"connectionLimit,omitempty"`
+ DefaultPoolID *string `json:"defaultPoolID,omitempty"`
+ ProvisioningStatus *string `json:"provisioningStatus,omitempty"`
+ OperatingStatus *string `json:"operatingStatus,omitempty"`
+ AllowedCIDRs []string `json:"allowedCIDRs,omitempty"`
+ TimeoutClientData *int32 `json:"timeoutClientData,omitempty"`
+ TimeoutMemberConnect *int32 `json:"timeoutMemberConnect,omitempty"`
+ TimeoutMemberData *int32 `json:"timeoutMemberData,omitempty"`
+ TimeoutTCPInspect *int32 `json:"timeoutTCPInspect,omitempty"`
+ InsertHeaders map[string]string `json:"insertHeaders,omitempty"`
+ Tags []string `json:"tags,omitempty"`
+}
+
+// ListenerResourceStatusApplyConfiguration constructs a declarative configuration of the ListenerResourceStatus type for use with
+// apply.
+func ListenerResourceStatus() *ListenerResourceStatusApplyConfiguration {
+ return &ListenerResourceStatusApplyConfiguration{}
+}
+
+// WithName sets the Name field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Name field is set to the value of the last call.
+func (b *ListenerResourceStatusApplyConfiguration) WithName(value string) *ListenerResourceStatusApplyConfiguration {
+ b.Name = &value
+ return b
+}
+
+// WithDescription sets the Description field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Description field is set to the value of the last call.
+func (b *ListenerResourceStatusApplyConfiguration) WithDescription(value string) *ListenerResourceStatusApplyConfiguration {
+ b.Description = &value
+ return b
+}
+
+// WithLoadBalancerID sets the LoadBalancerID field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the LoadBalancerID field is set to the value of the last call.
+func (b *ListenerResourceStatusApplyConfiguration) WithLoadBalancerID(value string) *ListenerResourceStatusApplyConfiguration {
+ b.LoadBalancerID = &value
+ return b
+}
+
+// WithProtocol sets the Protocol field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Protocol field is set to the value of the last call.
+func (b *ListenerResourceStatusApplyConfiguration) WithProtocol(value string) *ListenerResourceStatusApplyConfiguration {
+ b.Protocol = &value
+ return b
+}
+
+// WithProtocolPort sets the ProtocolPort field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ProtocolPort field is set to the value of the last call.
+func (b *ListenerResourceStatusApplyConfiguration) WithProtocolPort(value int32) *ListenerResourceStatusApplyConfiguration {
+ b.ProtocolPort = &value
+ return b
+}
+
+// WithAdminStateUp sets the AdminStateUp field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the AdminStateUp field is set to the value of the last call.
+func (b *ListenerResourceStatusApplyConfiguration) WithAdminStateUp(value bool) *ListenerResourceStatusApplyConfiguration {
+ b.AdminStateUp = &value
+ return b
+}
+
+// WithConnectionLimit sets the ConnectionLimit field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ConnectionLimit field is set to the value of the last call.
+func (b *ListenerResourceStatusApplyConfiguration) WithConnectionLimit(value int32) *ListenerResourceStatusApplyConfiguration {
+ b.ConnectionLimit = &value
+ return b
+}
+
+// WithDefaultPoolID sets the DefaultPoolID field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the DefaultPoolID field is set to the value of the last call.
+func (b *ListenerResourceStatusApplyConfiguration) WithDefaultPoolID(value string) *ListenerResourceStatusApplyConfiguration {
+ b.DefaultPoolID = &value
+ return b
+}
+
+// WithProvisioningStatus sets the ProvisioningStatus field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ProvisioningStatus field is set to the value of the last call.
+func (b *ListenerResourceStatusApplyConfiguration) WithProvisioningStatus(value string) *ListenerResourceStatusApplyConfiguration {
+ b.ProvisioningStatus = &value
+ return b
+}
+
+// WithOperatingStatus sets the OperatingStatus field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the OperatingStatus field is set to the value of the last call.
+func (b *ListenerResourceStatusApplyConfiguration) WithOperatingStatus(value string) *ListenerResourceStatusApplyConfiguration {
+ b.OperatingStatus = &value
+ return b
+}
+
+// WithAllowedCIDRs adds the given value to the AllowedCIDRs field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the AllowedCIDRs field.
+func (b *ListenerResourceStatusApplyConfiguration) WithAllowedCIDRs(values ...string) *ListenerResourceStatusApplyConfiguration {
+ for i := range values {
+ b.AllowedCIDRs = append(b.AllowedCIDRs, values[i])
+ }
+ return b
+}
+
+// WithTimeoutClientData sets the TimeoutClientData field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the TimeoutClientData field is set to the value of the last call.
+func (b *ListenerResourceStatusApplyConfiguration) WithTimeoutClientData(value int32) *ListenerResourceStatusApplyConfiguration {
+ b.TimeoutClientData = &value
+ return b
+}
+
+// WithTimeoutMemberConnect sets the TimeoutMemberConnect field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the TimeoutMemberConnect field is set to the value of the last call.
+func (b *ListenerResourceStatusApplyConfiguration) WithTimeoutMemberConnect(value int32) *ListenerResourceStatusApplyConfiguration {
+ b.TimeoutMemberConnect = &value
+ return b
+}
+
+// WithTimeoutMemberData sets the TimeoutMemberData field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the TimeoutMemberData field is set to the value of the last call.
+func (b *ListenerResourceStatusApplyConfiguration) WithTimeoutMemberData(value int32) *ListenerResourceStatusApplyConfiguration {
+ b.TimeoutMemberData = &value
+ return b
+}
+
+// WithTimeoutTCPInspect sets the TimeoutTCPInspect field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the TimeoutTCPInspect field is set to the value of the last call.
+func (b *ListenerResourceStatusApplyConfiguration) WithTimeoutTCPInspect(value int32) *ListenerResourceStatusApplyConfiguration {
+ b.TimeoutTCPInspect = &value
+ return b
+}
+
+// WithInsertHeaders puts the entries into the InsertHeaders field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, the entries provided by each call will be put on the InsertHeaders field,
+// overwriting an existing map entries in InsertHeaders field with the same key.
+func (b *ListenerResourceStatusApplyConfiguration) WithInsertHeaders(entries map[string]string) *ListenerResourceStatusApplyConfiguration {
+ if b.InsertHeaders == nil && len(entries) > 0 {
+ b.InsertHeaders = make(map[string]string, len(entries))
+ }
+ for k, v := range entries {
+ b.InsertHeaders[k] = v
+ }
+ return b
+}
+
+// WithTags adds the given value to the Tags field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the Tags field.
+func (b *ListenerResourceStatusApplyConfiguration) WithTags(values ...string) *ListenerResourceStatusApplyConfiguration {
+ for i := range values {
+ b.Tags = append(b.Tags, values[i])
+ }
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/listenerspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/listenerspec.go
new file mode 100644
index 000000000..05df67542
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/listenerspec.go
@@ -0,0 +1,79 @@
+/*
+Copyright 2025 The ORC 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+)
+
+// ListenerSpecApplyConfiguration represents a declarative configuration of the ListenerSpec type for use
+// with apply.
+type ListenerSpecApplyConfiguration struct {
+ Import *ListenerImportApplyConfiguration `json:"import,omitempty"`
+ Resource *ListenerResourceSpecApplyConfiguration `json:"resource,omitempty"`
+ ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"`
+ ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"`
+ CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"`
+}
+
+// ListenerSpecApplyConfiguration constructs a declarative configuration of the ListenerSpec type for use with
+// apply.
+func ListenerSpec() *ListenerSpecApplyConfiguration {
+ return &ListenerSpecApplyConfiguration{}
+}
+
+// WithImport sets the Import field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Import field is set to the value of the last call.
+func (b *ListenerSpecApplyConfiguration) WithImport(value *ListenerImportApplyConfiguration) *ListenerSpecApplyConfiguration {
+ b.Import = value
+ return b
+}
+
+// WithResource sets the Resource field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Resource field is set to the value of the last call.
+func (b *ListenerSpecApplyConfiguration) WithResource(value *ListenerResourceSpecApplyConfiguration) *ListenerSpecApplyConfiguration {
+ b.Resource = value
+ return b
+}
+
+// WithManagementPolicy sets the ManagementPolicy field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ManagementPolicy field is set to the value of the last call.
+func (b *ListenerSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *ListenerSpecApplyConfiguration {
+ b.ManagementPolicy = &value
+ return b
+}
+
+// WithManagedOptions sets the ManagedOptions field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ManagedOptions field is set to the value of the last call.
+func (b *ListenerSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *ListenerSpecApplyConfiguration {
+ b.ManagedOptions = value
+ return b
+}
+
+// WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the CloudCredentialsRef field is set to the value of the last call.
+func (b *ListenerSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *ListenerSpecApplyConfiguration {
+ b.CloudCredentialsRef = value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/listenerstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/listenerstatus.go
new file mode 100644
index 000000000..81a09aab3
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/listenerstatus.go
@@ -0,0 +1,66 @@
+/*
+Copyright 2025 The ORC 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ v1 "k8s.io/client-go/applyconfigurations/meta/v1"
+)
+
+// ListenerStatusApplyConfiguration represents a declarative configuration of the ListenerStatus type for use
+// with apply.
+type ListenerStatusApplyConfiguration struct {
+ Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"`
+ ID *string `json:"id,omitempty"`
+ Resource *ListenerResourceStatusApplyConfiguration `json:"resource,omitempty"`
+}
+
+// ListenerStatusApplyConfiguration constructs a declarative configuration of the ListenerStatus type for use with
+// apply.
+func ListenerStatus() *ListenerStatusApplyConfiguration {
+ return &ListenerStatusApplyConfiguration{}
+}
+
+// WithConditions adds the given value to the Conditions field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the Conditions field.
+func (b *ListenerStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *ListenerStatusApplyConfiguration {
+ for i := range values {
+ if values[i] == nil {
+ panic("nil value passed to WithConditions")
+ }
+ b.Conditions = append(b.Conditions, *values[i])
+ }
+ return b
+}
+
+// WithID sets the ID field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ID field is set to the value of the last call.
+func (b *ListenerStatusApplyConfiguration) WithID(value string) *ListenerStatusApplyConfiguration {
+ b.ID = &value
+ return b
+}
+
+// WithResource sets the Resource field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Resource field is set to the value of the last call.
+func (b *ListenerStatusApplyConfiguration) WithResource(value *ListenerResourceStatusApplyConfiguration) *ListenerStatusApplyConfiguration {
+ b.Resource = value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancer.go b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancer.go
new file mode 100644
index 000000000..ad1ede95a
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancer.go
@@ -0,0 +1,281 @@
+/*
+Copyright 2025 The ORC 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ internal "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/internal"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ types "k8s.io/apimachinery/pkg/types"
+ managedfields "k8s.io/apimachinery/pkg/util/managedfields"
+ v1 "k8s.io/client-go/applyconfigurations/meta/v1"
+)
+
+// LoadBalancerApplyConfiguration represents a declarative configuration of the LoadBalancer type for use
+// with apply.
+type LoadBalancerApplyConfiguration struct {
+ v1.TypeMetaApplyConfiguration `json:",inline"`
+ *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"`
+ Spec *LoadBalancerSpecApplyConfiguration `json:"spec,omitempty"`
+ Status *LoadBalancerStatusApplyConfiguration `json:"status,omitempty"`
+}
+
+// LoadBalancer constructs a declarative configuration of the LoadBalancer type for use with
+// apply.
+func LoadBalancer(name, namespace string) *LoadBalancerApplyConfiguration {
+ b := &LoadBalancerApplyConfiguration{}
+ b.WithName(name)
+ b.WithNamespace(namespace)
+ b.WithKind("LoadBalancer")
+ b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1")
+ return b
+}
+
+// ExtractLoadBalancer extracts the applied configuration owned by fieldManager from
+// loadBalancer. If no managedFields are found in loadBalancer for fieldManager, a
+// LoadBalancerApplyConfiguration is returned with only the Name, Namespace (if applicable),
+// APIVersion and Kind populated. It is possible that no managed fields were found for because other
+// field managers have taken ownership of all the fields previously owned by fieldManager, or because
+// the fieldManager never owned fields any fields.
+// loadBalancer must be a unmodified LoadBalancer API object that was retrieved from the Kubernetes API.
+// ExtractLoadBalancer provides a way to perform a extract/modify-in-place/apply workflow.
+// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously
+// applied if another fieldManager has updated or force applied any of the previously applied fields.
+// Experimental!
+func ExtractLoadBalancer(loadBalancer *apiv1alpha1.LoadBalancer, fieldManager string) (*LoadBalancerApplyConfiguration, error) {
+ return extractLoadBalancer(loadBalancer, fieldManager, "")
+}
+
+// ExtractLoadBalancerStatus is the same as ExtractLoadBalancer except
+// that it extracts the status subresource applied configuration.
+// Experimental!
+func ExtractLoadBalancerStatus(loadBalancer *apiv1alpha1.LoadBalancer, fieldManager string) (*LoadBalancerApplyConfiguration, error) {
+ return extractLoadBalancer(loadBalancer, fieldManager, "status")
+}
+
+func extractLoadBalancer(loadBalancer *apiv1alpha1.LoadBalancer, fieldManager string, subresource string) (*LoadBalancerApplyConfiguration, error) {
+ b := &LoadBalancerApplyConfiguration{}
+ err := managedfields.ExtractInto(loadBalancer, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LoadBalancer"), fieldManager, b, subresource)
+ if err != nil {
+ return nil, err
+ }
+ b.WithName(loadBalancer.Name)
+ b.WithNamespace(loadBalancer.Namespace)
+
+ b.WithKind("LoadBalancer")
+ b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1")
+ return b, nil
+}
+func (b LoadBalancerApplyConfiguration) IsApplyConfiguration() {}
+
+// WithKind sets the Kind field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Kind field is set to the value of the last call.
+func (b *LoadBalancerApplyConfiguration) WithKind(value string) *LoadBalancerApplyConfiguration {
+ b.TypeMetaApplyConfiguration.Kind = &value
+ return b
+}
+
+// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the APIVersion field is set to the value of the last call.
+func (b *LoadBalancerApplyConfiguration) WithAPIVersion(value string) *LoadBalancerApplyConfiguration {
+ b.TypeMetaApplyConfiguration.APIVersion = &value
+ return b
+}
+
+// WithName sets the Name field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Name field is set to the value of the last call.
+func (b *LoadBalancerApplyConfiguration) WithName(value string) *LoadBalancerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.Name = &value
+ return b
+}
+
+// WithGenerateName sets the GenerateName field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the GenerateName field is set to the value of the last call.
+func (b *LoadBalancerApplyConfiguration) WithGenerateName(value string) *LoadBalancerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.GenerateName = &value
+ return b
+}
+
+// WithNamespace sets the Namespace field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Namespace field is set to the value of the last call.
+func (b *LoadBalancerApplyConfiguration) WithNamespace(value string) *LoadBalancerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.Namespace = &value
+ return b
+}
+
+// WithUID sets the UID field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the UID field is set to the value of the last call.
+func (b *LoadBalancerApplyConfiguration) WithUID(value types.UID) *LoadBalancerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.UID = &value
+ return b
+}
+
+// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ResourceVersion field is set to the value of the last call.
+func (b *LoadBalancerApplyConfiguration) WithResourceVersion(value string) *LoadBalancerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.ResourceVersion = &value
+ return b
+}
+
+// WithGeneration sets the Generation field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Generation field is set to the value of the last call.
+func (b *LoadBalancerApplyConfiguration) WithGeneration(value int64) *LoadBalancerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.Generation = &value
+ return b
+}
+
+// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the CreationTimestamp field is set to the value of the last call.
+func (b *LoadBalancerApplyConfiguration) WithCreationTimestamp(value metav1.Time) *LoadBalancerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.CreationTimestamp = &value
+ return b
+}
+
+// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the DeletionTimestamp field is set to the value of the last call.
+func (b *LoadBalancerApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *LoadBalancerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value
+ return b
+}
+
+// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call.
+func (b *LoadBalancerApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *LoadBalancerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value
+ return b
+}
+
+// WithLabels puts the entries into the Labels field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, the entries provided by each call will be put on the Labels field,
+// overwriting an existing map entries in Labels field with the same key.
+func (b *LoadBalancerApplyConfiguration) WithLabels(entries map[string]string) *LoadBalancerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 {
+ b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries))
+ }
+ for k, v := range entries {
+ b.ObjectMetaApplyConfiguration.Labels[k] = v
+ }
+ return b
+}
+
+// WithAnnotations puts the entries into the Annotations field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, the entries provided by each call will be put on the Annotations field,
+// overwriting an existing map entries in Annotations field with the same key.
+func (b *LoadBalancerApplyConfiguration) WithAnnotations(entries map[string]string) *LoadBalancerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 {
+ b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries))
+ }
+ for k, v := range entries {
+ b.ObjectMetaApplyConfiguration.Annotations[k] = v
+ }
+ return b
+}
+
+// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the OwnerReferences field.
+func (b *LoadBalancerApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *LoadBalancerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ for i := range values {
+ if values[i] == nil {
+ panic("nil value passed to WithOwnerReferences")
+ }
+ b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i])
+ }
+ return b
+}
+
+// WithFinalizers adds the given value to the Finalizers field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the Finalizers field.
+func (b *LoadBalancerApplyConfiguration) WithFinalizers(values ...string) *LoadBalancerApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ for i := range values {
+ b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i])
+ }
+ return b
+}
+
+func (b *LoadBalancerApplyConfiguration) ensureObjectMetaApplyConfigurationExists() {
+ if b.ObjectMetaApplyConfiguration == nil {
+ b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{}
+ }
+}
+
+// WithSpec sets the Spec field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Spec field is set to the value of the last call.
+func (b *LoadBalancerApplyConfiguration) WithSpec(value *LoadBalancerSpecApplyConfiguration) *LoadBalancerApplyConfiguration {
+ b.Spec = value
+ return b
+}
+
+// WithStatus sets the Status field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Status field is set to the value of the last call.
+func (b *LoadBalancerApplyConfiguration) WithStatus(value *LoadBalancerStatusApplyConfiguration) *LoadBalancerApplyConfiguration {
+ b.Status = value
+ return b
+}
+
+// GetKind retrieves the value of the Kind field in the declarative configuration.
+func (b *LoadBalancerApplyConfiguration) GetKind() *string {
+ return b.TypeMetaApplyConfiguration.Kind
+}
+
+// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration.
+func (b *LoadBalancerApplyConfiguration) GetAPIVersion() *string {
+ return b.TypeMetaApplyConfiguration.APIVersion
+}
+
+// GetName retrieves the value of the Name field in the declarative configuration.
+func (b *LoadBalancerApplyConfiguration) GetName() *string {
+ b.ensureObjectMetaApplyConfigurationExists()
+ return b.ObjectMetaApplyConfiguration.Name
+}
+
+// GetNamespace retrieves the value of the Namespace field in the declarative configuration.
+func (b *LoadBalancerApplyConfiguration) GetNamespace() *string {
+ b.ensureObjectMetaApplyConfigurationExists()
+ return b.ObjectMetaApplyConfiguration.Namespace
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerfilter.go
new file mode 100644
index 000000000..56165a113
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerfilter.go
@@ -0,0 +1,159 @@
+/*
+Copyright 2025 The ORC 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+)
+
+// LoadBalancerFilterApplyConfiguration represents a declarative configuration of the LoadBalancerFilter type for use
+// with apply.
+type LoadBalancerFilterApplyConfiguration struct {
+ Name *apiv1alpha1.OpenStackName `json:"name,omitempty"`
+ Description *string `json:"description,omitempty"`
+ ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"`
+ VipSubnetRef *apiv1alpha1.KubernetesNameRef `json:"vipSubnetRef,omitempty"`
+ VipNetworkRef *apiv1alpha1.KubernetesNameRef `json:"vipNetworkRef,omitempty"`
+ VipPortRef *apiv1alpha1.KubernetesNameRef `json:"vipPortRef,omitempty"`
+ AvailabilityZone *string `json:"availabilityZone,omitempty"`
+ Provider *string `json:"provider,omitempty"`
+ VipAddress *string `json:"vipAddress,omitempty"`
+ Tags []apiv1alpha1.LoadBalancerTag `json:"tags,omitempty"`
+ TagsAny []apiv1alpha1.LoadBalancerTag `json:"tagsAny,omitempty"`
+ NotTags []apiv1alpha1.LoadBalancerTag `json:"notTags,omitempty"`
+ NotTagsAny []apiv1alpha1.LoadBalancerTag `json:"notTagsAny,omitempty"`
+}
+
+// LoadBalancerFilterApplyConfiguration constructs a declarative configuration of the LoadBalancerFilter type for use with
+// apply.
+func LoadBalancerFilter() *LoadBalancerFilterApplyConfiguration {
+ return &LoadBalancerFilterApplyConfiguration{}
+}
+
+// WithName sets the Name field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Name field is set to the value of the last call.
+func (b *LoadBalancerFilterApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *LoadBalancerFilterApplyConfiguration {
+ b.Name = &value
+ return b
+}
+
+// WithDescription sets the Description field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Description field is set to the value of the last call.
+func (b *LoadBalancerFilterApplyConfiguration) WithDescription(value string) *LoadBalancerFilterApplyConfiguration {
+ b.Description = &value
+ return b
+}
+
+// WithProjectRef sets the ProjectRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ProjectRef field is set to the value of the last call.
+func (b *LoadBalancerFilterApplyConfiguration) WithProjectRef(value apiv1alpha1.KubernetesNameRef) *LoadBalancerFilterApplyConfiguration {
+ b.ProjectRef = &value
+ return b
+}
+
+// WithVipSubnetRef sets the VipSubnetRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the VipSubnetRef field is set to the value of the last call.
+func (b *LoadBalancerFilterApplyConfiguration) WithVipSubnetRef(value apiv1alpha1.KubernetesNameRef) *LoadBalancerFilterApplyConfiguration {
+ b.VipSubnetRef = &value
+ return b
+}
+
+// WithVipNetworkRef sets the VipNetworkRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the VipNetworkRef field is set to the value of the last call.
+func (b *LoadBalancerFilterApplyConfiguration) WithVipNetworkRef(value apiv1alpha1.KubernetesNameRef) *LoadBalancerFilterApplyConfiguration {
+ b.VipNetworkRef = &value
+ return b
+}
+
+// WithVipPortRef sets the VipPortRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the VipPortRef field is set to the value of the last call.
+func (b *LoadBalancerFilterApplyConfiguration) WithVipPortRef(value apiv1alpha1.KubernetesNameRef) *LoadBalancerFilterApplyConfiguration {
+ b.VipPortRef = &value
+ return b
+}
+
+// WithAvailabilityZone sets the AvailabilityZone field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the AvailabilityZone field is set to the value of the last call.
+func (b *LoadBalancerFilterApplyConfiguration) WithAvailabilityZone(value string) *LoadBalancerFilterApplyConfiguration {
+ b.AvailabilityZone = &value
+ return b
+}
+
+// WithProvider sets the Provider field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Provider field is set to the value of the last call.
+func (b *LoadBalancerFilterApplyConfiguration) WithProvider(value string) *LoadBalancerFilterApplyConfiguration {
+ b.Provider = &value
+ return b
+}
+
+// WithVipAddress sets the VipAddress field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the VipAddress field is set to the value of the last call.
+func (b *LoadBalancerFilterApplyConfiguration) WithVipAddress(value string) *LoadBalancerFilterApplyConfiguration {
+ b.VipAddress = &value
+ return b
+}
+
+// WithTags adds the given value to the Tags field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the Tags field.
+func (b *LoadBalancerFilterApplyConfiguration) WithTags(values ...apiv1alpha1.LoadBalancerTag) *LoadBalancerFilterApplyConfiguration {
+ for i := range values {
+ b.Tags = append(b.Tags, values[i])
+ }
+ return b
+}
+
+// WithTagsAny adds the given value to the TagsAny field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the TagsAny field.
+func (b *LoadBalancerFilterApplyConfiguration) WithTagsAny(values ...apiv1alpha1.LoadBalancerTag) *LoadBalancerFilterApplyConfiguration {
+ for i := range values {
+ b.TagsAny = append(b.TagsAny, values[i])
+ }
+ return b
+}
+
+// WithNotTags adds the given value to the NotTags field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the NotTags field.
+func (b *LoadBalancerFilterApplyConfiguration) WithNotTags(values ...apiv1alpha1.LoadBalancerTag) *LoadBalancerFilterApplyConfiguration {
+ for i := range values {
+ b.NotTags = append(b.NotTags, values[i])
+ }
+ return b
+}
+
+// WithNotTagsAny adds the given value to the NotTagsAny field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the NotTagsAny field.
+func (b *LoadBalancerFilterApplyConfiguration) WithNotTagsAny(values ...apiv1alpha1.LoadBalancerTag) *LoadBalancerFilterApplyConfiguration {
+ for i := range values {
+ b.NotTagsAny = append(b.NotTagsAny, values[i])
+ }
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerimport.go
new file mode 100644
index 000000000..5322c6cdd
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerimport.go
@@ -0,0 +1,48 @@
+/*
+Copyright 2025 The ORC 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+// LoadBalancerImportApplyConfiguration represents a declarative configuration of the LoadBalancerImport type for use
+// with apply.
+type LoadBalancerImportApplyConfiguration struct {
+ ID *string `json:"id,omitempty"`
+ Filter *LoadBalancerFilterApplyConfiguration `json:"filter,omitempty"`
+}
+
+// LoadBalancerImportApplyConfiguration constructs a declarative configuration of the LoadBalancerImport type for use with
+// apply.
+func LoadBalancerImport() *LoadBalancerImportApplyConfiguration {
+ return &LoadBalancerImportApplyConfiguration{}
+}
+
+// WithID sets the ID field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ID field is set to the value of the last call.
+func (b *LoadBalancerImportApplyConfiguration) WithID(value string) *LoadBalancerImportApplyConfiguration {
+ b.ID = &value
+ return b
+}
+
+// WithFilter sets the Filter field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Filter field is set to the value of the last call.
+func (b *LoadBalancerImportApplyConfiguration) WithFilter(value *LoadBalancerFilterApplyConfiguration) *LoadBalancerImportApplyConfiguration {
+ b.Filter = value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerresourcespec.go
new file mode 100644
index 000000000..2628ef40a
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerresourcespec.go
@@ -0,0 +1,144 @@
+/*
+Copyright 2025 The ORC 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+)
+
+// LoadBalancerResourceSpecApplyConfiguration represents a declarative configuration of the LoadBalancerResourceSpec type for use
+// with apply.
+type LoadBalancerResourceSpecApplyConfiguration struct {
+ Name *apiv1alpha1.OpenStackName `json:"name,omitempty"`
+ Description *string `json:"description,omitempty"`
+ VipSubnetRef *apiv1alpha1.KubernetesNameRef `json:"vipSubnetRef,omitempty"`
+ VipNetworkRef *apiv1alpha1.KubernetesNameRef `json:"vipNetworkRef,omitempty"`
+ VipPortRef *apiv1alpha1.KubernetesNameRef `json:"vipPortRef,omitempty"`
+ FlavorRef *apiv1alpha1.KubernetesNameRef `json:"flavorRef,omitempty"`
+ ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"`
+ AdminStateUp *bool `json:"adminStateUp,omitempty"`
+ AvailabilityZone *string `json:"availabilityZone,omitempty"`
+ Provider *string `json:"provider,omitempty"`
+ VipAddress *apiv1alpha1.IPvAny `json:"vipAddress,omitempty"`
+ Tags []apiv1alpha1.LoadBalancerTag `json:"tags,omitempty"`
+}
+
+// LoadBalancerResourceSpecApplyConfiguration constructs a declarative configuration of the LoadBalancerResourceSpec type for use with
+// apply.
+func LoadBalancerResourceSpec() *LoadBalancerResourceSpecApplyConfiguration {
+ return &LoadBalancerResourceSpecApplyConfiguration{}
+}
+
+// WithName sets the Name field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Name field is set to the value of the last call.
+func (b *LoadBalancerResourceSpecApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *LoadBalancerResourceSpecApplyConfiguration {
+ b.Name = &value
+ return b
+}
+
+// WithDescription sets the Description field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Description field is set to the value of the last call.
+func (b *LoadBalancerResourceSpecApplyConfiguration) WithDescription(value string) *LoadBalancerResourceSpecApplyConfiguration {
+ b.Description = &value
+ return b
+}
+
+// WithVipSubnetRef sets the VipSubnetRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the VipSubnetRef field is set to the value of the last call.
+func (b *LoadBalancerResourceSpecApplyConfiguration) WithVipSubnetRef(value apiv1alpha1.KubernetesNameRef) *LoadBalancerResourceSpecApplyConfiguration {
+ b.VipSubnetRef = &value
+ return b
+}
+
+// WithVipNetworkRef sets the VipNetworkRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the VipNetworkRef field is set to the value of the last call.
+func (b *LoadBalancerResourceSpecApplyConfiguration) WithVipNetworkRef(value apiv1alpha1.KubernetesNameRef) *LoadBalancerResourceSpecApplyConfiguration {
+ b.VipNetworkRef = &value
+ return b
+}
+
+// WithVipPortRef sets the VipPortRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the VipPortRef field is set to the value of the last call.
+func (b *LoadBalancerResourceSpecApplyConfiguration) WithVipPortRef(value apiv1alpha1.KubernetesNameRef) *LoadBalancerResourceSpecApplyConfiguration {
+ b.VipPortRef = &value
+ return b
+}
+
+// WithFlavorRef sets the FlavorRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the FlavorRef field is set to the value of the last call.
+func (b *LoadBalancerResourceSpecApplyConfiguration) WithFlavorRef(value apiv1alpha1.KubernetesNameRef) *LoadBalancerResourceSpecApplyConfiguration {
+ b.FlavorRef = &value
+ return b
+}
+
+// WithProjectRef sets the ProjectRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ProjectRef field is set to the value of the last call.
+func (b *LoadBalancerResourceSpecApplyConfiguration) WithProjectRef(value apiv1alpha1.KubernetesNameRef) *LoadBalancerResourceSpecApplyConfiguration {
+ b.ProjectRef = &value
+ return b
+}
+
+// WithAdminStateUp sets the AdminStateUp field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the AdminStateUp field is set to the value of the last call.
+func (b *LoadBalancerResourceSpecApplyConfiguration) WithAdminStateUp(value bool) *LoadBalancerResourceSpecApplyConfiguration {
+ b.AdminStateUp = &value
+ return b
+}
+
+// WithAvailabilityZone sets the AvailabilityZone field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the AvailabilityZone field is set to the value of the last call.
+func (b *LoadBalancerResourceSpecApplyConfiguration) WithAvailabilityZone(value string) *LoadBalancerResourceSpecApplyConfiguration {
+ b.AvailabilityZone = &value
+ return b
+}
+
+// WithProvider sets the Provider field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Provider field is set to the value of the last call.
+func (b *LoadBalancerResourceSpecApplyConfiguration) WithProvider(value string) *LoadBalancerResourceSpecApplyConfiguration {
+ b.Provider = &value
+ return b
+}
+
+// WithVipAddress sets the VipAddress field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the VipAddress field is set to the value of the last call.
+func (b *LoadBalancerResourceSpecApplyConfiguration) WithVipAddress(value apiv1alpha1.IPvAny) *LoadBalancerResourceSpecApplyConfiguration {
+ b.VipAddress = &value
+ return b
+}
+
+// WithTags adds the given value to the Tags field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the Tags field.
+func (b *LoadBalancerResourceSpecApplyConfiguration) WithTags(values ...apiv1alpha1.LoadBalancerTag) *LoadBalancerResourceSpecApplyConfiguration {
+ for i := range values {
+ b.Tags = append(b.Tags, values[i])
+ }
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerresourcestatus.go
new file mode 100644
index 000000000..1da79b915
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerresourcestatus.go
@@ -0,0 +1,158 @@
+/*
+Copyright 2025 The ORC 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+// LoadBalancerResourceStatusApplyConfiguration represents a declarative configuration of the LoadBalancerResourceStatus type for use
+// with apply.
+type LoadBalancerResourceStatusApplyConfiguration struct {
+ Name *string `json:"name,omitempty"`
+ Description *string `json:"description,omitempty"`
+ VipSubnetID *string `json:"vipSubnetID,omitempty"`
+ VipNetworkID *string `json:"vipNetworkID,omitempty"`
+ VipPortID *string `json:"vipPortID,omitempty"`
+ FlavorID *string `json:"flavorID,omitempty"`
+ ProjectID *string `json:"projectID,omitempty"`
+ AdminStateUp *bool `json:"adminStateUp,omitempty"`
+ Tags []string `json:"tags,omitempty"`
+ AvailabilityZone *string `json:"availabilityZone,omitempty"`
+ ProvisioningStatus *string `json:"provisioningStatus,omitempty"`
+ OperatingStatus *string `json:"operatingStatus,omitempty"`
+ Provider *string `json:"provider,omitempty"`
+ VipAddress *string `json:"vipAddress,omitempty"`
+}
+
+// LoadBalancerResourceStatusApplyConfiguration constructs a declarative configuration of the LoadBalancerResourceStatus type for use with
+// apply.
+func LoadBalancerResourceStatus() *LoadBalancerResourceStatusApplyConfiguration {
+ return &LoadBalancerResourceStatusApplyConfiguration{}
+}
+
+// WithName sets the Name field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Name field is set to the value of the last call.
+func (b *LoadBalancerResourceStatusApplyConfiguration) WithName(value string) *LoadBalancerResourceStatusApplyConfiguration {
+ b.Name = &value
+ return b
+}
+
+// WithDescription sets the Description field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Description field is set to the value of the last call.
+func (b *LoadBalancerResourceStatusApplyConfiguration) WithDescription(value string) *LoadBalancerResourceStatusApplyConfiguration {
+ b.Description = &value
+ return b
+}
+
+// WithVipSubnetID sets the VipSubnetID field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the VipSubnetID field is set to the value of the last call.
+func (b *LoadBalancerResourceStatusApplyConfiguration) WithVipSubnetID(value string) *LoadBalancerResourceStatusApplyConfiguration {
+ b.VipSubnetID = &value
+ return b
+}
+
+// WithVipNetworkID sets the VipNetworkID field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the VipNetworkID field is set to the value of the last call.
+func (b *LoadBalancerResourceStatusApplyConfiguration) WithVipNetworkID(value string) *LoadBalancerResourceStatusApplyConfiguration {
+ b.VipNetworkID = &value
+ return b
+}
+
+// WithVipPortID sets the VipPortID field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the VipPortID field is set to the value of the last call.
+func (b *LoadBalancerResourceStatusApplyConfiguration) WithVipPortID(value string) *LoadBalancerResourceStatusApplyConfiguration {
+ b.VipPortID = &value
+ return b
+}
+
+// WithFlavorID sets the FlavorID field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the FlavorID field is set to the value of the last call.
+func (b *LoadBalancerResourceStatusApplyConfiguration) WithFlavorID(value string) *LoadBalancerResourceStatusApplyConfiguration {
+ b.FlavorID = &value
+ return b
+}
+
+// WithProjectID sets the ProjectID field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ProjectID field is set to the value of the last call.
+func (b *LoadBalancerResourceStatusApplyConfiguration) WithProjectID(value string) *LoadBalancerResourceStatusApplyConfiguration {
+ b.ProjectID = &value
+ return b
+}
+
+// WithAdminStateUp sets the AdminStateUp field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the AdminStateUp field is set to the value of the last call.
+func (b *LoadBalancerResourceStatusApplyConfiguration) WithAdminStateUp(value bool) *LoadBalancerResourceStatusApplyConfiguration {
+ b.AdminStateUp = &value
+ return b
+}
+
+// WithTags adds the given value to the Tags field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the Tags field.
+func (b *LoadBalancerResourceStatusApplyConfiguration) WithTags(values ...string) *LoadBalancerResourceStatusApplyConfiguration {
+ for i := range values {
+ b.Tags = append(b.Tags, values[i])
+ }
+ return b
+}
+
+// WithAvailabilityZone sets the AvailabilityZone field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the AvailabilityZone field is set to the value of the last call.
+func (b *LoadBalancerResourceStatusApplyConfiguration) WithAvailabilityZone(value string) *LoadBalancerResourceStatusApplyConfiguration {
+ b.AvailabilityZone = &value
+ return b
+}
+
+// WithProvisioningStatus sets the ProvisioningStatus field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ProvisioningStatus field is set to the value of the last call.
+func (b *LoadBalancerResourceStatusApplyConfiguration) WithProvisioningStatus(value string) *LoadBalancerResourceStatusApplyConfiguration {
+ b.ProvisioningStatus = &value
+ return b
+}
+
+// WithOperatingStatus sets the OperatingStatus field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the OperatingStatus field is set to the value of the last call.
+func (b *LoadBalancerResourceStatusApplyConfiguration) WithOperatingStatus(value string) *LoadBalancerResourceStatusApplyConfiguration {
+ b.OperatingStatus = &value
+ return b
+}
+
+// WithProvider sets the Provider field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Provider field is set to the value of the last call.
+func (b *LoadBalancerResourceStatusApplyConfiguration) WithProvider(value string) *LoadBalancerResourceStatusApplyConfiguration {
+ b.Provider = &value
+ return b
+}
+
+// WithVipAddress sets the VipAddress field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the VipAddress field is set to the value of the last call.
+func (b *LoadBalancerResourceStatusApplyConfiguration) WithVipAddress(value string) *LoadBalancerResourceStatusApplyConfiguration {
+ b.VipAddress = &value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerspec.go
new file mode 100644
index 000000000..445f4d890
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerspec.go
@@ -0,0 +1,79 @@
+/*
+Copyright 2025 The ORC 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+)
+
+// LoadBalancerSpecApplyConfiguration represents a declarative configuration of the LoadBalancerSpec type for use
+// with apply.
+type LoadBalancerSpecApplyConfiguration struct {
+ Import *LoadBalancerImportApplyConfiguration `json:"import,omitempty"`
+ Resource *LoadBalancerResourceSpecApplyConfiguration `json:"resource,omitempty"`
+ ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"`
+ ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"`
+ CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"`
+}
+
+// LoadBalancerSpecApplyConfiguration constructs a declarative configuration of the LoadBalancerSpec type for use with
+// apply.
+func LoadBalancerSpec() *LoadBalancerSpecApplyConfiguration {
+ return &LoadBalancerSpecApplyConfiguration{}
+}
+
+// WithImport sets the Import field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Import field is set to the value of the last call.
+func (b *LoadBalancerSpecApplyConfiguration) WithImport(value *LoadBalancerImportApplyConfiguration) *LoadBalancerSpecApplyConfiguration {
+ b.Import = value
+ return b
+}
+
+// WithResource sets the Resource field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Resource field is set to the value of the last call.
+func (b *LoadBalancerSpecApplyConfiguration) WithResource(value *LoadBalancerResourceSpecApplyConfiguration) *LoadBalancerSpecApplyConfiguration {
+ b.Resource = value
+ return b
+}
+
+// WithManagementPolicy sets the ManagementPolicy field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ManagementPolicy field is set to the value of the last call.
+func (b *LoadBalancerSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *LoadBalancerSpecApplyConfiguration {
+ b.ManagementPolicy = &value
+ return b
+}
+
+// WithManagedOptions sets the ManagedOptions field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ManagedOptions field is set to the value of the last call.
+func (b *LoadBalancerSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *LoadBalancerSpecApplyConfiguration {
+ b.ManagedOptions = value
+ return b
+}
+
+// WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the CloudCredentialsRef field is set to the value of the last call.
+func (b *LoadBalancerSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *LoadBalancerSpecApplyConfiguration {
+ b.CloudCredentialsRef = value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerstatus.go
new file mode 100644
index 000000000..173645261
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerstatus.go
@@ -0,0 +1,66 @@
+/*
+Copyright 2025 The ORC 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ v1 "k8s.io/client-go/applyconfigurations/meta/v1"
+)
+
+// LoadBalancerStatusApplyConfiguration represents a declarative configuration of the LoadBalancerStatus type for use
+// with apply.
+type LoadBalancerStatusApplyConfiguration struct {
+ Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"`
+ ID *string `json:"id,omitempty"`
+ Resource *LoadBalancerResourceStatusApplyConfiguration `json:"resource,omitempty"`
+}
+
+// LoadBalancerStatusApplyConfiguration constructs a declarative configuration of the LoadBalancerStatus type for use with
+// apply.
+func LoadBalancerStatus() *LoadBalancerStatusApplyConfiguration {
+ return &LoadBalancerStatusApplyConfiguration{}
+}
+
+// WithConditions adds the given value to the Conditions field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the Conditions field.
+func (b *LoadBalancerStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *LoadBalancerStatusApplyConfiguration {
+ for i := range values {
+ if values[i] == nil {
+ panic("nil value passed to WithConditions")
+ }
+ b.Conditions = append(b.Conditions, *values[i])
+ }
+ return b
+}
+
+// WithID sets the ID field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ID field is set to the value of the last call.
+func (b *LoadBalancerStatusApplyConfiguration) WithID(value string) *LoadBalancerStatusApplyConfiguration {
+ b.ID = &value
+ return b
+}
+
+// WithResource sets the Resource field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Resource field is set to the value of the last call.
+func (b *LoadBalancerStatusApplyConfiguration) WithResource(value *LoadBalancerResourceStatusApplyConfiguration) *LoadBalancerStatusApplyConfiguration {
+ b.Resource = value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go
index 5b5cb5142..486491408 100644
--- a/pkg/clients/applyconfiguration/internal/internal.go
+++ b/pkg/clients/applyconfiguration/internal/internal.go
@@ -998,6 +998,490 @@ var schemaYAML = typed.YAMLObject(`types:
- name: resource
type:
namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.KeyPairResourceStatus
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Listener
+ map:
+ fields:
+ - name: apiVersion
+ type:
+ scalar: string
+ - name: kind
+ type:
+ scalar: string
+ - name: metadata
+ type:
+ namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta
+ default: {}
+ - name: spec
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ListenerSpec
+ default: {}
+ - name: status
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ListenerStatus
+ default: {}
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ListenerFilter
+ map:
+ fields:
+ - name: description
+ type:
+ scalar: string
+ - name: loadBalancerRef
+ type:
+ scalar: string
+ - name: name
+ type:
+ scalar: string
+ - name: notTags
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: associative
+ - name: notTagsAny
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: associative
+ - name: protocol
+ type:
+ scalar: string
+ - name: protocolPort
+ type:
+ scalar: numeric
+ - name: tags
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: associative
+ - name: tagsAny
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: associative
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ListenerHSTS
+ map:
+ fields:
+ - name: includeSubDomains
+ type:
+ scalar: boolean
+ - name: maxAge
+ type:
+ scalar: numeric
+ - name: preload
+ type:
+ scalar: boolean
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ListenerImport
+ map:
+ fields:
+ - name: filter
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ListenerFilter
+ - name: id
+ type:
+ scalar: string
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ListenerResourceSpec
+ map:
+ fields:
+ - name: adminStateUp
+ type:
+ scalar: boolean
+ - name: allowedCIDRs
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: associative
+ - name: alpnProtocols
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: associative
+ - name: clientAuthentication
+ type:
+ scalar: string
+ - name: clientCATLSContainerRef
+ type:
+ scalar: string
+ - name: clientCRLContainerRef
+ type:
+ scalar: string
+ - name: connectionLimit
+ type:
+ scalar: numeric
+ - name: defaultPoolRef
+ type:
+ scalar: string
+ - name: defaultTLSContainerRef
+ type:
+ scalar: string
+ - name: description
+ type:
+ scalar: string
+ - name: hsts
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ListenerHSTS
+ - name: insertHeaders
+ type:
+ map:
+ elementType:
+ scalar: string
+ - name: loadBalancerRef
+ type:
+ scalar: string
+ - name: name
+ type:
+ scalar: string
+ - name: protocol
+ type:
+ scalar: string
+ - name: protocolPort
+ type:
+ scalar: numeric
+ - name: sniContainerRefs
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: associative
+ - name: tags
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: associative
+ - name: timeoutClientData
+ type:
+ scalar: numeric
+ - name: timeoutMemberConnect
+ type:
+ scalar: numeric
+ - name: timeoutMemberData
+ type:
+ scalar: numeric
+ - name: timeoutTCPInspect
+ type:
+ scalar: numeric
+ - name: tlsCiphers
+ type:
+ scalar: string
+ - name: tlsVersions
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: associative
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ListenerResourceStatus
+ map:
+ fields:
+ - name: adminStateUp
+ type:
+ scalar: boolean
+ - name: allowedCIDRs
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: atomic
+ - name: connectionLimit
+ type:
+ scalar: numeric
+ - name: defaultPoolID
+ type:
+ scalar: string
+ - name: description
+ type:
+ scalar: string
+ - name: insertHeaders
+ type:
+ map:
+ elementType:
+ scalar: string
+ - name: loadBalancerID
+ type:
+ scalar: string
+ - name: name
+ type:
+ scalar: string
+ - name: operatingStatus
+ type:
+ scalar: string
+ - name: protocol
+ type:
+ scalar: string
+ - name: protocolPort
+ type:
+ scalar: numeric
+ - name: provisioningStatus
+ type:
+ scalar: string
+ - name: tags
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: atomic
+ - name: timeoutClientData
+ type:
+ scalar: numeric
+ - name: timeoutMemberConnect
+ type:
+ scalar: numeric
+ - name: timeoutMemberData
+ type:
+ scalar: numeric
+ - name: timeoutTCPInspect
+ type:
+ scalar: numeric
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ListenerSpec
+ map:
+ fields:
+ - name: cloudCredentialsRef
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.CloudCredentialsReference
+ default: {}
+ - name: import
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ListenerImport
+ - name: managedOptions
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ManagedOptions
+ - name: managementPolicy
+ type:
+ scalar: string
+ - name: resource
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ListenerResourceSpec
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ListenerStatus
+ map:
+ fields:
+ - name: conditions
+ type:
+ list:
+ elementType:
+ namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Condition
+ elementRelationship: associative
+ keys:
+ - type
+ - name: id
+ type:
+ scalar: string
+ - name: resource
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ListenerResourceStatus
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LoadBalancer
+ map:
+ fields:
+ - name: apiVersion
+ type:
+ scalar: string
+ - name: kind
+ type:
+ scalar: string
+ - name: metadata
+ type:
+ namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta
+ default: {}
+ - name: spec
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LoadBalancerSpec
+ default: {}
+ - name: status
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LoadBalancerStatus
+ default: {}
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LoadBalancerFilter
+ map:
+ fields:
+ - name: availabilityZone
+ type:
+ scalar: string
+ - name: description
+ type:
+ scalar: string
+ - name: name
+ type:
+ scalar: string
+ - name: notTags
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: associative
+ - name: notTagsAny
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: associative
+ - name: projectRef
+ type:
+ scalar: string
+ - name: provider
+ type:
+ scalar: string
+ - name: tags
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: associative
+ - name: tagsAny
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: associative
+ - name: vipAddress
+ type:
+ scalar: string
+ - name: vipNetworkRef
+ type:
+ scalar: string
+ - name: vipPortRef
+ type:
+ scalar: string
+ - name: vipSubnetRef
+ type:
+ scalar: string
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LoadBalancerImport
+ map:
+ fields:
+ - name: filter
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LoadBalancerFilter
+ - name: id
+ type:
+ scalar: string
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LoadBalancerResourceSpec
+ map:
+ fields:
+ - name: adminStateUp
+ type:
+ scalar: boolean
+ - name: availabilityZone
+ type:
+ scalar: string
+ - name: description
+ type:
+ scalar: string
+ - name: flavorRef
+ type:
+ scalar: string
+ - name: name
+ type:
+ scalar: string
+ - name: projectRef
+ type:
+ scalar: string
+ - name: provider
+ type:
+ scalar: string
+ - name: tags
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: associative
+ - name: vipAddress
+ type:
+ scalar: string
+ - name: vipNetworkRef
+ type:
+ scalar: string
+ - name: vipPortRef
+ type:
+ scalar: string
+ - name: vipSubnetRef
+ type:
+ scalar: string
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LoadBalancerResourceStatus
+ map:
+ fields:
+ - name: adminStateUp
+ type:
+ scalar: boolean
+ - name: availabilityZone
+ type:
+ scalar: string
+ - name: description
+ type:
+ scalar: string
+ - name: flavorID
+ type:
+ scalar: string
+ - name: name
+ type:
+ scalar: string
+ - name: operatingStatus
+ type:
+ scalar: string
+ - name: projectID
+ type:
+ scalar: string
+ - name: provider
+ type:
+ scalar: string
+ - name: provisioningStatus
+ type:
+ scalar: string
+ - name: tags
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: atomic
+ - name: vipAddress
+ type:
+ scalar: string
+ - name: vipNetworkID
+ type:
+ scalar: string
+ - name: vipPortID
+ type:
+ scalar: string
+ - name: vipSubnetID
+ type:
+ scalar: string
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LoadBalancerSpec
+ map:
+ fields:
+ - name: cloudCredentialsRef
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.CloudCredentialsReference
+ default: {}
+ - name: import
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LoadBalancerImport
+ - name: managedOptions
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ManagedOptions
+ - name: managementPolicy
+ type:
+ scalar: string
+ - name: resource
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LoadBalancerResourceSpec
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LoadBalancerStatus
+ map:
+ fields:
+ - name: conditions
+ type:
+ list:
+ elementType:
+ namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Condition
+ elementRelationship: associative
+ keys:
+ - type
+ - name: id
+ type:
+ scalar: string
+ - name: resource
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LoadBalancerResourceStatus
- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ManagedOptions
map:
fields:
diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go
index e3166fefe..c529bf6d3 100644
--- a/pkg/clients/applyconfiguration/utils.go
+++ b/pkg/clients/applyconfiguration/utils.go
@@ -160,6 +160,36 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &apiv1alpha1.KeyPairSpecApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("KeyPairStatus"):
return &apiv1alpha1.KeyPairStatusApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("Listener"):
+ return &apiv1alpha1.ListenerApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("ListenerFilter"):
+ return &apiv1alpha1.ListenerFilterApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("ListenerHSTS"):
+ return &apiv1alpha1.ListenerHSTSApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("ListenerImport"):
+ return &apiv1alpha1.ListenerImportApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("ListenerResourceSpec"):
+ return &apiv1alpha1.ListenerResourceSpecApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("ListenerResourceStatus"):
+ return &apiv1alpha1.ListenerResourceStatusApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("ListenerSpec"):
+ return &apiv1alpha1.ListenerSpecApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("ListenerStatus"):
+ return &apiv1alpha1.ListenerStatusApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("LoadBalancer"):
+ return &apiv1alpha1.LoadBalancerApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("LoadBalancerFilter"):
+ return &apiv1alpha1.LoadBalancerFilterApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("LoadBalancerImport"):
+ return &apiv1alpha1.LoadBalancerImportApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("LoadBalancerResourceSpec"):
+ return &apiv1alpha1.LoadBalancerResourceSpecApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("LoadBalancerResourceStatus"):
+ return &apiv1alpha1.LoadBalancerResourceStatusApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("LoadBalancerSpec"):
+ return &apiv1alpha1.LoadBalancerSpecApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("LoadBalancerStatus"):
+ return &apiv1alpha1.LoadBalancerStatusApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("ManagedOptions"):
return &apiv1alpha1.ManagedOptionsApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("Network"):
diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go
index 4d2f93b0d..bf5d162c3 100644
--- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go
+++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go
@@ -34,6 +34,8 @@ type OpenstackV1alpha1Interface interface {
GroupsGetter
ImagesGetter
KeyPairsGetter
+ ListenersGetter
+ LoadBalancersGetter
NetworksGetter
PortsGetter
ProjectsGetter
@@ -78,6 +80,14 @@ func (c *OpenstackV1alpha1Client) KeyPairs(namespace string) KeyPairInterface {
return newKeyPairs(c, namespace)
}
+func (c *OpenstackV1alpha1Client) Listeners(namespace string) ListenerInterface {
+ return newListeners(c, namespace)
+}
+
+func (c *OpenstackV1alpha1Client) LoadBalancers(namespace string) LoadBalancerInterface {
+ return newLoadBalancers(c, namespace)
+}
+
func (c *OpenstackV1alpha1Client) Networks(namespace string) NetworkInterface {
return newNetworks(c, namespace)
}
diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go
index 44feeb45c..08a077521 100644
--- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go
+++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go
@@ -52,6 +52,14 @@ func (c *FakeOpenstackV1alpha1) KeyPairs(namespace string) v1alpha1.KeyPairInter
return newFakeKeyPairs(c, namespace)
}
+func (c *FakeOpenstackV1alpha1) Listeners(namespace string) v1alpha1.ListenerInterface {
+ return newFakeListeners(c, namespace)
+}
+
+func (c *FakeOpenstackV1alpha1) LoadBalancers(namespace string) v1alpha1.LoadBalancerInterface {
+ return newFakeLoadBalancers(c, namespace)
+}
+
func (c *FakeOpenstackV1alpha1) Networks(namespace string) v1alpha1.NetworkInterface {
return newFakeNetworks(c, namespace)
}
diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_listener.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_listener.go
new file mode 100644
index 000000000..21bd701de
--- /dev/null
+++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_listener.go
@@ -0,0 +1,51 @@
+/*
+Copyright 2025 The ORC 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 (
+ v1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1"
+ typedapiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset/typed/api/v1alpha1"
+ gentype "k8s.io/client-go/gentype"
+)
+
+// fakeListeners implements ListenerInterface
+type fakeListeners struct {
+ *gentype.FakeClientWithListAndApply[*v1alpha1.Listener, *v1alpha1.ListenerList, *apiv1alpha1.ListenerApplyConfiguration]
+ Fake *FakeOpenstackV1alpha1
+}
+
+func newFakeListeners(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.ListenerInterface {
+ return &fakeListeners{
+ gentype.NewFakeClientWithListAndApply[*v1alpha1.Listener, *v1alpha1.ListenerList, *apiv1alpha1.ListenerApplyConfiguration](
+ fake.Fake,
+ namespace,
+ v1alpha1.SchemeGroupVersion.WithResource("listeners"),
+ v1alpha1.SchemeGroupVersion.WithKind("Listener"),
+ func() *v1alpha1.Listener { return &v1alpha1.Listener{} },
+ func() *v1alpha1.ListenerList { return &v1alpha1.ListenerList{} },
+ func(dst, src *v1alpha1.ListenerList) { dst.ListMeta = src.ListMeta },
+ func(list *v1alpha1.ListenerList) []*v1alpha1.Listener { return gentype.ToPointerSlice(list.Items) },
+ func(list *v1alpha1.ListenerList, items []*v1alpha1.Listener) {
+ list.Items = gentype.FromPointerSlice(items)
+ },
+ ),
+ fake,
+ }
+}
diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_loadbalancer.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_loadbalancer.go
new file mode 100644
index 000000000..3b870e898
--- /dev/null
+++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_loadbalancer.go
@@ -0,0 +1,53 @@
+/*
+Copyright 2025 The ORC 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 (
+ v1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1"
+ typedapiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset/typed/api/v1alpha1"
+ gentype "k8s.io/client-go/gentype"
+)
+
+// fakeLoadBalancers implements LoadBalancerInterface
+type fakeLoadBalancers struct {
+ *gentype.FakeClientWithListAndApply[*v1alpha1.LoadBalancer, *v1alpha1.LoadBalancerList, *apiv1alpha1.LoadBalancerApplyConfiguration]
+ Fake *FakeOpenstackV1alpha1
+}
+
+func newFakeLoadBalancers(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.LoadBalancerInterface {
+ return &fakeLoadBalancers{
+ gentype.NewFakeClientWithListAndApply[*v1alpha1.LoadBalancer, *v1alpha1.LoadBalancerList, *apiv1alpha1.LoadBalancerApplyConfiguration](
+ fake.Fake,
+ namespace,
+ v1alpha1.SchemeGroupVersion.WithResource("loadbalancers"),
+ v1alpha1.SchemeGroupVersion.WithKind("LoadBalancer"),
+ func() *v1alpha1.LoadBalancer { return &v1alpha1.LoadBalancer{} },
+ func() *v1alpha1.LoadBalancerList { return &v1alpha1.LoadBalancerList{} },
+ func(dst, src *v1alpha1.LoadBalancerList) { dst.ListMeta = src.ListMeta },
+ func(list *v1alpha1.LoadBalancerList) []*v1alpha1.LoadBalancer {
+ return gentype.ToPointerSlice(list.Items)
+ },
+ func(list *v1alpha1.LoadBalancerList, items []*v1alpha1.LoadBalancer) {
+ list.Items = gentype.FromPointerSlice(items)
+ },
+ ),
+ fake,
+ }
+}
diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go
index 56550a99f..58c95a459 100644
--- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go
+++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go
@@ -30,6 +30,10 @@ type ImageExpansion interface{}
type KeyPairExpansion interface{}
+type ListenerExpansion interface{}
+
+type LoadBalancerExpansion interface{}
+
type NetworkExpansion interface{}
type PortExpansion interface{}
diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/listener.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/listener.go
new file mode 100644
index 000000000..118092b73
--- /dev/null
+++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/listener.go
@@ -0,0 +1,74 @@
+/*
+Copyright 2025 The ORC 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 "context"
+
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ applyconfigurationapiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1"
+ scheme "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset/scheme"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ types "k8s.io/apimachinery/pkg/types"
+ watch "k8s.io/apimachinery/pkg/watch"
+ gentype "k8s.io/client-go/gentype"
+)
+
+// ListenersGetter has a method to return a ListenerInterface.
+// A group's client should implement this interface.
+type ListenersGetter interface {
+ Listeners(namespace string) ListenerInterface
+}
+
+// ListenerInterface has methods to work with Listener resources.
+type ListenerInterface interface {
+ Create(ctx context.Context, listener *apiv1alpha1.Listener, opts v1.CreateOptions) (*apiv1alpha1.Listener, error)
+ Update(ctx context.Context, listener *apiv1alpha1.Listener, opts v1.UpdateOptions) (*apiv1alpha1.Listener, error)
+ // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+ UpdateStatus(ctx context.Context, listener *apiv1alpha1.Listener, opts v1.UpdateOptions) (*apiv1alpha1.Listener, 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) (*apiv1alpha1.Listener, error)
+ List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.ListenerList, 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 *apiv1alpha1.Listener, err error)
+ Apply(ctx context.Context, listener *applyconfigurationapiv1alpha1.ListenerApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.Listener, err error)
+ // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().
+ ApplyStatus(ctx context.Context, listener *applyconfigurationapiv1alpha1.ListenerApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.Listener, err error)
+ ListenerExpansion
+}
+
+// listeners implements ListenerInterface
+type listeners struct {
+ *gentype.ClientWithListAndApply[*apiv1alpha1.Listener, *apiv1alpha1.ListenerList, *applyconfigurationapiv1alpha1.ListenerApplyConfiguration]
+}
+
+// newListeners returns a Listeners
+func newListeners(c *OpenstackV1alpha1Client, namespace string) *listeners {
+ return &listeners{
+ gentype.NewClientWithListAndApply[*apiv1alpha1.Listener, *apiv1alpha1.ListenerList, *applyconfigurationapiv1alpha1.ListenerApplyConfiguration](
+ "listeners",
+ c.RESTClient(),
+ scheme.ParameterCodec,
+ namespace,
+ func() *apiv1alpha1.Listener { return &apiv1alpha1.Listener{} },
+ func() *apiv1alpha1.ListenerList { return &apiv1alpha1.ListenerList{} },
+ ),
+ }
+}
diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/loadbalancer.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/loadbalancer.go
new file mode 100644
index 000000000..f807ae79e
--- /dev/null
+++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/loadbalancer.go
@@ -0,0 +1,74 @@
+/*
+Copyright 2025 The ORC 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 "context"
+
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ applyconfigurationapiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1"
+ scheme "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset/scheme"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ types "k8s.io/apimachinery/pkg/types"
+ watch "k8s.io/apimachinery/pkg/watch"
+ gentype "k8s.io/client-go/gentype"
+)
+
+// LoadBalancersGetter has a method to return a LoadBalancerInterface.
+// A group's client should implement this interface.
+type LoadBalancersGetter interface {
+ LoadBalancers(namespace string) LoadBalancerInterface
+}
+
+// LoadBalancerInterface has methods to work with LoadBalancer resources.
+type LoadBalancerInterface interface {
+ Create(ctx context.Context, loadBalancer *apiv1alpha1.LoadBalancer, opts v1.CreateOptions) (*apiv1alpha1.LoadBalancer, error)
+ Update(ctx context.Context, loadBalancer *apiv1alpha1.LoadBalancer, opts v1.UpdateOptions) (*apiv1alpha1.LoadBalancer, error)
+ // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+ UpdateStatus(ctx context.Context, loadBalancer *apiv1alpha1.LoadBalancer, opts v1.UpdateOptions) (*apiv1alpha1.LoadBalancer, 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) (*apiv1alpha1.LoadBalancer, error)
+ List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.LoadBalancerList, 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 *apiv1alpha1.LoadBalancer, err error)
+ Apply(ctx context.Context, loadBalancer *applyconfigurationapiv1alpha1.LoadBalancerApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.LoadBalancer, err error)
+ // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().
+ ApplyStatus(ctx context.Context, loadBalancer *applyconfigurationapiv1alpha1.LoadBalancerApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.LoadBalancer, err error)
+ LoadBalancerExpansion
+}
+
+// loadBalancers implements LoadBalancerInterface
+type loadBalancers struct {
+ *gentype.ClientWithListAndApply[*apiv1alpha1.LoadBalancer, *apiv1alpha1.LoadBalancerList, *applyconfigurationapiv1alpha1.LoadBalancerApplyConfiguration]
+}
+
+// newLoadBalancers returns a LoadBalancers
+func newLoadBalancers(c *OpenstackV1alpha1Client, namespace string) *loadBalancers {
+ return &loadBalancers{
+ gentype.NewClientWithListAndApply[*apiv1alpha1.LoadBalancer, *apiv1alpha1.LoadBalancerList, *applyconfigurationapiv1alpha1.LoadBalancerApplyConfiguration](
+ "loadbalancers",
+ c.RESTClient(),
+ scheme.ParameterCodec,
+ namespace,
+ func() *apiv1alpha1.LoadBalancer { return &apiv1alpha1.LoadBalancer{} },
+ func() *apiv1alpha1.LoadBalancerList { return &apiv1alpha1.LoadBalancerList{} },
+ ),
+ }
+}
diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go
index 1b4497815..b845d63eb 100644
--- a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go
+++ b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go
@@ -36,6 +36,10 @@ type Interface interface {
Images() ImageInformer
// KeyPairs returns a KeyPairInformer.
KeyPairs() KeyPairInformer
+ // Listeners returns a ListenerInformer.
+ Listeners() ListenerInformer
+ // LoadBalancers returns a LoadBalancerInformer.
+ LoadBalancers() LoadBalancerInformer
// Networks returns a NetworkInformer.
Networks() NetworkInformer
// Ports returns a PortInformer.
@@ -105,6 +109,16 @@ func (v *version) KeyPairs() KeyPairInformer {
return &keyPairInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
+// Listeners returns a ListenerInformer.
+func (v *version) Listeners() ListenerInformer {
+ return &listenerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
+}
+
+// LoadBalancers returns a LoadBalancerInformer.
+func (v *version) LoadBalancers() LoadBalancerInformer {
+ return &loadBalancerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
+}
+
// Networks returns a NetworkInformer.
func (v *version) Networks() NetworkInformer {
return &networkInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/listener.go b/pkg/clients/informers/externalversions/api/v1alpha1/listener.go
new file mode 100644
index 000000000..3efbf9a84
--- /dev/null
+++ b/pkg/clients/informers/externalversions/api/v1alpha1/listener.go
@@ -0,0 +1,102 @@
+/*
+Copyright 2025 The ORC 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 "context"
+ time "time"
+
+ v2apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ clientset "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset"
+ internalinterfaces "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/informers/externalversions/internalinterfaces"
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/listers/api/v1alpha1"
+ 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"
+)
+
+// ListenerInformer provides access to a shared informer and lister for
+// Listeners.
+type ListenerInformer interface {
+ Informer() cache.SharedIndexInformer
+ Lister() apiv1alpha1.ListenerLister
+}
+
+type listenerInformer struct {
+ factory internalinterfaces.SharedInformerFactory
+ tweakListOptions internalinterfaces.TweakListOptionsFunc
+ namespace string
+}
+
+// NewListenerInformer constructs a new informer for Listener 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 NewListenerInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
+ return NewFilteredListenerInformer(client, namespace, resyncPeriod, indexers, nil)
+}
+
+// NewFilteredListenerInformer constructs a new informer for Listener 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 NewFilteredListenerInformer(client clientset.Interface, namespace string, 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.OpenstackV1alpha1().Listeners(namespace).List(context.Background(), options)
+ },
+ WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.OpenstackV1alpha1().Listeners(namespace).Watch(context.Background(), options)
+ },
+ ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.OpenstackV1alpha1().Listeners(namespace).List(ctx, options)
+ },
+ WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.OpenstackV1alpha1().Listeners(namespace).Watch(ctx, options)
+ },
+ },
+ &v2apiv1alpha1.Listener{},
+ resyncPeriod,
+ indexers,
+ )
+}
+
+func (f *listenerInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
+ return NewFilteredListenerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
+}
+
+func (f *listenerInformer) Informer() cache.SharedIndexInformer {
+ return f.factory.InformerFor(&v2apiv1alpha1.Listener{}, f.defaultInformer)
+}
+
+func (f *listenerInformer) Lister() apiv1alpha1.ListenerLister {
+ return apiv1alpha1.NewListenerLister(f.Informer().GetIndexer())
+}
diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/loadbalancer.go b/pkg/clients/informers/externalversions/api/v1alpha1/loadbalancer.go
new file mode 100644
index 000000000..6477eaf49
--- /dev/null
+++ b/pkg/clients/informers/externalversions/api/v1alpha1/loadbalancer.go
@@ -0,0 +1,102 @@
+/*
+Copyright 2025 The ORC 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 "context"
+ time "time"
+
+ v2apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ clientset "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset"
+ internalinterfaces "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/informers/externalversions/internalinterfaces"
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/listers/api/v1alpha1"
+ 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"
+)
+
+// LoadBalancerInformer provides access to a shared informer and lister for
+// LoadBalancers.
+type LoadBalancerInformer interface {
+ Informer() cache.SharedIndexInformer
+ Lister() apiv1alpha1.LoadBalancerLister
+}
+
+type loadBalancerInformer struct {
+ factory internalinterfaces.SharedInformerFactory
+ tweakListOptions internalinterfaces.TweakListOptionsFunc
+ namespace string
+}
+
+// NewLoadBalancerInformer constructs a new informer for LoadBalancer 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 NewLoadBalancerInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
+ return NewFilteredLoadBalancerInformer(client, namespace, resyncPeriod, indexers, nil)
+}
+
+// NewFilteredLoadBalancerInformer constructs a new informer for LoadBalancer 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 NewFilteredLoadBalancerInformer(client clientset.Interface, namespace string, 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.OpenstackV1alpha1().LoadBalancers(namespace).List(context.Background(), options)
+ },
+ WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.OpenstackV1alpha1().LoadBalancers(namespace).Watch(context.Background(), options)
+ },
+ ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.OpenstackV1alpha1().LoadBalancers(namespace).List(ctx, options)
+ },
+ WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.OpenstackV1alpha1().LoadBalancers(namespace).Watch(ctx, options)
+ },
+ },
+ &v2apiv1alpha1.LoadBalancer{},
+ resyncPeriod,
+ indexers,
+ )
+}
+
+func (f *loadBalancerInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
+ return NewFilteredLoadBalancerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
+}
+
+func (f *loadBalancerInformer) Informer() cache.SharedIndexInformer {
+ return f.factory.InformerFor(&v2apiv1alpha1.LoadBalancer{}, f.defaultInformer)
+}
+
+func (f *loadBalancerInformer) Lister() apiv1alpha1.LoadBalancerLister {
+ return apiv1alpha1.NewLoadBalancerLister(f.Informer().GetIndexer())
+}
diff --git a/pkg/clients/informers/externalversions/generic.go b/pkg/clients/informers/externalversions/generic.go
index 30911d11f..82648461c 100644
--- a/pkg/clients/informers/externalversions/generic.go
+++ b/pkg/clients/informers/externalversions/generic.go
@@ -65,6 +65,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Images().Informer()}, nil
case v1alpha1.SchemeGroupVersion.WithResource("keypairs"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().KeyPairs().Informer()}, nil
+ case v1alpha1.SchemeGroupVersion.WithResource("listeners"):
+ return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Listeners().Informer()}, nil
+ case v1alpha1.SchemeGroupVersion.WithResource("loadbalancers"):
+ return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().LoadBalancers().Informer()}, nil
case v1alpha1.SchemeGroupVersion.WithResource("networks"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Networks().Informer()}, nil
case v1alpha1.SchemeGroupVersion.WithResource("ports"):
diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go
index ba2888731..99baa2003 100644
--- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go
+++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go
@@ -66,6 +66,22 @@ type KeyPairListerExpansion interface{}
// KeyPairNamespaceLister.
type KeyPairNamespaceListerExpansion interface{}
+// ListenerListerExpansion allows custom methods to be added to
+// ListenerLister.
+type ListenerListerExpansion interface{}
+
+// ListenerNamespaceListerExpansion allows custom methods to be added to
+// ListenerNamespaceLister.
+type ListenerNamespaceListerExpansion interface{}
+
+// LoadBalancerListerExpansion allows custom methods to be added to
+// LoadBalancerLister.
+type LoadBalancerListerExpansion interface{}
+
+// LoadBalancerNamespaceListerExpansion allows custom methods to be added to
+// LoadBalancerNamespaceLister.
+type LoadBalancerNamespaceListerExpansion interface{}
+
// NetworkListerExpansion allows custom methods to be added to
// NetworkLister.
type NetworkListerExpansion interface{}
diff --git a/pkg/clients/listers/api/v1alpha1/listener.go b/pkg/clients/listers/api/v1alpha1/listener.go
new file mode 100644
index 000000000..e1fd7fa30
--- /dev/null
+++ b/pkg/clients/listers/api/v1alpha1/listener.go
@@ -0,0 +1,70 @@
+/*
+Copyright 2025 The ORC 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 (
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ labels "k8s.io/apimachinery/pkg/labels"
+ listers "k8s.io/client-go/listers"
+ cache "k8s.io/client-go/tools/cache"
+)
+
+// ListenerLister helps list Listeners.
+// All objects returned here must be treated as read-only.
+type ListenerLister interface {
+ // List lists all Listeners in the indexer.
+ // Objects returned here must be treated as read-only.
+ List(selector labels.Selector) (ret []*apiv1alpha1.Listener, err error)
+ // Listeners returns an object that can list and get Listeners.
+ Listeners(namespace string) ListenerNamespaceLister
+ ListenerListerExpansion
+}
+
+// listenerLister implements the ListenerLister interface.
+type listenerLister struct {
+ listers.ResourceIndexer[*apiv1alpha1.Listener]
+}
+
+// NewListenerLister returns a new ListenerLister.
+func NewListenerLister(indexer cache.Indexer) ListenerLister {
+ return &listenerLister{listers.New[*apiv1alpha1.Listener](indexer, apiv1alpha1.Resource("listener"))}
+}
+
+// Listeners returns an object that can list and get Listeners.
+func (s *listenerLister) Listeners(namespace string) ListenerNamespaceLister {
+ return listenerNamespaceLister{listers.NewNamespaced[*apiv1alpha1.Listener](s.ResourceIndexer, namespace)}
+}
+
+// ListenerNamespaceLister helps list and get Listeners.
+// All objects returned here must be treated as read-only.
+type ListenerNamespaceLister interface {
+ // List lists all Listeners in the indexer for a given namespace.
+ // Objects returned here must be treated as read-only.
+ List(selector labels.Selector) (ret []*apiv1alpha1.Listener, err error)
+ // Get retrieves the Listener from the indexer for a given namespace and name.
+ // Objects returned here must be treated as read-only.
+ Get(name string) (*apiv1alpha1.Listener, error)
+ ListenerNamespaceListerExpansion
+}
+
+// listenerNamespaceLister implements the ListenerNamespaceLister
+// interface.
+type listenerNamespaceLister struct {
+ listers.ResourceIndexer[*apiv1alpha1.Listener]
+}
diff --git a/pkg/clients/listers/api/v1alpha1/loadbalancer.go b/pkg/clients/listers/api/v1alpha1/loadbalancer.go
new file mode 100644
index 000000000..5a144dffc
--- /dev/null
+++ b/pkg/clients/listers/api/v1alpha1/loadbalancer.go
@@ -0,0 +1,70 @@
+/*
+Copyright 2025 The ORC 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 (
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ labels "k8s.io/apimachinery/pkg/labels"
+ listers "k8s.io/client-go/listers"
+ cache "k8s.io/client-go/tools/cache"
+)
+
+// LoadBalancerLister helps list LoadBalancers.
+// All objects returned here must be treated as read-only.
+type LoadBalancerLister interface {
+ // List lists all LoadBalancers in the indexer.
+ // Objects returned here must be treated as read-only.
+ List(selector labels.Selector) (ret []*apiv1alpha1.LoadBalancer, err error)
+ // LoadBalancers returns an object that can list and get LoadBalancers.
+ LoadBalancers(namespace string) LoadBalancerNamespaceLister
+ LoadBalancerListerExpansion
+}
+
+// loadBalancerLister implements the LoadBalancerLister interface.
+type loadBalancerLister struct {
+ listers.ResourceIndexer[*apiv1alpha1.LoadBalancer]
+}
+
+// NewLoadBalancerLister returns a new LoadBalancerLister.
+func NewLoadBalancerLister(indexer cache.Indexer) LoadBalancerLister {
+ return &loadBalancerLister{listers.New[*apiv1alpha1.LoadBalancer](indexer, apiv1alpha1.Resource("loadbalancer"))}
+}
+
+// LoadBalancers returns an object that can list and get LoadBalancers.
+func (s *loadBalancerLister) LoadBalancers(namespace string) LoadBalancerNamespaceLister {
+ return loadBalancerNamespaceLister{listers.NewNamespaced[*apiv1alpha1.LoadBalancer](s.ResourceIndexer, namespace)}
+}
+
+// LoadBalancerNamespaceLister helps list and get LoadBalancers.
+// All objects returned here must be treated as read-only.
+type LoadBalancerNamespaceLister interface {
+ // List lists all LoadBalancers in the indexer for a given namespace.
+ // Objects returned here must be treated as read-only.
+ List(selector labels.Selector) (ret []*apiv1alpha1.LoadBalancer, err error)
+ // Get retrieves the LoadBalancer from the indexer for a given namespace and name.
+ // Objects returned here must be treated as read-only.
+ Get(name string) (*apiv1alpha1.LoadBalancer, error)
+ LoadBalancerNamespaceListerExpansion
+}
+
+// loadBalancerNamespaceLister implements the LoadBalancerNamespaceLister
+// interface.
+type loadBalancerNamespaceLister struct {
+ listers.ResourceIndexer[*apiv1alpha1.LoadBalancer]
+}
diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md
index 23b3b2403..7cb616f3f 100644
--- a/website/docs/crd-reference.md
+++ b/website/docs/crd-reference.md
@@ -16,6 +16,8 @@ Package v1alpha1 contains API Schema definitions for the openstack v1alpha1 API
- [Group](#group)
- [Image](#image)
- [KeyPair](#keypair)
+- [Listener](#listener)
+- [LoadBalancer](#loadbalancer)
- [Network](#network)
- [Port](#port)
- [Project](#project)
@@ -169,6 +171,8 @@ _Appears in:_
- [GroupSpec](#groupspec)
- [ImageSpec](#imagespec)
- [KeyPairSpec](#keypairspec)
+- [ListenerSpec](#listenerspec)
+- [LoadBalancerSpec](#loadbalancerspec)
- [NetworkSpec](#networkspec)
- [PortSpec](#portspec)
- [ProjectSpec](#projectspec)
@@ -1005,6 +1009,7 @@ _Appears in:_
- [FloatingIPFilter](#floatingipfilter)
- [FloatingIPResourceSpec](#floatingipresourcespec)
- [HostRoute](#hostroute)
+- [LoadBalancerResourceSpec](#loadbalancerresourcespec)
- [SubnetFilter](#subnetfilter)
- [SubnetGateway](#subnetgateway)
- [SubnetResourceSpec](#subnetresourcespec)
@@ -1616,6 +1621,10 @@ _Appears in:_
- [FloatingIPResourceSpec](#floatingipresourcespec)
- [GroupFilter](#groupfilter)
- [GroupResourceSpec](#groupresourcespec)
+- [ListenerFilter](#listenerfilter)
+- [ListenerResourceSpec](#listenerresourcespec)
+- [LoadBalancerFilter](#loadbalancerfilter)
+- [LoadBalancerResourceSpec](#loadbalancerresourcespec)
- [NetworkFilter](#networkfilter)
- [NetworkResourceSpec](#networkresourcespec)
- [PortFilter](#portfilter)
@@ -1637,6 +1646,434 @@ _Appears in:_
+#### Listener
+
+
+
+Listener is the Schema for an ORC resource.
+
+
+
+
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | |
+| `kind` _string_ | `Listener` | | |
+| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | |
+| `spec` _[ListenerSpec](#listenerspec)_ | spec specifies the desired state of the resource. | | |
+| `status` _[ListenerStatus](#listenerstatus)_ | status defines the observed state of the resource. | | |
+
+
+#### ListenerClientAuthentication
+
+_Underlying type:_ _string_
+
+ListenerClientAuthentication represents TLS client authentication mode.
+
+_Validation:_
+- Enum: [NONE OPTIONAL MANDATORY]
+
+_Appears in:_
+- [ListenerResourceSpec](#listenerresourcespec)
+
+| Field | Description |
+| --- | --- |
+| `NONE` | |
+| `OPTIONAL` | |
+| `MANDATORY` | |
+
+
+#### ListenerFilter
+
+
+
+ListenerFilter defines an existing resource by its properties.
+
+_Validation:_
+- MinProperties: 1
+
+_Appears in:_
+- [ListenerImport](#listenerimport)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `name` _[OpenStackName](#openstackname)_ | name of the existing resource. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
|
+| `description` _string_ | description of the existing resource. | | MaxLength: 255
MinLength: 1
|
+| `loadBalancerRef` _[KubernetesNameRef](#kubernetesnameref)_ | loadBalancerRef filters by the LoadBalancer this listener belongs to. | | MaxLength: 253
MinLength: 1
|
+| `protocol` _[ListenerProtocol](#listenerprotocol)_ | protocol filters by the protocol used by the listener. | | Enum: [HTTP HTTPS SCTP PROMETHEUS TCP TERMINATED_HTTPS UDP]
|
+| `protocolPort` _integer_ | protocolPort filters by the port used by the listener. | | Maximum: 65535
Minimum: 1
|
+| `tags` _[ListenerTag](#listenertag) array_ | tags is a list of tags to filter by. If specified, the resource must
have all of the tags specified to be included in the result. | | MaxItems: 64
MaxLength: 255
MinLength: 1
|
+| `tagsAny` _[ListenerTag](#listenertag) array_ | tagsAny is a list of tags to filter by. If specified, the resource
must have at least one of the tags specified to be included in the
result. | | MaxItems: 64
MaxLength: 255
MinLength: 1
|
+| `notTags` _[ListenerTag](#listenertag) array_ | notTags is a list of tags to filter by. If specified, resources which
contain all of the given tags will be excluded from the result. | | MaxItems: 64
MaxLength: 255
MinLength: 1
|
+| `notTagsAny` _[ListenerTag](#listenertag) array_ | notTagsAny is a list of tags to filter by. If specified, resources
which contain any of the given tags will be excluded from the result. | | MaxItems: 64
MaxLength: 255
MinLength: 1
|
+
+
+#### ListenerHSTS
+
+
+
+ListenerHSTS represents HTTP Strict Transport Security configuration.
+
+
+
+_Appears in:_
+- [ListenerResourceSpec](#listenerresourcespec)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `maxAge` _integer_ | maxAge is the maximum time in seconds that the browser should remember
that this site is only to be accessed using HTTPS. | | Minimum: 0
|
+| `includeSubDomains` _boolean_ | includeSubDomains specifies whether this rule applies to all subdomains. | | |
+| `preload` _boolean_ | preload specifies whether the domain should be included in browsers' preload list. | | |
+
+
+#### ListenerImport
+
+
+
+ListenerImport specifies an existing resource which will be imported instead of
+creating a new one
+
+_Validation:_
+- MaxProperties: 1
+- MinProperties: 1
+
+_Appears in:_
+- [ListenerSpec](#listenerspec)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `id` _string_ | id contains the unique identifier of an existing OpenStack resource. Note
that when specifying an import by ID, the resource MUST already exist.
The ORC object will enter an error state if the resource does not exist. | | Format: uuid
|
+| `filter` _[ListenerFilter](#listenerfilter)_ | filter contains a resource query which is expected to return a single
result. The controller will continue to retry if filter returns no
results. If filter returns multiple results the controller will set an
error state and will not continue to retry. | | MinProperties: 1
|
+
+
+#### ListenerProtocol
+
+_Underlying type:_ _string_
+
+ListenerProtocol represents the protocol used by a listener.
+
+_Validation:_
+- Enum: [HTTP HTTPS SCTP PROMETHEUS TCP TERMINATED_HTTPS UDP]
+
+_Appears in:_
+- [ListenerFilter](#listenerfilter)
+- [ListenerResourceSpec](#listenerresourcespec)
+
+| Field | Description |
+| --- | --- |
+| `HTTP` | |
+| `HTTPS` | |
+| `SCTP` | |
+| `PROMETHEUS` | |
+| `TCP` | |
+| `TERMINATED_HTTPS` | |
+| `UDP` | |
+
+
+#### ListenerResourceSpec
+
+
+
+ListenerResourceSpec contains the desired state of the resource.
+
+
+
+_Appears in:_
+- [ListenerSpec](#listenerspec)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
|
+| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
|
+| `loadBalancerRef` _[KubernetesNameRef](#kubernetesnameref)_ | loadBalancerRef is a reference to the LoadBalancer this listener belongs to. | | MaxLength: 253
MinLength: 1
|
+| `protocol` _[ListenerProtocol](#listenerprotocol)_ | protocol is the protocol the listener will use. | | Enum: [HTTP HTTPS SCTP PROMETHEUS TCP TERMINATED_HTTPS UDP]
|
+| `protocolPort` _integer_ | protocolPort is the port on which the listener will accept connections. | | Maximum: 65535
Minimum: 1
|
+| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the listener, which is up (true) or down (false). | | |
+| `connectionLimit` _integer_ | connectionLimit is the maximum number of connections permitted for this listener.
Default value is -1 which represents infinite connections. | | Minimum: -1
|
+| `defaultTLSContainerRef` _string_ | defaultTLSContainerRef is a reference to a secret containing a PKCS12 format
certificate/key bundle for TERMINATED_HTTPS listeners. | | MaxLength: 255
|
+| `sniContainerRefs` _string array_ | sniContainerRefs is a list of references to secrets containing PKCS12 format
certificate/key bundles for TERMINATED_HTTPS listeners using SNI. | | MaxItems: 25
items:MaxLength: 255
|
+| `defaultPoolRef` _[KubernetesNameRef](#kubernetesnameref)_ | defaultPoolRef is a reference to the default Pool for this listener. | | MaxLength: 253
MinLength: 1
|
+| `insertHeaders` _object (keys:string, values:string)_ | insertHeaders is a dictionary of optional headers to insert into the request
before it is sent to the backend member. | | |
+| `timeoutClientData` _integer_ | timeoutClientData is the frontend client inactivity timeout in milliseconds. | | Minimum: 0
|
+| `timeoutMemberConnect` _integer_ | timeoutMemberConnect is the backend member connection timeout in milliseconds. | | Minimum: 0
|
+| `timeoutMemberData` _integer_ | timeoutMemberData is the backend member inactivity timeout in milliseconds. | | Minimum: 0
|
+| `timeoutTCPInspect` _integer_ | timeoutTCPInspect is the time in milliseconds to wait for additional TCP packets
for content inspection. | | Minimum: 0
|
+| `allowedCIDRs` _string array_ | allowedCIDRs is a list of IPv4/IPv6 CIDRs that are permitted to connect to this listener. | | MaxItems: 256
items:MaxLength: 64
|
+| `tlsCiphers` _string_ | tlsCiphers is a colon-separated list of ciphers for TLS-terminated listeners. | | MaxLength: 2048
|
+| `tlsVersions` _string array_ | tlsVersions is a list of TLS protocol versions to be used by the listener. | | MaxItems: 10
items:MaxLength: 32
|
+| `alpnProtocols` _string array_ | alpnProtocols is a list of ALPN protocols for TLS-enabled listeners. | | MaxItems: 10
items:MaxLength: 32
|
+| `clientAuthentication` _[ListenerClientAuthentication](#listenerclientauthentication)_ | clientAuthentication is the TLS client authentication mode. | | Enum: [NONE OPTIONAL MANDATORY]
|
+| `clientCATLSContainerRef` _string_ | clientCATLSContainerRef is a reference to a secret containing the CA certificate
for client authentication. | | MaxLength: 255
|
+| `clientCRLContainerRef` _string_ | clientCRLContainerRef is a reference to a secret containing the CA revocation list
for client authentication. | | MaxLength: 255
|
+| `hsts` _[ListenerHSTS](#listenerhsts)_ | hsts is the HTTP Strict Transport Security configuration. | | |
+| `tags` _[ListenerTag](#listenertag) array_ | tags is a list of tags which will be applied to the listener. | | MaxItems: 64
MaxLength: 255
MinLength: 1
|
+
+
+#### ListenerResourceStatus
+
+
+
+ListenerResourceStatus represents the observed state of the resource.
+
+
+
+_Appears in:_
+- [ListenerStatus](#listenerstatus)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `name` _string_ | name is a human-readable name for the resource. | | MaxLength: 1024
|
+| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
|
+| `loadBalancerID` _string_ | loadBalancerID is the ID of the LoadBalancer this listener belongs to. | | MaxLength: 1024
|
+| `protocol` _string_ | protocol is the protocol used by the listener. | | MaxLength: 64
|
+| `protocolPort` _integer_ | protocolPort is the port used by the listener. | | |
+| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the listener,
which is up (true) or down (false). | | |
+| `connectionLimit` _integer_ | connectionLimit is the maximum number of connections permitted for this listener. | | |
+| `defaultPoolID` _string_ | defaultPoolID is the ID of the default pool for this listener. | | MaxLength: 1024
|
+| `provisioningStatus` _string_ | provisioningStatus is the provisioning status of the listener. | | MaxLength: 1024
|
+| `operatingStatus` _string_ | operatingStatus is the operating status of the listener. | | MaxLength: 1024
|
+| `allowedCIDRs` _string array_ | allowedCIDRs is the list of CIDRs permitted to connect to this listener. | | MaxItems: 256
items:MaxLength: 64
|
+| `timeoutClientData` _integer_ | timeoutClientData is the frontend client inactivity timeout in milliseconds. | | |
+| `timeoutMemberConnect` _integer_ | timeoutMemberConnect is the backend member connection timeout in milliseconds. | | |
+| `timeoutMemberData` _integer_ | timeoutMemberData is the backend member inactivity timeout in milliseconds. | | |
+| `timeoutTCPInspect` _integer_ | timeoutTCPInspect is the time to wait for additional TCP packets in milliseconds. | | |
+| `insertHeaders` _object (keys:string, values:string)_ | insertHeaders is a dictionary of headers inserted into the request. | | |
+| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 64
items:MaxLength: 255
|
+
+
+#### ListenerSpec
+
+
+
+ListenerSpec defines the desired state of an ORC object.
+
+
+
+_Appears in:_
+- [Listener](#listener)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `import` _[ListenerImport](#listenerimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
|
+| `resource` _[ListenerResourceSpec](#listenerresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | |
+| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
|
+| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | |
+| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | |
+
+
+#### ListenerStatus
+
+
+
+ListenerStatus defines the observed state of an ORC resource.
+
+
+
+_Appears in:_
+- [Listener](#listener)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
|
+| `id` _string_ | id is the unique identifier of the OpenStack resource. | | |
+| `resource` _[ListenerResourceStatus](#listenerresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | |
+
+
+#### ListenerTag
+
+_Underlying type:_ _string_
+
+
+
+_Validation:_
+- MaxLength: 255
+- MinLength: 1
+
+_Appears in:_
+- [ListenerFilter](#listenerfilter)
+- [ListenerResourceSpec](#listenerresourcespec)
+
+
+
+#### LoadBalancer
+
+
+
+LoadBalancer is the Schema for an ORC resource.
+
+
+
+
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | |
+| `kind` _string_ | `LoadBalancer` | | |
+| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | |
+| `spec` _[LoadBalancerSpec](#loadbalancerspec)_ | spec specifies the desired state of the resource. | | |
+| `status` _[LoadBalancerStatus](#loadbalancerstatus)_ | status defines the observed state of the resource. | | |
+
+
+#### LoadBalancerFilter
+
+
+
+LoadBalancerFilter defines an existing resource by its properties
+
+_Validation:_
+- MinProperties: 1
+
+_Appears in:_
+- [LoadBalancerImport](#loadbalancerimport)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
|
+| `description` _string_ | description of the existing resource | | MaxLength: 255
MinLength: 1
|
+| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
|
+| `vipSubnetRef` _[KubernetesNameRef](#kubernetesnameref)_ | vipSubnetRef filters by the subnet on which the load balancer's address is allocated. | | MaxLength: 253
MinLength: 1
|
+| `vipNetworkRef` _[KubernetesNameRef](#kubernetesnameref)_ | vipNetworkRef filters by the network on which the load balancer's address is allocated. | | MaxLength: 253
MinLength: 1
|
+| `vipPortRef` _[KubernetesNameRef](#kubernetesnameref)_ | vipPortRef filters by the neutron port used for the VIP. | | MaxLength: 253
MinLength: 1
|
+| `availabilityZone` _string_ | availabilityZone is the availability zone in which to create the load balancer. | | MaxLength: 255
|
+| `provider` _string_ | provider filters by the name of the load balancer provider. | | MaxLength: 255
|
+| `vipAddress` _string_ | vipAddress filters by the IP address of the load balancer's VIP. | | MaxLength: 64
|
+| `tags` _[LoadBalancerTag](#loadbalancertag) array_ | tags is a list of tags to filter by. If specified, the resource must
have all of the tags specified to be included in the result. | | MaxItems: 64
MaxLength: 255
MinLength: 1
|
+| `tagsAny` _[LoadBalancerTag](#loadbalancertag) array_ | tagsAny is a list of tags to filter by. If specified, the resource
must have at least one of the tags specified to be included in the
result. | | MaxItems: 64
MaxLength: 255
MinLength: 1
|
+| `notTags` _[LoadBalancerTag](#loadbalancertag) array_ | notTags is a list of tags to filter by. If specified, resources which
contain all of the given tags will be excluded from the result. | | MaxItems: 64
MaxLength: 255
MinLength: 1
|
+| `notTagsAny` _[LoadBalancerTag](#loadbalancertag) array_ | notTagsAny is a list of tags to filter by. If specified, resources
which contain any of the given tags will be excluded from the result. | | MaxItems: 64
MaxLength: 255
MinLength: 1
|
+
+
+#### LoadBalancerImport
+
+
+
+LoadBalancerImport specifies an existing resource which will be imported instead of
+creating a new one
+
+_Validation:_
+- MaxProperties: 1
+- MinProperties: 1
+
+_Appears in:_
+- [LoadBalancerSpec](#loadbalancerspec)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `id` _string_ | id contains the unique identifier of an existing OpenStack resource. Note
that when specifying an import by ID, the resource MUST already exist.
The ORC object will enter an error state if the resource does not exist. | | Format: uuid
|
+| `filter` _[LoadBalancerFilter](#loadbalancerfilter)_ | filter contains a resource query which is expected to return a single
result. The controller will continue to retry if filter returns no
results. If filter returns multiple results the controller will set an
error state and will not continue to retry. | | MinProperties: 1
|
+
+
+#### LoadBalancerResourceSpec
+
+
+
+LoadBalancerResourceSpec contains the desired state of the resource.
+
+
+
+_Appears in:_
+- [LoadBalancerSpec](#loadbalancerspec)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
|
+| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
|
+| `vipSubnetRef` _[KubernetesNameRef](#kubernetesnameref)_ | vipSubnetRef is the subnet on which to allocate the load balancer's address. | | MaxLength: 253
MinLength: 1
|
+| `vipNetworkRef` _[KubernetesNameRef](#kubernetesnameref)_ | vipNetworkRef is the network on which to allocate the load balancer's address. | | MaxLength: 253
MinLength: 1
|
+| `vipPortRef` _[KubernetesNameRef](#kubernetesnameref)_ | vipPortRef is a reference to a neutron port to use for the VIP. If the port
has more than one subnet you must specify either vipSubnetRef or vipAddress
to clarify which address should be used for the VIP. | | MaxLength: 253
MinLength: 1
|
+| `flavorRef` _[KubernetesNameRef](#kubernetesnameref)_ | flavorRef is a reference to the ORC Flavor which this resource is associated with. | | MaxLength: 253
MinLength: 1
|
+| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
|
+| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the load balancer, which is up (true) or down (false) | | |
+| `availabilityZone` _string_ | availabilityZone is the availability zone in which to create the load balancer. | | MaxLength: 255
|
+| `provider` _string_ | provider is the name of the load balancer provider. | | MaxLength: 255
|
+| `vipAddress` _[IPvAny](#ipvany)_ | vipAddress is the specific IP address to use for the VIP (optional).
If not specified, one is allocated automatically from the subnet. | | MaxLength: 45
MinLength: 1
|
+| `tags` _[LoadBalancerTag](#loadbalancertag) array_ | tags is a list of tags which will be applied to the load balancer. | | MaxItems: 64
MaxLength: 255
MinLength: 1
|
+
+
+#### LoadBalancerResourceStatus
+
+
+
+LoadBalancerResourceStatus represents the observed state of the resource.
+
+
+
+_Appears in:_
+- [LoadBalancerStatus](#loadbalancerstatus)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
|
+| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
|
+| `vipSubnetID` _string_ | vipSubnetID is the ID of the Subnet to which the resource is associated. | | MaxLength: 1024
|
+| `vipNetworkID` _string_ | vipNetworkID is the ID of the Network to which the resource is associated. | | MaxLength: 1024
|
+| `vipPortID` _string_ | vipPortID is the ID of the Port to which the resource is associated. | | MaxLength: 1024
|
+| `flavorID` _string_ | flavorID is the ID of the Flavor to which the resource is associated. | | MaxLength: 1024
|
+| `projectID` _string_ | projectID is the ID of the Project to which the resource is associated. | | MaxLength: 1024
|
+| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the load balancer,
which is up (true) or down (false). | | |
+| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 64
items:MaxLength: 255
|
+| `availabilityZone` _string_ | availabilityZone is the availability zone where the load balancer is located. | | MaxLength: 1024
|
+| `provisioningStatus` _string_ | provisioningStatus is the provisioning status of the load balancer.
This value is ACTIVE, PENDING_CREATE or ERROR. | | MaxLength: 1024
|
+| `operatingStatus` _string_ | operatingStatus is the operating status of the load balancer,
such as ONLINE or OFFLINE. | | MaxLength: 1024
|
+| `provider` _string_ | provider is the name of the load balancer provider. | | MaxLength: 1024
|
+| `vipAddress` _string_ | vipAddress is the IP address of the load balancer's VIP. | | MaxLength: 64
|
+
+
+#### LoadBalancerSpec
+
+
+
+LoadBalancerSpec defines the desired state of an ORC object.
+
+
+
+_Appears in:_
+- [LoadBalancer](#loadbalancer)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `import` _[LoadBalancerImport](#loadbalancerimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
|
+| `resource` _[LoadBalancerResourceSpec](#loadbalancerresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | |
+| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
|
+| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | |
+| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | |
+
+
+#### LoadBalancerStatus
+
+
+
+LoadBalancerStatus defines the observed state of an ORC resource.
+
+
+
+_Appears in:_
+- [LoadBalancer](#loadbalancer)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
|
+| `id` _string_ | id is the unique identifier of the OpenStack resource. | | |
+| `resource` _[LoadBalancerResourceStatus](#loadbalancerresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | |
+
+
+#### LoadBalancerTag
+
+_Underlying type:_ _string_
+
+
+
+_Validation:_
+- MaxLength: 255
+- MinLength: 1
+
+_Appears in:_
+- [LoadBalancerFilter](#loadbalancerfilter)
+- [LoadBalancerResourceSpec](#loadbalancerresourcespec)
+
+
+
#### MAC
_Underlying type:_ _string_
@@ -1682,6 +2119,8 @@ _Appears in:_
- [GroupSpec](#groupspec)
- [ImageSpec](#imagespec)
- [KeyPairSpec](#keypairspec)
+- [ListenerSpec](#listenerspec)
+- [LoadBalancerSpec](#loadbalancerspec)
- [NetworkSpec](#networkspec)
- [PortSpec](#portspec)
- [ProjectSpec](#projectspec)
@@ -1716,6 +2155,8 @@ _Appears in:_
- [GroupSpec](#groupspec)
- [ImageSpec](#imagespec)
- [KeyPairSpec](#keypairspec)
+- [ListenerSpec](#listenerspec)
+- [LoadBalancerSpec](#loadbalancerspec)
- [NetworkSpec](#networkspec)
- [PortSpec](#portspec)
- [ProjectSpec](#projectspec)
@@ -2009,6 +2450,10 @@ _Appears in:_
- [ImageResourceSpec](#imageresourcespec)
- [KeyPairFilter](#keypairfilter)
- [KeyPairResourceSpec](#keypairresourcespec)
+- [ListenerFilter](#listenerfilter)
+- [ListenerResourceSpec](#listenerresourcespec)
+- [LoadBalancerFilter](#loadbalancerfilter)
+- [LoadBalancerResourceSpec](#loadbalancerresourcespec)
- [NetworkFilter](#networkfilter)
- [NetworkResourceSpec](#networkresourcespec)
- [PortFilter](#portfilter)
diff --git a/website/docs/design/dependency-resolver.md b/website/docs/design/dependency-resolver.md
new file mode 100644
index 000000000..fcfb510cf
--- /dev/null
+++ b/website/docs/design/dependency-resolver.md
@@ -0,0 +1,391 @@
+# Dependency Resolver Builder Pattern
+
+## Problem Statement
+
+Controllers frequently need to resolve `*KubernetesNameRef` fields to their OpenStack IDs. This results in repetitive boilerplate code across controllers:
+
+```go
+var networkID string
+if filter.NetworkRef != nil {
+ networkKey := client.ObjectKey{Name: string(*filter.NetworkRef), Namespace: obj.Namespace}
+ if err := actuator.k8sClient.Get(ctx, networkKey, network); err != nil {
+ if apierrors.IsNotFound(err) {
+ reconcileStatus = reconcileStatus.WithReconcileStatus(
+ progress.WaitingOnObject("Network", networkKey.Name, progress.WaitingOnCreation))
+ } else {
+ reconcileStatus = reconcileStatus.WithReconcileStatus(
+ progress.WrapError(fmt.Errorf("fetching network %s: %w", networkKey.Name, err)))
+ }
+ } else {
+ if !orcv1alpha1.IsAvailable(network) || network.Status.ID == nil {
+ reconcileStatus = reconcileStatus.WithReconcileStatus(
+ progress.WaitingOnObject("Network", networkKey.Name, progress.WaitingOnReady))
+ } else {
+ networkID = *network.Status.ID
+ }
+ }
+}
+// Repeat for Subnet, Project, Port, etc...
+```
+
+This pattern is repeated across:
+- `ListOSResourcesForImport` (resolving filter refs)
+- `CreateResource` (resolving spec refs)
+- Various reconcilers
+
+## Proposed Solution
+
+A builder-pattern resolver that:
+1. Chains multiple dependency resolutions
+2. Accumulates errors and wait states
+3. Returns resolved IDs in a type-safe way
+4. Provides clear, readable code
+
+## API Design
+
+### Core Types
+
+```go
+package dependency
+
+// Resolver accumulates dependency resolutions and their statuses
+type Resolver struct {
+ ctx context.Context
+ k8sClient client.Client
+ namespace string
+ reconcileStatus progress.ReconcileStatus
+ resolved map[string]string // kind -> ID
+}
+
+// NewResolver creates a new dependency resolver
+func NewResolver(ctx context.Context, k8sClient client.Client, namespace string) *Resolver
+
+// Optional resolves a ref if it's non-nil, skips if nil
+// T is the ORC resource type (e.g., *orcv1alpha1.Network)
+func (r *Resolver) Optional[TP DependencyType[T], T any](
+ ref *KubernetesNameRef,
+ kind string,
+ getID func(TP) *string,
+) *Resolver
+
+// Required resolves a ref that must exist (adds error if nil)
+func (r *Resolver) Required[TP DependencyType[T], T any](
+ ref KubernetesNameRef,
+ kind string,
+ getID func(TP) *string,
+) *Resolver
+
+// Result returns the resolved IDs and accumulated status
+func (r *Resolver) Result() (ResolvedDependencies, progress.ReconcileStatus)
+
+// ResolvedDependencies provides type-safe access to resolved IDs
+type ResolvedDependencies struct {
+ ids map[string]string
+}
+
+func (r ResolvedDependencies) Get(kind string) string
+func (r ResolvedDependencies) GetPtr(kind string) *string
+```
+
+### Usage Examples
+
+#### Example 1: ListOSResourcesForImport (Filter Resolution)
+
+**Before:**
+```go
+func (actuator floatingipCreateActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) {
+ var reconcileStatus progress.ReconcileStatus
+
+ network := &orcv1alpha1.Network{}
+ if filter.FloatingNetworkRef != nil {
+ networkKey := client.ObjectKey{Name: string(ptr.Deref(filter.FloatingNetworkRef, "")), Namespace: obj.Namespace}
+ if err := actuator.k8sClient.Get(ctx, networkKey, network); err != nil {
+ // ... 15 lines of error handling
+ }
+ }
+
+ port := &orcv1alpha1.Port{}
+ if filter.PortRef != nil {
+ // ... another 15 lines
+ }
+
+ project := &orcv1alpha1.Project{}
+ if filter.ProjectRef != nil {
+ // ... another 15 lines
+ }
+
+ if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
+ return nil, reconcileStatus
+ }
+
+ listOpts := floatingips.ListOpts{
+ PortID: ptr.Deref(port.Status.ID, ""),
+ FloatingNetworkID: ptr.Deref(network.Status.ID, ""),
+ ProjectID: ptr.Deref(project.Status.ID, ""),
+ // ...
+ }
+ // ...
+}
+```
+
+**After:**
+```go
+func (actuator floatingipCreateActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) {
+ deps, rs := dependency.NewResolver(ctx, actuator.k8sClient, obj.Namespace).
+ Optional[*orcv1alpha1.Network](filter.FloatingNetworkRef, "Network", func(n *orcv1alpha1.Network) *string { return n.Status.ID }).
+ Optional[*orcv1alpha1.Port](filter.PortRef, "Port", func(p *orcv1alpha1.Port) *string { return p.Status.ID }).
+ Optional[*orcv1alpha1.Project](filter.ProjectRef, "Project", func(p *orcv1alpha1.Project) *string { return p.Status.ID }).
+ Result()
+
+ if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule {
+ return nil, rs
+ }
+
+ listOpts := floatingips.ListOpts{
+ PortID: deps.Get("Port"),
+ FloatingNetworkID: deps.Get("Network"),
+ ProjectID: deps.Get("Project"),
+ // ...
+ }
+ // ...
+}
+```
+
+#### Example 2: CreateResource (Spec Resolution)
+
+**Before:**
+```go
+func (actuator loadbalancerActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) {
+ resource := obj.Spec.Resource
+ var reconcileStatus progress.ReconcileStatus
+
+ var vipSubnetID string
+ if resource.VipSubnetRef != nil {
+ subnet, subnetDepRS := subnetDependency.GetDependency(
+ ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Subnet) bool {
+ return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
+ },
+ )
+ reconcileStatus = reconcileStatus.WithReconcileStatus(subnetDepRS)
+ if subnet != nil {
+ vipSubnetID = ptr.Deref(subnet.Status.ID, "")
+ }
+ }
+
+ var vipNetworkID string
+ if resource.VipNetworkRef != nil {
+ // ... repeat pattern
+ }
+
+ // ... more dependencies
+}
+```
+
+**After:**
+```go
+func (actuator loadbalancerActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) {
+ resource := obj.Spec.Resource
+
+ deps, rs := dependency.NewResolver(ctx, actuator.k8sClient, obj.Namespace).
+ Optional[*orcv1alpha1.Subnet](resource.VipSubnetRef, "Subnet", func(s *orcv1alpha1.Subnet) *string { return s.Status.ID }).
+ Optional[*orcv1alpha1.Network](resource.VipNetworkRef, "Network", func(n *orcv1alpha1.Network) *string { return n.Status.ID }).
+ Optional[*orcv1alpha1.Port](resource.VipPortRef, "Port", func(p *orcv1alpha1.Port) *string { return p.Status.ID }).
+ Optional[*orcv1alpha1.Flavor](resource.FlavorRef, "Flavor", func(f *orcv1alpha1.Flavor) *string { return f.Status.ID }).
+ Optional[*orcv1alpha1.Project](resource.ProjectRef, "Project", func(p *orcv1alpha1.Project) *string { return p.Status.ID }).
+ Result()
+
+ if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule {
+ return nil, rs
+ }
+
+ createOpts := loadbalancers.CreateOpts{
+ VipSubnetID: deps.Get("Subnet"),
+ VipNetworkID: deps.Get("Network"),
+ VipPortID: deps.Get("Port"),
+ FlavorID: deps.Get("Flavor"),
+ ProjectID: deps.Get("Project"),
+ // ...
+ }
+ // ...
+}
+```
+
+## Implementation
+
+### File: `internal/util/dependency/resolver.go`
+
+```go
+package dependency
+
+import (
+ "context"
+ "fmt"
+
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/utils/ptr"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress"
+)
+
+// Resolver accumulates dependency resolutions and their statuses.
+type Resolver struct {
+ ctx context.Context
+ k8sClient client.Client
+ namespace string
+ reconcileStatus progress.ReconcileStatus
+ resolved map[string]string
+}
+
+// NewResolver creates a new dependency resolver for the given namespace.
+func NewResolver(ctx context.Context, k8sClient client.Client, namespace string) *Resolver {
+ return &Resolver{
+ ctx: ctx,
+ k8sClient: k8sClient,
+ namespace: namespace,
+ resolved: make(map[string]string),
+ }
+}
+
+// Optional resolves a ref if it's non-nil. If the ref is nil, it's skipped.
+// The kind parameter is used for error messages and as the key in resolved dependencies.
+// The getID function extracts the OpenStack ID from the resolved object.
+func Optional[TP DependencyType[T], T any](r *Resolver, ref *orcv1alpha1.KubernetesNameRef, kind string, getID func(TP) *string) *Resolver {
+ if ref == nil {
+ return r
+ }
+
+ name := string(*ref)
+ var obj TP = new(T)
+ objectKey := client.ObjectKey{Name: name, Namespace: r.namespace}
+
+ if err := r.k8sClient.Get(r.ctx, objectKey, obj); err != nil {
+ if apierrors.IsNotFound(err) {
+ r.reconcileStatus = r.reconcileStatus.WaitingOnObject(kind, name, progress.WaitingOnCreation)
+ } else {
+ r.reconcileStatus = r.reconcileStatus.WithError(fmt.Errorf("fetching %s %s: %w", kind, name, err))
+ }
+ return r
+ }
+
+ if !orcv1alpha1.IsAvailable(obj) {
+ r.reconcileStatus = r.reconcileStatus.WaitingOnObject(kind, name, progress.WaitingOnReady)
+ return r
+ }
+
+ id := getID(obj)
+ if id == nil {
+ r.reconcileStatus = r.reconcileStatus.WaitingOnObject(kind, name, progress.WaitingOnReady)
+ return r
+ }
+
+ r.resolved[kind] = *id
+ return r
+}
+
+// Required resolves a ref that must exist. If the ref is empty, an error is added.
+func Required[TP DependencyType[T], T any](r *Resolver, ref orcv1alpha1.KubernetesNameRef, kind string, getID func(TP) *string) *Resolver {
+ return Optional[TP, T](r, ptr.To(ref), kind, getID)
+}
+
+// Result returns the resolved dependencies and accumulated reconcile status.
+func (r *Resolver) Result() (ResolvedDependencies, progress.ReconcileStatus) {
+ return ResolvedDependencies{ids: r.resolved}, r.reconcileStatus
+}
+
+// ResolvedDependencies provides access to resolved OpenStack IDs.
+type ResolvedDependencies struct {
+ ids map[string]string
+}
+
+// Get returns the resolved ID for the given kind, or empty string if not resolved.
+func (r ResolvedDependencies) Get(kind string) string {
+ return r.ids[kind]
+}
+
+// GetPtr returns a pointer to the resolved ID, or nil if not resolved.
+func (r ResolvedDependencies) GetPtr(kind string) *string {
+ if id, ok := r.ids[kind]; ok {
+ return &id
+ }
+ return nil
+}
+
+// Has returns true if the kind was resolved.
+func (r ResolvedDependencies) Has(kind string) bool {
+ _, ok := r.ids[kind]
+ return ok
+}
+```
+
+### Alternative: Method Chaining with Generics
+
+Due to Go's limitation that methods cannot have type parameters, the fluent API requires top-level functions:
+
+```go
+// Usage with top-level functions (required by Go)
+deps, rs := dependency.Optional[*orcv1alpha1.Network](
+ dependency.Optional[*orcv1alpha1.Subnet](
+ dependency.NewResolver(ctx, k8sClient, namespace),
+ filter.SubnetRef, "Subnet", func(s *orcv1alpha1.Subnet) *string { return s.Status.ID },
+ ),
+ filter.NetworkRef, "Network", func(n *orcv1alpha1.Network) *string { return n.Status.ID },
+).Result()
+```
+
+This is less readable. A better alternative is a non-generic wrapper:
+
+```go
+// Resolver methods return *Resolver for chaining
+func (r *Resolver) OptionalNetwork(ref *KubernetesNameRef) *Resolver
+func (r *Resolver) OptionalSubnet(ref *KubernetesNameRef) *Resolver
+func (r *Resolver) OptionalProject(ref *KubernetesNameRef) *Resolver
+// ... one method per known type
+```
+
+This sacrifices genericity for readability but requires adding a method for each type.
+
+## Trade-offs
+
+### Pros
+- Significantly reduces boilerplate (45+ lines → 10 lines for 3 dependencies)
+- Consistent error handling across all controllers
+- Clear, readable code
+- Type-safe
+- No reflection magic
+
+### Cons
+- Go generics limitation: can't have generic methods, so either:
+ - Use top-level functions (less fluent)
+ - Add a method per type (more code in resolver)
+- Slightly more abstraction to understand
+
+## Relationship with Existing DeletionGuardDependency
+
+This resolver is complementary to `DeletionGuardDependency`:
+
+| Aspect | DeletionGuardDependency | Resolver |
+|--------|------------------------|----------|
+| **Purpose** | Full lifecycle management with finalizers | Simple one-off resolution |
+| **Finalizers** | Yes, prevents deletion | No |
+| **Watch/Index** | Yes, triggers reconciliation | No |
+| **Use case** | Spec refs (create/update) | Filter refs, quick lookups |
+
+For `CreateResource`, you might still want `DeletionGuardDependency.GetDependency()` if you need finalizer protection. The Resolver is best for:
+- `ListOSResourcesForImport` filter resolution
+- Quick lookups where finalizers aren't needed
+- Reducing boilerplate in any dependency fetching
+
+## Migration Path
+
+1. Add `Resolver` to `internal/util/dependency/resolver.go`
+2. Update one controller (e.g., floatingip) to use it
+3. Validate it works correctly
+4. Gradually migrate other controllers
+
+## Open Questions
+
+1. Should `Resolver` integrate with `DeletionGuardDependency` to add finalizers?
+2. Should there be pre-defined methods like `OptionalProject()` for common types?
+3. Should the ready check (`IsAvailable && Status.ID != nil`) be customizable per call?