From 1a5d7f3bad3de33c4c16806a9cf0c90bd712e57b Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Wed, 24 Dec 2025 17:41:55 +0200 Subject: [PATCH 1/6] globalize get dependency helper - create a dependency helper replacing repetative parts of code - update template to use it --- .../data/controller/actuator.go.template | 42 +++++------- internal/util/dependency/helpers.go | 66 +++++++++++++++++++ 2 files changed, 83 insertions(+), 25 deletions(-) create mode 100644 internal/util/dependency/helpers.go 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/internal/util/dependency/helpers.go b/internal/util/dependency/helpers.go new file mode 100644 index 000000000..ea62c2ff2 --- /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, name, err)) + } + + if !isReady(obj) { + return nil, progress.NewReconcileStatus().WaitingOnObject(kind, string(*name), progress.WaitingOnReady) + } + + return obj, nil +} From 31e1b97a85c77cb76d6709a84403156db8613d9d Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Thu, 25 Dec 2025 13:45:06 +0200 Subject: [PATCH 2/6] e2e with octavia allow testing octavia by enabling it in the e2e job --- .github/workflows/e2e.yaml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) 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 From 392302218c62e20fe50a5c5ca8cd5c1a5cae3344 Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Wed, 24 Dec 2025 14:52:17 +0200 Subject: [PATCH 3/6] Scaffolding for the LoadBalancer controller $ go run ./cmd/scaffold-controller -interactive=false \ -kind=LoadBalancer \ -gophercloud-client=NewLoadBalancerV2 \ -gophercloud-module=github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/loadbalancers \ -optional-create-dependency Subnet \ -optional-create-dependency Network \ -optional-create-dependency Port \ -optional-create-dependency Flavor \ -optional-create-dependency Project --- api/v1alpha1/loadbalancer_types.go | 124 +++++++ api/v1alpha1/zz_generated.deepcopy.go | 90 +++++ cmd/models-schema/zz_generated.openapi.go | 154 +++++++++ config/rbac/role.yaml | 2 + .../openstack_v1alpha1_loadbalancer.yaml | 14 + internal/controllers/loadbalancer/actuator.go | 315 ++++++++++++++++++ .../controllers/loadbalancer/actuator_test.go | 119 +++++++ .../controllers/loadbalancer/controller.go | 177 ++++++++++ internal/controllers/loadbalancer/status.go | 68 ++++ .../loadbalancer-create-full/00-assert.yaml | 53 +++ .../00-create-resource.yaml | 85 +++++ .../loadbalancer-create-full/00-secret.yaml | 6 + .../tests/loadbalancer-create-full/README.md | 11 + .../00-assert.yaml | 27 ++ .../00-create-resource.yaml | 14 + .../00-secret.yaml | 6 + .../01-assert.yaml | 11 + .../01-delete-secret.yaml | 7 + .../loadbalancer-create-minimal/README.md | 15 + .../loadbalancer-dependency/00-assert.yaml | 90 +++++ .../00-create-resources-missing-deps.yaml | 84 +++++ .../loadbalancer-dependency/00-secret.yaml | 6 + .../loadbalancer-dependency/01-assert.yaml | 90 +++++ .../01-create-dependencies.yaml | 71 ++++ .../loadbalancer-dependency/02-assert.yaml | 41 +++ .../02-delete-dependencies.yaml | 17 + .../loadbalancer-dependency/03-assert.yaml | 17 + .../03-delete-resources.yaml | 22 ++ .../tests/loadbalancer-dependency/README.md | 21 ++ .../loadbalancer-import-error/00-assert.yaml | 30 ++ .../00-create-resources.yaml | 28 ++ .../loadbalancer-import-error/00-secret.yaml | 6 + .../loadbalancer-import-error/01-assert.yaml | 15 + .../01-import-resource.yaml | 13 + .../tests/loadbalancer-import-error/README.md | 13 + .../tests/loadbalancer-import/00-assert.yaml | 15 + .../00-import-resource.yaml | 15 + .../tests/loadbalancer-import/00-secret.yaml | 6 + .../tests/loadbalancer-import/01-assert.yaml | 34 ++ .../01-create-trap-resource.yaml | 17 + .../tests/loadbalancer-import/02-assert.yaml | 33 ++ .../02-create-resource.yaml | 14 + .../tests/loadbalancer-import/README.md | 18 + .../tests/loadbalancer-update/00-assert.yaml | 26 ++ .../00-minimal-resource.yaml | 14 + .../loadbalancer-update/00-prerequisites.yaml | 6 + .../tests/loadbalancer-update/01-assert.yaml | 17 + .../01-updated-resource.yaml | 10 + .../tests/loadbalancer-update/02-assert.yaml | 26 ++ .../02-reverted-resource.yaml | 7 + .../tests/loadbalancer-update/README.md | 17 + internal/osclients/loadbalancer.go | 104 ++++++ website/docs/crd-reference.md | 9 + 53 files changed, 2250 insertions(+) create mode 100644 api/v1alpha1/loadbalancer_types.go create mode 100644 config/samples/openstack_v1alpha1_loadbalancer.yaml create mode 100644 internal/controllers/loadbalancer/actuator.go create mode 100644 internal/controllers/loadbalancer/actuator_test.go create mode 100644 internal/controllers/loadbalancer/controller.go create mode 100644 internal/controllers/loadbalancer/status.go create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-create-resource.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-secret.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-create-full/README.md create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-create-resource.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-secret.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/01-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/01-delete-secret.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/README.md create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-create-resources-missing-deps.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-secret.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-dependency/01-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-dependency/01-create-dependencies.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-dependency/02-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-dependency/02-delete-dependencies.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-dependency/03-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-dependency/03-delete-resources.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-dependency/README.md create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-create-resources.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-secret.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import-error/01-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import-error/01-import-resource.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import-error/README.md create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import/00-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import/00-import-resource.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import/00-secret.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import/01-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import/01-create-trap-resource.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import/02-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import/02-create-resource.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import/README.md create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-update/00-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-update/00-minimal-resource.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-update/00-prerequisites.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-update/01-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-update/01-updated-resource.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-update/02-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-update/02-reverted-resource.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-update/README.md create mode 100644 internal/osclients/loadbalancer.go diff --git a/api/v1alpha1/loadbalancer_types.go b/api/v1alpha1/loadbalancer_types.go new file mode 100644 index 000000000..c19796647 --- /dev/null +++ b/api/v1alpha1/loadbalancer_types.go @@ -0,0 +1,124 @@ +/* +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 + +// LoadBalancerResourceSpec contains the desired state of the resource. +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"` + + // subnetRef is a reference to the ORC Subnet which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="subnetRef is immutable" + SubnetRef *KubernetesNameRef `json:"subnetRef,omitempty"` + + // networkRef is a reference to the ORC Network which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="networkRef is immutable" + NetworkRef *KubernetesNameRef `json:"networkRef,omitempty"` + + // portRef is a reference to the ORC Port which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="portRef is immutable" + PortRef *KubernetesNameRef `json:"portRef,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"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the CreateOpts structure from + // github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/loadbalancers + // + // Until you have implemented mutability for the field, you must add a CEL validation + // preventing the field being modified: + // `// +kubebuilder:validation:XValidation:rule="self == oldSelf",message=" is immutable"` +} + +// 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"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the ListOpts structure from + // github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/loadbalancers +} + +// 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"` + + // subnetID is the ID of the Subnet to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + SubnetID string `json:"subnetID,omitempty"` + + // networkID is the ID of the Network to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + NetworkID string `json:"networkID,omitempty"` + + // portID is the ID of the Port to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + PortID string `json:"portID,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"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the LoadBalancer structure from + // github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/loadbalancers +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 093e63451..eedad9ef9 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1975,6 +1975,96 @@ 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 *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 + } +} + +// 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 *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.SubnetRef != nil { + in, out := &in.SubnetRef, &out.SubnetRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.NetworkRef != nil { + in, out := &in.NetworkRef, &out.NetworkRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.PortRef != nil { + in, out := &in.PortRef, &out.PortRef + *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 + } +} + +// 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 +} + +// 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 *ManagedOptions) DeepCopyInto(out *ManagedOptions) { *out = *in diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 8eab33c2d..20b224dcd 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -100,6 +100,9 @@ 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.LoadBalancerFilter": schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerFilter(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.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 +3762,157 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairStatus(ref comm } } +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: "", + }, + }, + }, + }, + }, + } +} + +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: "", + }, + }, + "subnetRef": { + SchemaProps: spec.SchemaProps{ + Description: "subnetRef is a reference to the ORC Subnet which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "networkRef": { + SchemaProps: spec.SchemaProps{ + Description: "networkRef is a reference to the ORC Network which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "portRef": { + SchemaProps: spec.SchemaProps{ + Description: "portRef is a reference to the ORC Port which this resource is associated with.", + 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: "", + }, + }, + }, + }, + }, + } +} + +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: "", + }, + }, + "subnetID": { + SchemaProps: spec.SchemaProps{ + Description: "subnetID is the ID of the Subnet to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + "networkID": { + SchemaProps: spec.SchemaProps{ + Description: "networkID is the ID of the Network to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + "portID": { + SchemaProps: spec.SchemaProps{ + Description: "portID 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: "", + }, + }, + }, + }, + }, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_ManagedOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 5a0a7443b..047bace25 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -23,6 +23,7 @@ rules: - groups - images - keypairs + - loadbalancers - networks - ports - projects @@ -53,6 +54,7 @@ rules: - groups/status - images/status - keypairs/status + - loadbalancers/status - networks/status - ports/status - projects/status diff --git a/config/samples/openstack_v1alpha1_loadbalancer.yaml b/config/samples/openstack_v1alpha1_loadbalancer.yaml new file mode 100644 index 000000000..bb18845fa --- /dev/null +++ b/config/samples/openstack_v1alpha1_loadbalancer.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-sample +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Sample LoadBalancer + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/loadbalancer/actuator.go b/internal/controllers/loadbalancer/actuator.go new file mode 100644 index 000000000..36f8e7147 --- /dev/null +++ b/internal/controllers/loadbalancer/actuator.go @@ -0,0 +1,315 @@ +/* +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" + + "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" + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" +) + +// 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 + } + + // TODO(scaffolding) If you need to filter resources on fields that the List() function + // of gophercloud does not support, it's possible to perform client-side filtering. + // Check osclients.ResourceFilter + + listOpts := loadbalancers.ListOpts{ + Name: getResourceName(orcObject), + Description: ptr.Deref(resourceSpec.Description, ""), + } + + return actuator.osClient.ListLoadBalancers(ctx, listOpts), true +} + +func (actuator loadbalancerActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { + // TODO(scaffolding) If you need to filter resources on fields that the List() function + // of gophercloud does not support, it's possible to perform client-side filtering. + // Check osclients.ResourceFilter + + listOpts := loadbalancers.ListOpts{ + Name: string(ptr.Deref(filter.Name, "")), + Description: string(ptr.Deref(filter.Description, "")), + // TODO(scaffolding): Add more import filters + } + + return actuator.osClient.ListLoadBalancers(ctx, listOpts), nil +} + +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 subnetID string + if resource.SubnetRef != 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 { + subnetID = ptr.Deref(subnet.Status.ID, "") + } + } + + var networkID string + if resource.NetworkRef != 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 { + networkID = ptr.Deref(network.Status.ID, "") + } + } + + var portID string + if resource.PortRef != 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 { + portID = 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 + } + createOpts := loadbalancers.CreateOpts{ + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + SubnetID: subnetID, + NetworkID: networkID, + PortID: portID, + FlavorID: flavorID, + ProjectID: projectID, + // TODO(scaffolding): Add more fields + } + + 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) + + // TODO(scaffolding): add handler for all fields supporting mutability + + 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 (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..07ec76cd7 --- /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.SubnetRef == nil { + return nil + } + return []string{string(*resource.SubnetRef)} + }, + 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.NetworkRef == nil { + return nil + } + return []string{string(*resource.NetworkRef)} + }, + 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.PortRef == nil { + return nil + } + return []string{string(*resource.PortRef)} + }, + 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..ba855df07 --- /dev/null +++ b/internal/controllers/loadbalancer/status.go @@ -0,0 +1,68 @@ +/* +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 + } else { + return metav1.ConditionUnknown, nil + } + } + return metav1.ConditionTrue, nil +} + +func (loadbalancerStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.LoadBalancerResourceStatus(). + WithSubnetID(osResource.SubnetID). + WithNetworkID(osResource.NetworkID). + WithPortID(osResource.PortID). + WithFlavorID(osResource.FlavorID). + WithProjectID(osResource.ProjectID). + WithName(osResource.Name) + + // TODO(scaffolding): add all of the fields supported in the LoadBalancerResourceStatus struct + // If a zero-value isn't expected in the response, place it behind a conditional + + if osResource.Description != "" { + resourceStatus.WithDescription(osResource.Description) + } + + 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..51a73c327 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-assert.yaml @@ -0,0 +1,53 @@ +--- +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 + # TODO(scaffolding): Add all fields the resource supports + 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: Flavor + name: loadbalancer-create-full + ref: flavor + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: loadbalancer-create-full + ref: project +assertAll: + - celExpr: "loadbalancer.status.id != ''" + - celExpr: "loadbalancer.status.resource.subnetID == subnet.status.id" + - celExpr: "loadbalancer.status.resource.networkID == network.status.id" + - celExpr: "loadbalancer.status.resource.portID == port.status.id" + - celExpr: "loadbalancer.status.resource.flavorID == flavor.status.id" + - celExpr: "loadbalancer.status.resource.projectID == project.status.id" + # TODO(scaffolding): Add more checks 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..1f3d9fd6e --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-create-resource.yaml @@ -0,0 +1,85 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: loadbalancer-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: loadbalancer-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: loadbalancer-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Flavor +metadata: + name: loadbalancer-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: loadbalancer-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + name: loadbalancer-create-full-override + description: LoadBalancer from "create full" test + subnetRef: loadbalancer-create-full + networkRef: loadbalancer-create-full + portRef: loadbalancer-create-full + flavorRef: loadbalancer-create-full + projectRef: loadbalancer-create-full + # TODO(scaffolding): Add all fields the resource supports 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..9230c2c73 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-assert.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-create-minimal +status: + resource: + name: loadbalancer-create-minimal + # TODO(scaffolding): Add all fields the resource supports + 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 +assertAll: + - celExpr: "loadbalancer.status.id != ''" + # TODO(scaffolding): Add more checks 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..750dbd630 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-create-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-create-minimal +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Only add the mandatory fields. It's possible the resource + # doesn't have mandatory fields, in that case, leave it empty. + resource: {} 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..3b21fdb81 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-assert.yaml @@ -0,0 +1,90 @@ +--- +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-flavor +status: + conditions: + - type: Available + message: Waiting for Flavor/loadbalancer-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Flavor/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..3410659ca --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-create-resources-missing-deps.yaml @@ -0,0 +1,84 @@ + +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-dependency-no-subnet +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + subnetRef: loadbalancer-dependency + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-dependency-no-network +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: loadbalancer-dependency + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-dependency-no-port +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + portRef: loadbalancer-dependency + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-dependency-no-flavor +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + flavorRef: loadbalancer-dependency + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-dependency-no-project +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + projectRef: loadbalancer-dependency + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-dependency-no-secret +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: loadbalancer-dependency + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} 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..5db640da1 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/01-assert.yaml @@ -0,0 +1,90 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-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: LoadBalancer +metadata: + name: loadbalancer-dependency-no-subnet +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: LoadBalancer +metadata: + name: loadbalancer-dependency-no-network +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: LoadBalancer +metadata: + name: loadbalancer-dependency-no-port +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: LoadBalancer +metadata: + name: loadbalancer-dependency-no-flavor +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: LoadBalancer +metadata: + name: loadbalancer-dependency-no-project +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/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..db46fcfdf --- /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: Subnet +metadata: + name: loadbalancer-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: loadbalancer-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: loadbalancer-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Flavor +metadata: + name: loadbalancer-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: loadbalancer-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} 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..9f9aa44fe --- /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: 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: Flavor + name: loadbalancer-dependency + ref: flavor + - 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: "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: "flavor.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/loadbalancer' in flavor.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..601628e32 --- /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 network loadbalancer-dependency --wait=false + namespaced: true + - command: kubectl delete port loadbalancer-dependency --wait=false + namespaced: true + - command: kubectl delete flavor 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..c87e7cb8a --- /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 network loadbalancer-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get port loadbalancer-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get flavor 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..7fe29cc28 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/03-delete-resources.yaml @@ -0,0 +1,22 @@ +--- +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-flavor +- 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..310f9510b --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-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: LoadBalancer +metadata: + name: loadbalancer-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/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..37ab64de5 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-create-resources.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-import-error-external-1 +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: LoadBalancer from "import error" test + # TODO(scaffolding): add any required field +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-import-error-external-2 +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: LoadBalancer from "import error" test + # TODO(scaffolding): add any required field 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..b5f3c6de4 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/00-import-resource.yaml @@ -0,0 +1,15 @@ +--- +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 + # TODO(scaffolding): Add all fields supported by the filter 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..aa4e17c01 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/01-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-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: loadbalancer-import-external-not-this-one + description: LoadBalancer loadbalancer-import-external from "loadbalancer-import" test + # TODO(scaffolding): Add fields necessary to match filter +--- +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..fea259b60 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/01-create-trap-resource.yaml @@ -0,0 +1,17 @@ +--- +# 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: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: LoadBalancer loadbalancer-import-external from "loadbalancer-import" test + # TODO(scaffolding): Add fields necessary to match filter 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..7678b3e49 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/02-assert.yaml @@ -0,0 +1,33 @@ +--- +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 +assertAll: + - celExpr: "loadbalancer1.status.id != loadbalancer2.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-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: loadbalancer-import-external + description: LoadBalancer loadbalancer-import-external from "loadbalancer-import" test + # TODO(scaffolding): Add all fields the resource supports 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..5c55a3b64 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/02-create-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-import-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: LoadBalancer loadbalancer-import-external from "loadbalancer-import" test + # TODO(scaffolding): Add fields necessary to match filter 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..bc7dffe84 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/00-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: "!has(loadbalancer.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-update +status: + resource: + name: loadbalancer-update + # TODO(scaffolding): Add matches for more fields + 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..b0a66b498 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/00-minimal-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-update +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created or updated + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Only add the mandatory fields. It's possible the resource + # doesn't have mandatory fields, in that case, leave it empty. + resource: {} 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..045711ee7 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-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/loadbalancer/tests/loadbalancer-update/01-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-update/01-assert.yaml new file mode 100644 index 000000000..9cd82b42f --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/01-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-update +status: + resource: + name: loadbalancer-update-updated + description: loadbalancer-update-updated + # TODO(scaffolding): match all fields that were modified + 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..4b0cebf26 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/01-updated-resource.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-update +spec: + resource: + name: loadbalancer-update-updated + description: loadbalancer-update-updated + # TODO(scaffolding): update all mutable fields 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..82a2efd49 --- /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: "!has(loadbalancer.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-update +status: + resource: + name: loadbalancer-update + # TODO(scaffolding): validate that updated fields were all reverted to their original value + 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/osclients/loadbalancer.go b/internal/osclients/loadbalancer.go new file mode 100644 index 000000000..51101a8ba --- /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).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/website/docs/crd-reference.md b/website/docs/crd-reference.md index 23b3b2403..2af8f88c9 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -1616,6 +1616,7 @@ _Appears in:_ - [FloatingIPResourceSpec](#floatingipresourcespec) - [GroupFilter](#groupfilter) - [GroupResourceSpec](#groupresourcespec) +- [LoadBalancerResourceSpec](#loadbalancerresourcespec) - [NetworkFilter](#networkfilter) - [NetworkResourceSpec](#networkresourcespec) - [PortFilter](#portfilter) @@ -1637,6 +1638,12 @@ _Appears in:_ + + + + + + #### MAC _Underlying type:_ _string_ @@ -2009,6 +2016,8 @@ _Appears in:_ - [ImageResourceSpec](#imageresourcespec) - [KeyPairFilter](#keypairfilter) - [KeyPairResourceSpec](#keypairresourcespec) +- [LoadBalancerFilter](#loadbalancerfilter) +- [LoadBalancerResourceSpec](#loadbalancerresourcespec) - [NetworkFilter](#networkfilter) - [NetworkResourceSpec](#networkresourcespec) - [PortFilter](#portfilter) From bf32498156c5c28ef3e2a0727214d23595d304b6 Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Thu, 25 Dec 2025 11:28:54 +0200 Subject: [PATCH 4/6] OSASINFRA-4029: loadbalancer: add LoadBalancer controller Add support for OpenStack Octavia Load Balancer resources. This includes: - LoadBalancer CRD with support for VIP subnet/network/port references - Controller with create, update, delete, and import capabilities - Status reporting with provisioning and operating status - Dependency resolution for Subnet, Network, Port, and Project references - Kuttl tests for create, update, import, and dependency scenarios Closes #619 --- PROJECT | 8 + api/v1alpha1/loadbalancer_types.go | 175 +++++- api/v1alpha1/zz_generated.deepcopy.go | 224 +++++++- .../zz_generated.loadbalancer-resource.go | 177 ++++++ cmd/manager/main.go | 2 + cmd/models-schema/zz_generated.openapi.go | 497 ++++++++++++++++- cmd/resource-generator/main.go | 3 + .../openstack.k-orc.cloud_loadbalancers.yaml | 524 ++++++++++++++++++ config/crd/kustomization.yaml | 1 + config/samples/kustomization.yaml | 1 + .../openstack_v1alpha1_loadbalancer.yaml | 14 +- internal/controllers/loadbalancer/actuator.go | 146 +++-- .../controllers/loadbalancer/controller.go | 12 +- internal/controllers/loadbalancer/status.go | 37 +- .../loadbalancer-create-full/00-assert.yaml | 19 +- .../00-create-resource.yaml | 53 +- .../00-assert.yaml | 12 +- .../00-create-resource.yaml | 31 +- .../loadbalancer-dependency/00-assert.yaml | 15 - .../00-create-resources-missing-deps.yaml | 35 +- .../loadbalancer-dependency/01-assert.yaml | 25 - .../01-create-dependencies.yaml | 38 +- .../loadbalancer-dependency/02-assert.yaml | 12 +- .../02-delete-dependencies.yaml | 4 +- .../loadbalancer-dependency/03-assert.yaml | 4 +- .../03-delete-resources.yaml | 3 - .../loadbalancer-import-error/00-assert.yaml | 4 - .../00-create-resources.yaml | 31 +- .../00-import-resource.yaml | 26 +- .../tests/loadbalancer-import/01-assert.yaml | 3 - .../01-create-trap-resource.yaml | 3 +- .../tests/loadbalancer-import/02-assert.yaml | 8 +- .../02-create-resource.yaml | 3 +- .../tests/loadbalancer-update/00-assert.yaml | 3 +- .../00-minimal-resource.yaml | 6 +- .../loadbalancer-update/00-prerequisites.yaml | 25 + .../tests/loadbalancer-update/01-assert.yaml | 4 +- .../01-updated-resource.yaml | 5 +- .../tests/loadbalancer-update/02-assert.yaml | 4 +- .../loadbalancer/zz_generated.adapter.go | 88 +++ .../loadbalancer/zz_generated.controller.go | 45 ++ internal/osclients/loadbalancer.go | 2 +- internal/osclients/mock/doc.go | 3 + internal/osclients/mock/loadbalancer.go | 131 +++++ internal/scope/mock.go | 51 +- internal/scope/provider.go | 4 + internal/scope/scope.go | 1 + internal/util/dependency/helpers.go | 2 +- kuttl-test.yaml | 1 + .../api/v1alpha1/loadbalancer.go | 281 ++++++++++ .../api/v1alpha1/loadbalancerfilter.go | 159 ++++++ .../api/v1alpha1/loadbalancerimport.go | 48 ++ .../api/v1alpha1/loadbalancerresourcespec.go | 144 +++++ .../v1alpha1/loadbalancerresourcestatus.go | 158 ++++++ .../api/v1alpha1/loadbalancerspec.go | 79 +++ .../api/v1alpha1/loadbalancerstatus.go | 66 +++ .../applyconfiguration/internal/internal.go | 210 +++++++ pkg/clients/applyconfiguration/utils.go | 14 + .../typed/api/v1alpha1/api_client.go | 5 + .../api/v1alpha1/fake/fake_api_client.go | 4 + .../api/v1alpha1/fake/fake_loadbalancer.go | 53 ++ .../typed/api/v1alpha1/generated_expansion.go | 2 + .../typed/api/v1alpha1/loadbalancer.go | 74 +++ .../api/v1alpha1/interface.go | 7 + .../api/v1alpha1/loadbalancer.go | 102 ++++ .../informers/externalversions/generic.go | 2 + .../api/v1alpha1/expansion_generated.go | 8 + .../listers/api/v1alpha1/loadbalancer.go | 70 +++ website/docs/crd-reference.md | 178 ++++++ website/docs/design/dependency-resolver.md | 391 +++++++++++++ 70 files changed, 4275 insertions(+), 305 deletions(-) create mode 100644 api/v1alpha1/zz_generated.loadbalancer-resource.go create mode 100644 config/crd/bases/openstack.k-orc.cloud_loadbalancers.yaml create mode 100644 internal/controllers/loadbalancer/zz_generated.adapter.go create mode 100644 internal/controllers/loadbalancer/zz_generated.controller.go create mode 100644 internal/osclients/mock/loadbalancer.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/loadbalancer.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerfilter.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerimport.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerresourcespec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerresourcestatus.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerspec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerstatus.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_loadbalancer.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/loadbalancer.go create mode 100644 pkg/clients/informers/externalversions/api/v1alpha1/loadbalancer.go create mode 100644 pkg/clients/listers/api/v1alpha1/loadbalancer.go create mode 100644 website/docs/design/dependency-resolver.md diff --git a/PROJECT b/PROJECT index 8d6e2c12d..d3f41ffce 100644 --- a/PROJECT +++ b/PROJECT @@ -56,6 +56,14 @@ 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: LoadBalancer + path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 + version: v1alpha1 - api: crdVersion: v1 namespaced: true diff --git a/api/v1alpha1/loadbalancer_types.go b/api/v1alpha1/loadbalancer_types.go index c19796647..761435115 100644 --- a/api/v1alpha1/loadbalancer_types.go +++ b/api/v1alpha1/loadbalancer_types.go @@ -16,7 +16,12 @@ 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. @@ -29,20 +34,22 @@ type LoadBalancerResourceSpec struct { // +optional Description *string `json:"description,omitempty"` - // subnetRef is a reference to the ORC Subnet which this resource is associated with. + // vipSubnetRef is the subnet on which to allocate the load balancer's address. // +optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="subnetRef is immutable" - SubnetRef *KubernetesNameRef `json:"subnetRef,omitempty"` + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="vipSubnetRef is immutable" + VipSubnetRef *KubernetesNameRef `json:"vipSubnetRef,omitempty"` - // networkRef is a reference to the ORC Network which this resource is associated with. + // vipNetworkRef is the network on which to allocate the load balancer's address. // +optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="networkRef is immutable" - NetworkRef *KubernetesNameRef `json:"networkRef,omitempty"` + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="vipNetworkRef is immutable" + VipNetworkRef *KubernetesNameRef `json:"vipNetworkRef,omitempty"` - // portRef is a reference to the ORC Port which this resource is associated with. + // 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="portRef is immutable" - PortRef *KubernetesNameRef `json:"portRef,omitempty"` + // +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 @@ -54,13 +61,33 @@ type LoadBalancerResourceSpec struct { // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="projectRef is immutable" ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the CreateOpts structure from - // github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/loadbalancers - // - // Until you have implemented mutability for the field, you must add a CEL validation - // preventing the field being modified: - // `// +kubebuilder:validation:XValidation:rule="self == oldSelf",message=" is immutable"` + // 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 @@ -76,9 +103,66 @@ type LoadBalancerFilter struct { // +optional Description *string `json:"description,omitempty"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the ListOpts structure from - // github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/loadbalancers + // 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. @@ -93,20 +177,20 @@ type LoadBalancerResourceStatus struct { // +optional Description string `json:"description,omitempty"` - // subnetID is the ID of the Subnet to which the resource is associated. + // vipSubnetID is the ID of the Subnet to which the resource is associated. // +kubebuilder:validation:MaxLength=1024 // +optional - SubnetID string `json:"subnetID,omitempty"` + VipSubnetID string `json:"vipSubnetID,omitempty"` - // networkID is the ID of the Network to which the resource is associated. + // vipNetworkID is the ID of the Network to which the resource is associated. // +kubebuilder:validation:MaxLength=1024 // +optional - NetworkID string `json:"networkID,omitempty"` + VipNetworkID string `json:"vipNetworkID,omitempty"` - // portID is the ID of the Port to which the resource is associated. + // vipPortID is the ID of the Port to which the resource is associated. // +kubebuilder:validation:MaxLength=1024 // +optional - PortID string `json:"portID,omitempty"` + VipPortID string `json:"vipPortID,omitempty"` // flavorID is the ID of the Flavor to which the resource is associated. // +kubebuilder:validation:MaxLength=1024 @@ -118,7 +202,42 @@ type LoadBalancerResourceStatus struct { // +optional ProjectID string `json:"projectID,omitempty"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the LoadBalancer structure from - // github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/loadbalancers + // 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 eedad9ef9..8d480ed8a 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1975,6 +1975,33 @@ 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 *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 @@ -1988,6 +2015,46 @@ func (in *LoadBalancerFilter) DeepCopyInto(out *LoadBalancerFilter) { *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. @@ -2000,6 +2067,63 @@ func (in *LoadBalancerFilter) DeepCopy() *LoadBalancerFilter { 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 @@ -2013,18 +2137,18 @@ func (in *LoadBalancerResourceSpec) DeepCopyInto(out *LoadBalancerResourceSpec) *out = new(string) **out = **in } - if in.SubnetRef != nil { - in, out := &in.SubnetRef, &out.SubnetRef + if in.VipSubnetRef != nil { + in, out := &in.VipSubnetRef, &out.VipSubnetRef *out = new(KubernetesNameRef) **out = **in } - if in.NetworkRef != nil { - in, out := &in.NetworkRef, &out.NetworkRef + if in.VipNetworkRef != nil { + in, out := &in.VipNetworkRef, &out.VipNetworkRef *out = new(KubernetesNameRef) **out = **in } - if in.PortRef != nil { - in, out := &in.PortRef, &out.PortRef + if in.VipPortRef != nil { + in, out := &in.VipPortRef, &out.VipPortRef *out = new(KubernetesNameRef) **out = **in } @@ -2038,6 +2162,21 @@ func (in *LoadBalancerResourceSpec) DeepCopyInto(out *LoadBalancerResourceSpec) *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. @@ -2053,6 +2192,16 @@ func (in *LoadBalancerResourceSpec) DeepCopy() *LoadBalancerResourceSpec { // 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. @@ -2065,6 +2214,69 @@ func (in *LoadBalancerResourceStatus) DeepCopy() *LoadBalancerResourceStatus { 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.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..0b761fe98 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -34,6 +34,7 @@ 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/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 +127,7 @@ func main() { keypair.New(scopeFactory), group.New(scopeFactory), role.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 20b224dcd..49cf59aa5 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -100,9 +100,14 @@ 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.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), @@ -3762,6 +3767,56 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairStatus(ref comm } } +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{ @@ -3783,9 +3838,217 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerFilter(ref 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"}, } } @@ -3810,23 +4073,23 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerResourceSp Format: "", }, }, - "subnetRef": { + "vipSubnetRef": { SchemaProps: spec.SchemaProps{ - Description: "subnetRef is a reference to the ORC Subnet which this resource is associated with.", + Description: "vipSubnetRef is the subnet on which to allocate the load balancer's address.", Type: []string{"string"}, Format: "", }, }, - "networkRef": { + "vipNetworkRef": { SchemaProps: spec.SchemaProps{ - Description: "networkRef is a reference to the ORC Network which this resource is associated with.", + Description: "vipNetworkRef is the network on which to allocate the load balancer's address.", Type: []string{"string"}, Format: "", }, }, - "portRef": { + "vipPortRef": { SchemaProps: spec.SchemaProps{ - Description: "portRef is a reference to the ORC Port which this resource is associated with.", + 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: "", }, @@ -3845,6 +4108,54 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerResourceSp 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: "", + }, + }, + }, + }, + }, }, }, }, @@ -3872,23 +4183,23 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerResourceSt Format: "", }, }, - "subnetID": { + "vipSubnetID": { SchemaProps: spec.SchemaProps{ - Description: "subnetID is the ID of the Subnet to which the resource is associated.", + Description: "vipSubnetID is the ID of the Subnet to which the resource is associated.", Type: []string{"string"}, Format: "", }, }, - "networkID": { + "vipNetworkID": { SchemaProps: spec.SchemaProps{ - Description: "networkID is the ID of the Network to which the resource is associated.", + Description: "vipNetworkID is the ID of the Network to which the resource is associated.", Type: []string{"string"}, Format: "", }, }, - "portID": { + "vipPortID": { SchemaProps: spec.SchemaProps{ - Description: "portID is the ID of the Port to which the resource is associated.", + Description: "vipPortID is the ID of the Port to which the resource is associated.", Type: []string{"string"}, Format: "", }, @@ -3907,9 +4218,171 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerResourceSt 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"}, } } diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index 6848b155f..d5f2a5b85 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -162,6 +162,9 @@ var resources []templateFields = []templateFields{ { Name: "Group", }, + { + Name: "LoadBalancer", + }, } // These resources won't be generated 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..8012052f5 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -9,6 +9,7 @@ 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_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/samples/kustomization.yaml b/config/samples/kustomization.yaml index dac467c69..075c016fa 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -7,6 +7,7 @@ resources: - openstack_v1alpha1_group.yaml - openstack_v1alpha1_image.yaml - openstack_v1alpha1_keypair.yaml +- openstack_v1alpha1_loadbalancer.yaml - openstack_v1alpha1_network.yaml - openstack_v1alpha1_port.yaml - openstack_v1alpha1_project.yaml diff --git a/config/samples/openstack_v1alpha1_loadbalancer.yaml b/config/samples/openstack_v1alpha1_loadbalancer.yaml index bb18845fa..9510e2d0f 100644 --- a/config/samples/openstack_v1alpha1_loadbalancer.yaml +++ b/config/samples/openstack_v1alpha1_loadbalancer.yaml @@ -5,10 +5,20 @@ metadata: name: loadbalancer-sample spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: + name: my-loadbalancer description: Sample LoadBalancer - # TODO(scaffolding): Add all fields the resource supports + 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/loadbalancer/actuator.go b/internal/controllers/loadbalancer/actuator.go index 36f8e7147..62714ec99 100644 --- a/internal/controllers/loadbalancer/actuator.go +++ b/internal/controllers/loadbalancer/actuator.go @@ -19,6 +19,8 @@ package loadbalancer import ( "context" "iter" + "slices" + "time" "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/loadbalancers" corev1 "k8s.io/api/core/v1" @@ -31,9 +33,15 @@ 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" + "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 @@ -70,30 +78,71 @@ func (actuator loadbalancerActuator) ListOSResourcesForAdoption(ctx context.Cont return nil, false } - // TODO(scaffolding) If you need to filter resources on fields that the List() function - // of gophercloud does not support, it's possible to perform client-side filtering. - // Check osclients.ResourceFilter - listOpts := loadbalancers.ListOpts{ - Name: getResourceName(orcObject), - Description: ptr.Deref(resourceSpec.Description, ""), + 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) { - // TODO(scaffolding) If you need to filter resources on fields that the List() function - // of gophercloud does not support, it's possible to perform client-side filtering. - // Check osclients.ResourceFilter + 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: string(ptr.Deref(filter.Description, "")), - // TODO(scaffolding): Add more import filters + 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), nil + return actuator.osClient.ListLoadBalancers(ctx, listOpts), reconcileStatus } func (actuator loadbalancerActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) { @@ -106,8 +155,8 @@ func (actuator loadbalancerActuator) CreateResource(ctx context.Context, obj orc } var reconcileStatus progress.ReconcileStatus - var subnetID string - if resource.SubnetRef != nil { + 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 @@ -115,12 +164,12 @@ func (actuator loadbalancerActuator) CreateResource(ctx context.Context, obj orc ) reconcileStatus = reconcileStatus.WithReconcileStatus(subnetDepRS) if subnet != nil { - subnetID = ptr.Deref(subnet.Status.ID, "") + vipSubnetID = ptr.Deref(subnet.Status.ID, "") } } - var networkID string - if resource.NetworkRef != nil { + 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 @@ -128,12 +177,12 @@ func (actuator loadbalancerActuator) CreateResource(ctx context.Context, obj orc ) reconcileStatus = reconcileStatus.WithReconcileStatus(networkDepRS) if network != nil { - networkID = ptr.Deref(network.Status.ID, "") + vipNetworkID = ptr.Deref(network.Status.ID, "") } } - var portID string - if resource.PortRef != nil { + 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 @@ -141,7 +190,7 @@ func (actuator loadbalancerActuator) CreateResource(ctx context.Context, obj orc ) reconcileStatus = reconcileStatus.WithReconcileStatus(portDepRS) if port != nil { - portID = ptr.Deref(port.Status.ID, "") + vipPortID = ptr.Deref(port.Status.ID, "") } } @@ -173,15 +222,27 @@ func (actuator loadbalancerActuator) CreateResource(ctx context.Context, obj orc 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, ""), - SubnetID: subnetID, - NetworkID: networkID, - PortID: portID, - FlavorID: flavorID, - ProjectID: projectID, - // TODO(scaffolding): Add more fields + 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) @@ -213,8 +274,8 @@ func (actuator loadbalancerActuator) updateResource(ctx context.Context, obj orc handleNameUpdate(&updateOpts, obj, osResource) handleDescriptionUpdate(&updateOpts, resource, osResource) - - // TODO(scaffolding): add handler for all fields supporting mutability + handleAdminStateUpdate(&updateOpts, resource, osResource) + handleTagsUpdate(&updateOpts, resource, osResource) needsUpdate, err := needsUpdate(updateOpts) if err != nil { @@ -268,6 +329,27 @@ func handleDescriptionUpdate(updateOpts *loadbalancers.UpdateOpts, resource *res } } +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, diff --git a/internal/controllers/loadbalancer/controller.go b/internal/controllers/loadbalancer/controller.go index 07ec76cd7..93d031c84 100644 --- a/internal/controllers/loadbalancer/controller.go +++ b/internal/controllers/loadbalancer/controller.go @@ -55,10 +55,10 @@ var subnetDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.LoadBa "spec.resource.subnetRef", func(loadbalancer *orcv1alpha1.LoadBalancer) []string { resource := loadbalancer.Spec.Resource - if resource == nil || resource.SubnetRef == nil { + if resource == nil || resource.VipSubnetRef == nil { return nil } - return []string{string(*resource.SubnetRef)} + return []string{string(*resource.VipSubnetRef)} }, finalizer, externalObjectFieldOwner, ) @@ -67,10 +67,10 @@ var networkDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.LoadB "spec.resource.networkRef", func(loadbalancer *orcv1alpha1.LoadBalancer) []string { resource := loadbalancer.Spec.Resource - if resource == nil || resource.NetworkRef == nil { + if resource == nil || resource.VipNetworkRef == nil { return nil } - return []string{string(*resource.NetworkRef)} + return []string{string(*resource.VipNetworkRef)} }, finalizer, externalObjectFieldOwner, ) @@ -79,10 +79,10 @@ var portDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.LoadBala "spec.resource.portRef", func(loadbalancer *orcv1alpha1.LoadBalancer) []string { resource := loadbalancer.Spec.Resource - if resource == nil || resource.PortRef == nil { + if resource == nil || resource.VipPortRef == nil { return nil } - return []string{string(*resource.PortRef)} + return []string{string(*resource.VipPortRef)} }, finalizer, externalObjectFieldOwner, ) diff --git a/internal/controllers/loadbalancer/status.go b/internal/controllers/loadbalancer/status.go index ba855df07..60b414a94 100644 --- a/internal/controllers/loadbalancer/status.go +++ b/internal/controllers/loadbalancer/status.go @@ -41,28 +41,37 @@ func (loadbalancerStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.L if osResource == nil { if orcObject.Status.ID == nil { return metav1.ConditionFalse, nil - } else { - return metav1.ConditionUnknown, 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) } - return metav1.ConditionTrue, nil } func (loadbalancerStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { resourceStatus := orcapplyconfigv1alpha1.LoadBalancerResourceStatus(). - WithSubnetID(osResource.SubnetID). - WithNetworkID(osResource.NetworkID). - WithPortID(osResource.PortID). + WithName(osResource.Name). + WithDescription(osResource.Description). + WithVipSubnetID(osResource.VipSubnetID). + WithVipNetworkID(osResource.VipNetworkID). + WithVipPortID(osResource.VipPortID). + WithVipAddress(osResource.VipAddress). WithFlavorID(osResource.FlavorID). WithProjectID(osResource.ProjectID). - WithName(osResource.Name) - - // TODO(scaffolding): add all of the fields supported in the LoadBalancerResourceStatus struct - // If a zero-value isn't expected in the response, place it behind a conditional - - if osResource.Description != "" { - resourceStatus.WithDescription(osResource.Description) - } + 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 index 51a73c327..8f1586922 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-assert.yaml @@ -7,7 +7,10 @@ status: resource: name: loadbalancer-create-full-override description: LoadBalancer from "create full" test - # TODO(scaffolding): Add all fields the resource supports + adminStateUp: true + tags: + - tag1 + - tag2 conditions: - type: Available status: "True" @@ -35,19 +38,15 @@ resourceRefs: kind: Port name: loadbalancer-create-full ref: port - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Flavor - name: loadbalancer-create-full - ref: flavor - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project name: loadbalancer-create-full ref: project assertAll: - celExpr: "loadbalancer.status.id != ''" - - celExpr: "loadbalancer.status.resource.subnetID == subnet.status.id" - - celExpr: "loadbalancer.status.resource.networkID == network.status.id" - - celExpr: "loadbalancer.status.resource.portID == port.status.id" - - celExpr: "loadbalancer.status.resource.flavorID == flavor.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" - # TODO(scaffolding): Add more checks + - 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 index 1f3d9fd6e..66605a006 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-create-resource.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-full/00-create-resource.yaml @@ -1,15 +1,13 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Subnet +kind: Project metadata: name: loadbalancer-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: {} --- apiVersion: openstack.k-orc.cloud/v1alpha1 @@ -18,51 +16,39 @@ metadata: name: loadbalancer-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Port -metadata: - name: loadbalancer-create-full -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: {} --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Flavor +kind: Subnet metadata: name: loadbalancer-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + networkRef: loadbalancer-create-full + ipVersion: 4 + cidr: 10.0.0.0/24 --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Project +kind: Port metadata: name: loadbalancer-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + networkRef: loadbalancer-create-full + addresses: + - subnetRef: loadbalancer-create-full + ip: 10.0.0.10 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer @@ -70,16 +56,17 @@ metadata: name: loadbalancer-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: name: loadbalancer-create-full-override description: LoadBalancer from "create full" test - subnetRef: loadbalancer-create-full - networkRef: loadbalancer-create-full - portRef: loadbalancer-create-full - flavorRef: loadbalancer-create-full + vipSubnetRef: loadbalancer-create-full + vipNetworkRef: loadbalancer-create-full + vipPortRef: loadbalancer-create-full projectRef: loadbalancer-create-full - # TODO(scaffolding): Add all fields the resource supports + adminStateUp: true + tags: + - tag1 + - tag2 diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-assert.yaml index 9230c2c73..9e9ef45f8 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-assert.yaml @@ -4,9 +4,6 @@ kind: LoadBalancer metadata: name: loadbalancer-create-minimal status: - resource: - name: loadbalancer-create-minimal - # TODO(scaffolding): Add all fields the resource supports conditions: - type: Available status: "True" @@ -22,6 +19,13 @@ resourceRefs: 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 != ''" - # TODO(scaffolding): Add more checks + - 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 index 750dbd630..d527aa280 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-create-resource.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-create-resource.yaml @@ -1,14 +1,37 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LoadBalancer +kind: Network metadata: name: loadbalancer-create-minimal spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Only add the mandatory fields. It's possible the resource - # doesn't have mandatory fields, in that case, leave it empty. 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-dependency/00-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-assert.yaml index 3b21fdb81..50720584f 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-assert.yaml @@ -61,21 +61,6 @@ status: --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer -metadata: - name: loadbalancer-dependency-no-flavor -status: - conditions: - - type: Available - message: Waiting for Flavor/loadbalancer-dependency to be created - status: "False" - reason: Progressing - - type: Progressing - message: Waiting for Flavor/loadbalancer-dependency to be created - status: "True" - reason: Progressing ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LoadBalancer metadata: name: loadbalancer-dependency-no-project status: 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 index 3410659ca..f2ee54961 100644 --- 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 @@ -1,4 +1,3 @@ - --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer @@ -6,13 +5,11 @@ metadata: name: loadbalancer-dependency-no-subnet spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: - subnetRef: loadbalancer-dependency - # TODO(scaffolding): Add the necessary fields to create the resource + vipSubnetRef: loadbalancer-dependency --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer @@ -20,13 +17,11 @@ metadata: name: loadbalancer-dependency-no-network spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: - networkRef: loadbalancer-dependency - # TODO(scaffolding): Add the necessary fields to create the resource + vipNetworkRef: loadbalancer-dependency --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer @@ -34,27 +29,11 @@ metadata: name: loadbalancer-dependency-no-port spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: - portRef: loadbalancer-dependency - # TODO(scaffolding): Add the necessary fields to create the resource ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LoadBalancer -metadata: - name: loadbalancer-dependency-no-flavor -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - flavorRef: loadbalancer-dependency - # TODO(scaffolding): Add the necessary fields to create the resource + vipPortRef: loadbalancer-dependency --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer @@ -62,13 +41,12 @@ metadata: name: loadbalancer-dependency-no-project spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: + vipSubnetRef: loadbalancer-dependency-subnet projectRef: loadbalancer-dependency - # TODO(scaffolding): Add the necessary fields to create the resource --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer @@ -76,9 +54,8 @@ metadata: name: loadbalancer-dependency-no-secret spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: loadbalancer-dependency managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + vipSubnetRef: loadbalancer-dependency-subnet diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/01-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/01-assert.yaml index 5db640da1..ec74c08e9 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/01-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/01-assert.yaml @@ -6,11 +6,9 @@ metadata: 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 --- @@ -21,11 +19,9 @@ metadata: 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 --- @@ -36,11 +32,9 @@ metadata: 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 --- @@ -51,26 +45,9 @@ metadata: 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: LoadBalancer -metadata: - name: loadbalancer-dependency-no-flavor -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 --- @@ -81,10 +58,8 @@ metadata: 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/loadbalancer/tests/loadbalancer-dependency/01-create-dependencies.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/01-create-dependencies.yaml index db46fcfdf..4bd2487e4 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/01-create-dependencies.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/01-create-dependencies.yaml @@ -6,16 +6,14 @@ commands: namespaced: true --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Subnet +kind: Project metadata: name: loadbalancer-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: {} --- apiVersion: openstack.k-orc.cloud/v1alpha1 @@ -24,48 +22,50 @@ metadata: name: loadbalancer-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: {} --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Port +kind: Subnet metadata: name: loadbalancer-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + networkRef: loadbalancer-dependency + ipVersion: 4 + cidr: 10.0.0.0/24 --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Flavor +kind: Subnet metadata: - name: loadbalancer-dependency + name: loadbalancer-dependency-subnet spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + networkRef: loadbalancer-dependency + ipVersion: 4 + cidr: 10.0.1.0/24 --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Project +kind: Port metadata: name: loadbalancer-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + 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 index 9f9aa44fe..850000719 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/02-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/02-assert.yaml @@ -6,6 +6,10 @@ resourceRefs: 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 @@ -14,10 +18,6 @@ resourceRefs: kind: Port name: loadbalancer-dependency ref: port - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Flavor - name: loadbalancer-dependency - ref: flavor - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project name: loadbalancer-dependency @@ -29,12 +29,12 @@ resourceRefs: 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: "flavor.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/loadbalancer' in flavor.metadata.finalizers" - celExpr: "project.metadata.deletionTimestamp != 0" - celExpr: "'openstack.k-orc.cloud/loadbalancer' in project.metadata.finalizers" - celExpr: "secret.metadata.deletionTimestamp != 0" diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/02-delete-dependencies.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/02-delete-dependencies.yaml index 601628e32..ab613419e 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/02-delete-dependencies.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/02-delete-dependencies.yaml @@ -5,12 +5,12 @@ 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 flavor loadbalancer-dependency --wait=false - namespaced: true - command: kubectl delete project loadbalancer-dependency --wait=false namespaced: true - command: kubectl delete secret loadbalancer-dependency --wait=false diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/03-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/03-assert.yaml index c87e7cb8a..26f0781b8 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/03-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/03-assert.yaml @@ -5,12 +5,12 @@ 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 flavor loadbalancer-dependency --namespace $NAMESPACE" - skipLogOutput: true - script: "! kubectl get project loadbalancer-dependency --namespace $NAMESPACE" skipLogOutput: true - script: "! kubectl get secret loadbalancer-dependency --namespace $NAMESPACE" diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/03-delete-resources.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/03-delete-resources.yaml index 7fe29cc28..b4eb49b0c 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/03-delete-resources.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/03-delete-resources.yaml @@ -14,9 +14,6 @@ delete: - 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-flavor - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer name: loadbalancer-dependency-no-project diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-assert.yaml index 310f9510b..3f41cc651 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-assert.yaml @@ -6,11 +6,9 @@ metadata: 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 --- @@ -21,10 +19,8 @@ metadata: 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/loadbalancer/tests/loadbalancer-import-error/00-create-resources.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-create-resources.yaml index 37ab64de5..94cf2dd6b 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-create-resources.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-error/00-create-resources.yaml @@ -1,17 +1,41 @@ --- 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: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: + vipSubnetRef: loadbalancer-import-error description: LoadBalancer from "import error" test - # TODO(scaffolding): add any required field --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer @@ -19,10 +43,9 @@ metadata: name: loadbalancer-import-error-external-2 spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: + vipSubnetRef: loadbalancer-import-error description: LoadBalancer from "import error" test - # TODO(scaffolding): add any required field diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import/00-import-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import/00-import-resource.yaml index b5f3c6de4..93c311aeb 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-import/00-import-resource.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/00-import-resource.yaml @@ -1,5 +1,30 @@ --- 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 @@ -12,4 +37,3 @@ spec: filter: name: loadbalancer-import-external description: LoadBalancer loadbalancer-import-external from "loadbalancer-import" test - # TODO(scaffolding): Add all fields supported by the filter diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import/01-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import/01-assert.yaml index aa4e17c01..d7d034bd0 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-import/01-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/01-assert.yaml @@ -6,17 +6,14 @@ metadata: 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: loadbalancer-import-external-not-this-one description: LoadBalancer loadbalancer-import-external from "loadbalancer-import" test - # TODO(scaffolding): Add fields necessary to match filter --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer 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 index fea259b60..6b4f82a35 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-import/01-create-trap-resource.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/01-create-trap-resource.yaml @@ -8,10 +8,9 @@ metadata: name: loadbalancer-import-external-not-this-one spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: + vipSubnetRef: loadbalancer-import description: LoadBalancer loadbalancer-import-external from "loadbalancer-import" test - # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import/02-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import/02-assert.yaml index 7678b3e49..17e021a11 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-import/02-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/02-assert.yaml @@ -10,8 +10,13 @@ resourceRefs: 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 @@ -20,14 +25,11 @@ metadata: 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: loadbalancer-import-external description: LoadBalancer loadbalancer-import-external from "loadbalancer-import" test - # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import/02-create-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import/02-create-resource.yaml index 5c55a3b64..a0ca7b241 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-import/02-create-resource.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import/02-create-resource.yaml @@ -5,10 +5,9 @@ metadata: name: loadbalancer-import-external spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: + vipSubnetRef: loadbalancer-import description: LoadBalancer loadbalancer-import-external from "loadbalancer-import" test - # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-update/00-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-update/00-assert.yaml index bc7dffe84..554c1157d 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-update/00-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/00-assert.yaml @@ -7,7 +7,7 @@ resourceRefs: name: loadbalancer-update ref: loadbalancer assertAll: - - celExpr: "!has(loadbalancer.status.resource.description)" + - celExpr: "loadbalancer.status.resource.description == ''" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer @@ -16,7 +16,6 @@ metadata: status: resource: name: loadbalancer-update - # TODO(scaffolding): Add matches for more fields conditions: - type: Available status: "True" diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-update/00-minimal-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-update/00-minimal-resource.yaml index b0a66b498..cfa04301b 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-update/00-minimal-resource.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/00-minimal-resource.yaml @@ -5,10 +5,8 @@ metadata: name: loadbalancer-update spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created or updated cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Only add the mandatory fields. It's possible the resource - # doesn't have mandatory fields, in that case, leave it empty. - resource: {} + 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 index 045711ee7..5d74c652d 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-update/00-prerequisites.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/00-prerequisites.yaml @@ -4,3 +4,28 @@ 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 index 9cd82b42f..bdfe8f077 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-update/01-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/01-assert.yaml @@ -7,7 +7,9 @@ status: resource: name: loadbalancer-update-updated description: loadbalancer-update-updated - # TODO(scaffolding): match all fields that were modified + adminStateUp: false + tags: + - updated-tag conditions: - type: Available status: "True" diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-update/01-updated-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-update/01-updated-resource.yaml index 4b0cebf26..b8fc612de 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-update/01-updated-resource.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/01-updated-resource.yaml @@ -5,6 +5,9 @@ metadata: name: loadbalancer-update spec: resource: + vipSubnetRef: loadbalancer-update name: loadbalancer-update-updated description: loadbalancer-update-updated - # TODO(scaffolding): update all mutable fields + 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 index 82a2efd49..ea257400d 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-update/02-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-update/02-assert.yaml @@ -7,7 +7,8 @@ resourceRefs: name: loadbalancer-update ref: loadbalancer assertAll: - - celExpr: "!has(loadbalancer.status.resource.description)" + - celExpr: "loadbalancer.status.resource.description == ''" + - celExpr: "loadbalancer.status.resource.tags.size() == 0" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer @@ -16,7 +17,6 @@ metadata: status: resource: name: loadbalancer-update - # TODO(scaffolding): validate that updated fields were all reverted to their original value conditions: - type: Available status: "True" 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/loadbalancer.go b/internal/osclients/loadbalancer.go index 51101a8ba..7d26da8f8 100644 --- a/internal/osclients/loadbalancer.go +++ b/internal/osclients/loadbalancer.go @@ -63,7 +63,7 @@ func (c loadbalancerClient) CreateLoadBalancer(ctx context.Context, opts loadbal } func (c loadbalancerClient) DeleteLoadBalancer(ctx context.Context, resourceID string) error { - return loadbalancers.Delete(ctx, c.client, resourceID).ExtractErr() + return loadbalancers.Delete(ctx, c.client, resourceID, nil).ExtractErr() } func (c loadbalancerClient) GetLoadBalancer(ctx context.Context, resourceID string) (*loadbalancers.LoadBalancer, error) { diff --git a/internal/osclients/mock/doc.go b/internal/osclients/mock/doc.go index 47292b65f..85629c050 100644 --- a/internal/osclients/mock/doc.go +++ b/internal/osclients/mock/doc.go @@ -44,6 +44,9 @@ 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=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/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..61c460564 100644 --- a/internal/scope/mock.go +++ b/internal/scope/mock.go @@ -34,17 +34,18 @@ 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 + LoadBalancerClient *mock.MockLoadBalancerClient + NetworkClient *mock.MockNetworkClient + RoleClient *mock.MockRoleClient + ServiceClient *mock.MockServiceClient + VolumeClient *mock.MockVolumeClient + VolumeTypeClient *mock.MockVolumeTypeClient clientScopeCreateError error } @@ -61,19 +62,21 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { 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, + LoadBalancerClient: loadBalancerClient, + NetworkClient: networkClient, + RoleClient: roleClient, + ServiceClient: serviceClient, + VolumeClient: volumeClient, + VolumeTypeClient: volumetypeClient, } } @@ -132,6 +135,10 @@ func (f *MockScopeFactory) NewRoleClient() (osclients.RoleClient, error) { return f.RoleClient, 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..f1cf1a682 100644 --- a/internal/scope/provider.go +++ b/internal/scope/provider.go @@ -181,6 +181,10 @@ func (s *providerScope) NewRoleClient() (clients.RoleClient, error) { return clients.NewRoleClient(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..27d799b88 100644 --- a/internal/scope/scope.go +++ b/internal/scope/scope.go @@ -59,6 +59,7 @@ type Scope interface { NewServiceClient() (osclients.ServiceClient, error) NewVolumeClient() (osclients.VolumeClient, error) NewVolumeTypeClient() (osclients.VolumeTypeClient, error) + NewLoadBalancerClient() (osclients.LoadBalancerClient, error) ExtractToken() (*tokens.Token, error) } diff --git a/internal/util/dependency/helpers.go b/internal/util/dependency/helpers.go index ea62c2ff2..7d4da0484 100644 --- a/internal/util/dependency/helpers.go +++ b/internal/util/dependency/helpers.go @@ -55,7 +55,7 @@ func FetchDependency[TP DependencyType[T], T any]( 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, name, err)) + return nil, progress.WrapError(fmt.Errorf("fetching %s %s: %w", kind, string(*name), err)) } if !isReady(obj) { diff --git a/kuttl-test.yaml b/kuttl-test.yaml index d499782e6..b7adad88e 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -8,6 +8,7 @@ testDirs: - ./internal/controllers/group/tests/ - ./internal/controllers/image/tests/ - ./internal/controllers/keypair/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/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..38887057b 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -998,6 +998,216 @@ 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.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..d93a3e538 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -160,6 +160,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.KeyPairSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("KeyPairStatus"): return &apiv1alpha1.KeyPairStatusApplyConfiguration{} + 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..4556105c5 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,7 @@ type OpenstackV1alpha1Interface interface { GroupsGetter ImagesGetter KeyPairsGetter + LoadBalancersGetter NetworksGetter PortsGetter ProjectsGetter @@ -78,6 +79,10 @@ func (c *OpenstackV1alpha1Client) KeyPairs(namespace string) KeyPairInterface { return newKeyPairs(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..b0a138fe9 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,10 @@ func (c *FakeOpenstackV1alpha1) KeyPairs(namespace string) v1alpha1.KeyPairInter return newFakeKeyPairs(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_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..afba3d5b8 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,8 @@ type ImageExpansion interface{} type KeyPairExpansion interface{} +type LoadBalancerExpansion interface{} + type NetworkExpansion interface{} type PortExpansion interface{} 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..b3ca37d17 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go @@ -36,6 +36,8 @@ type Interface interface { Images() ImageInformer // KeyPairs returns a KeyPairInformer. KeyPairs() KeyPairInformer + // LoadBalancers returns a LoadBalancerInformer. + LoadBalancers() LoadBalancerInformer // Networks returns a NetworkInformer. Networks() NetworkInformer // Ports returns a PortInformer. @@ -105,6 +107,11 @@ func (v *version) KeyPairs() KeyPairInformer { return &keyPairInformer{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/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..50fec064c 100644 --- a/pkg/clients/informers/externalversions/generic.go +++ b/pkg/clients/informers/externalversions/generic.go @@ -65,6 +65,8 @@ 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("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..ff0ecf224 100644 --- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go +++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go @@ -66,6 +66,14 @@ type KeyPairListerExpansion interface{} // KeyPairNamespaceLister. type KeyPairNamespaceListerExpansion 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/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 2af8f88c9..5f8db1516 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -16,6 +16,7 @@ Package v1alpha1 contains API Schema definitions for the openstack v1alpha1 API - [Group](#group) - [Image](#image) - [KeyPair](#keypair) +- [LoadBalancer](#loadbalancer) - [Network](#network) - [Port](#port) - [Project](#project) @@ -169,6 +170,7 @@ _Appears in:_ - [GroupSpec](#groupspec) - [ImageSpec](#imagespec) - [KeyPairSpec](#keypairspec) +- [LoadBalancerSpec](#loadbalancerspec) - [NetworkSpec](#networkspec) - [PortSpec](#portspec) - [ProjectSpec](#projectspec) @@ -1005,6 +1007,7 @@ _Appears in:_ - [FloatingIPFilter](#floatingipfilter) - [FloatingIPResourceSpec](#floatingipresourcespec) - [HostRoute](#hostroute) +- [LoadBalancerResourceSpec](#loadbalancerresourcespec) - [SubnetFilter](#subnetfilter) - [SubnetGateway](#subnetgateway) - [SubnetResourceSpec](#subnetresourcespec) @@ -1616,6 +1619,7 @@ _Appears in:_ - [FloatingIPResourceSpec](#floatingipresourcespec) - [GroupFilter](#groupfilter) - [GroupResourceSpec](#groupresourcespec) +- [LoadBalancerFilter](#loadbalancerfilter) - [LoadBalancerResourceSpec](#loadbalancerresourcespec) - [NetworkFilter](#networkfilter) - [NetworkResourceSpec](#networkresourcespec) @@ -1638,9 +1642,181 @@ _Appears in:_ +#### 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) @@ -1689,6 +1865,7 @@ _Appears in:_ - [GroupSpec](#groupspec) - [ImageSpec](#imagespec) - [KeyPairSpec](#keypairspec) +- [LoadBalancerSpec](#loadbalancerspec) - [NetworkSpec](#networkspec) - [PortSpec](#portspec) - [ProjectSpec](#projectspec) @@ -1723,6 +1900,7 @@ _Appears in:_ - [GroupSpec](#groupspec) - [ImageSpec](#imagespec) - [KeyPairSpec](#keypairspec) +- [LoadBalancerSpec](#loadbalancerspec) - [NetworkSpec](#networkspec) - [PortSpec](#portspec) - [ProjectSpec](#projectspec) 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? From 65fc089c08ea0e341265d85e7bece7fc22bc7374 Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Thu, 25 Dec 2025 14:07:27 +0200 Subject: [PATCH 5/6] Scaffolding for the listener controller $ go run ./cmd/scaffold-controller -interactive=false -kind=Listener -gophercloud-client=NewLoadBalancerV2 -gophercloud-module=github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/listeners -gophercloud-type=Listener -openstack-json-object=listener -available-polling-period=15 -deleting-polling-period=15 -required-create-dependency=LoadBalancer -optional-create-dependency=Pool -import-dependency=LoadBalancer --- api/v1alpha1/listener_types.go | 98 ++++++ api/v1alpha1/zz_generated.deepcopy.go | 75 +++++ cmd/models-schema/zz_generated.openapi.go | 120 +++++++ config/rbac/role.yaml | 2 + .../samples/openstack_v1alpha1_listener.yaml | 14 + internal/controllers/listener/actuator.go | 305 ++++++++++++++++++ .../controllers/listener/actuator_test.go | 119 +++++++ internal/controllers/listener/controller.go | 135 ++++++++ internal/controllers/listener/status.go | 77 +++++ .../tests/listener-create-full/00-assert.yaml | 38 +++ .../00-create-resource.yaml | 43 +++ .../tests/listener-create-full/00-secret.yaml | 6 + .../tests/listener-create-full/README.md | 11 + .../listener-create-minimal/00-assert.yaml | 32 ++ .../00-create-resource.yaml | 28 ++ .../listener-create-minimal/00-secret.yaml | 6 + .../listener-create-minimal/01-assert.yaml | 11 + .../01-delete-secret.yaml | 7 + .../tests/listener-create-minimal/README.md | 15 + .../tests/listener-dependency/00-assert.yaml | 45 +++ .../00-create-resources-missing-deps.yaml | 56 ++++ .../tests/listener-dependency/00-secret.yaml | 6 + .../tests/listener-dependency/01-assert.yaml | 45 +++ .../01-create-dependencies.yaml | 32 ++ .../tests/listener-dependency/02-assert.yaml | 23 ++ .../02-delete-dependencies.yaml | 11 + .../tests/listener-dependency/03-assert.yaml | 11 + .../03-delete-resources.yaml | 13 + .../tests/listener-dependency/README.md | 21 ++ .../listener-import-dependency/00-assert.yaml | 17 + .../00-import-resource.yaml | 26 ++ .../listener-import-dependency/00-secret.yaml | 6 + .../listener-import-dependency/01-assert.yaml | 32 ++ .../01-create-trap-resource.yaml | 42 +++ .../listener-import-dependency/02-assert.yaml | 34 ++ .../02-create-resource.yaml | 41 +++ .../listener-import-dependency/03-assert.yaml | 6 + .../03-delete-import-dependencies.yaml | 7 + .../listener-import-dependency/04-assert.yaml | 6 + .../04-delete-resource.yaml | 7 + .../listener-import-dependency/README.md | 29 ++ .../listener-import-error/00-assert.yaml | 30 ++ .../00-create-resources.yaml | 43 +++ .../listener-import-error/00-secret.yaml | 6 + .../listener-import-error/01-assert.yaml | 15 + .../01-import-resource.yaml | 13 + .../tests/listener-import-error/README.md | 13 + .../tests/listener-import/00-assert.yaml | 15 + .../listener-import/00-import-resource.yaml | 15 + .../tests/listener-import/00-secret.yaml | 6 + .../tests/listener-import/01-assert.yaml | 34 ++ .../01-create-trap-resource.yaml | 31 ++ .../tests/listener-import/02-assert.yaml | 33 ++ .../listener-import/02-create-resource.yaml | 28 ++ .../listener/tests/listener-import/README.md | 18 ++ .../tests/listener-update/00-assert.yaml | 26 ++ .../listener-update/00-minimal-resource.yaml | 28 ++ .../listener-update/00-prerequisites.yaml | 6 + .../tests/listener-update/01-assert.yaml | 17 + .../listener-update/01-updated-resource.yaml | 10 + .../tests/listener-update/02-assert.yaml | 26 ++ .../listener-update/02-reverted-resource.yaml | 7 + .../listener/tests/listener-update/README.md | 17 + internal/osclients/listener.go | 104 ++++++ website/docs/crd-reference.md | 10 + 65 files changed, 2209 insertions(+) create mode 100644 api/v1alpha1/listener_types.go create mode 100644 config/samples/openstack_v1alpha1_listener.yaml create mode 100644 internal/controllers/listener/actuator.go create mode 100644 internal/controllers/listener/actuator_test.go create mode 100644 internal/controllers/listener/controller.go create mode 100644 internal/controllers/listener/status.go create mode 100644 internal/controllers/listener/tests/listener-create-full/00-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-create-full/00-create-resource.yaml create mode 100644 internal/controllers/listener/tests/listener-create-full/00-secret.yaml create mode 100644 internal/controllers/listener/tests/listener-create-full/README.md create mode 100644 internal/controllers/listener/tests/listener-create-minimal/00-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-create-minimal/00-create-resource.yaml create mode 100644 internal/controllers/listener/tests/listener-create-minimal/00-secret.yaml create mode 100644 internal/controllers/listener/tests/listener-create-minimal/01-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-create-minimal/01-delete-secret.yaml create mode 100644 internal/controllers/listener/tests/listener-create-minimal/README.md create mode 100644 internal/controllers/listener/tests/listener-dependency/00-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-dependency/00-create-resources-missing-deps.yaml create mode 100644 internal/controllers/listener/tests/listener-dependency/00-secret.yaml create mode 100644 internal/controllers/listener/tests/listener-dependency/01-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-dependency/01-create-dependencies.yaml create mode 100644 internal/controllers/listener/tests/listener-dependency/02-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-dependency/02-delete-dependencies.yaml create mode 100644 internal/controllers/listener/tests/listener-dependency/03-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-dependency/03-delete-resources.yaml create mode 100644 internal/controllers/listener/tests/listener-dependency/README.md create mode 100644 internal/controllers/listener/tests/listener-import-dependency/00-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-import-dependency/00-import-resource.yaml create mode 100644 internal/controllers/listener/tests/listener-import-dependency/00-secret.yaml create mode 100644 internal/controllers/listener/tests/listener-import-dependency/01-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-import-dependency/01-create-trap-resource.yaml create mode 100644 internal/controllers/listener/tests/listener-import-dependency/02-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-import-dependency/02-create-resource.yaml create mode 100644 internal/controllers/listener/tests/listener-import-dependency/03-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-import-dependency/03-delete-import-dependencies.yaml create mode 100644 internal/controllers/listener/tests/listener-import-dependency/04-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-import-dependency/04-delete-resource.yaml create mode 100644 internal/controllers/listener/tests/listener-import-dependency/README.md create mode 100644 internal/controllers/listener/tests/listener-import-error/00-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-import-error/00-create-resources.yaml create mode 100644 internal/controllers/listener/tests/listener-import-error/00-secret.yaml create mode 100644 internal/controllers/listener/tests/listener-import-error/01-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-import-error/01-import-resource.yaml create mode 100644 internal/controllers/listener/tests/listener-import-error/README.md create mode 100644 internal/controllers/listener/tests/listener-import/00-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-import/00-import-resource.yaml create mode 100644 internal/controllers/listener/tests/listener-import/00-secret.yaml create mode 100644 internal/controllers/listener/tests/listener-import/01-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-import/01-create-trap-resource.yaml create mode 100644 internal/controllers/listener/tests/listener-import/02-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-import/02-create-resource.yaml create mode 100644 internal/controllers/listener/tests/listener-import/README.md create mode 100644 internal/controllers/listener/tests/listener-update/00-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-update/00-minimal-resource.yaml create mode 100644 internal/controllers/listener/tests/listener-update/00-prerequisites.yaml create mode 100644 internal/controllers/listener/tests/listener-update/01-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-update/01-updated-resource.yaml create mode 100644 internal/controllers/listener/tests/listener-update/02-assert.yaml create mode 100644 internal/controllers/listener/tests/listener-update/02-reverted-resource.yaml create mode 100644 internal/controllers/listener/tests/listener-update/README.md create mode 100644 internal/osclients/listener.go diff --git a/api/v1alpha1/listener_types.go b/api/v1alpha1/listener_types.go new file mode 100644 index 000000000..88a9cb7de --- /dev/null +++ b/api/v1alpha1/listener_types.go @@ -0,0 +1,98 @@ +/* +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 + +// 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 ORC LoadBalancer which this resource is associated with. + // +required + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="loadBalancerRef is immutable" + LoadBalancerRef KubernetesNameRef `json:"loadBalancerRef,omitempty"` + + // poolRef is a reference to the ORC Pool which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="poolRef is immutable" + PoolRef *KubernetesNameRef `json:"poolRef,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the CreateOpts structure from + // github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/listeners + // + // Until you have implemented mutability for the field, you must add a CEL validation + // preventing the field being modified: + // `// +kubebuilder:validation:XValidation:rule="self == oldSelf",message=" is immutable"` +} + +// 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 is a reference to the ORC LoadBalancer which this resource is associated with. + // +optional + LoadBalancerRef *KubernetesNameRef `json:"loadBalancerRef,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the ListOpts structure from + // github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/listeners +} + +// ListenerResourceStatus represents the observed state of the resource. +type ListenerResourceStatus 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"` + + // loadBalancerID is the ID of the LoadBalancer to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + LoadBalancerID string `json:"loadBalancerID,omitempty"` + + // poolID is the ID of the Pool to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + PoolID string `json:"poolID,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the Listener structure from + // github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/listeners +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 8d480ed8a..0e7dfd7bd 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1975,6 +1975,81 @@ 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 *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 + } +} + +// 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 *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.PoolRef != nil { + in, out := &in.PoolRef, &out.PoolRef + *out = new(KubernetesNameRef) + **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 +} + +// 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 *LoadBalancer) DeepCopyInto(out *LoadBalancer) { *out = *in diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 49cf59aa5..f57a47457 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -100,6 +100,9 @@ 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.ListenerFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ListenerFilter(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.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), @@ -3767,6 +3770,123 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairStatus(ref comm } } +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 is a reference to the ORC LoadBalancer which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +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 ORC LoadBalancer which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "poolRef": { + SchemaProps: spec.SchemaProps{ + Description: "poolRef is a reference to the ORC Pool which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"loadBalancerRef"}, + }, + }, + } +} + +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. 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: "", + }, + }, + "loadBalancerID": { + SchemaProps: spec.SchemaProps{ + Description: "loadBalancerID is the ID of the LoadBalancer to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + "poolID": { + SchemaProps: spec.SchemaProps{ + Description: "poolID is the ID of the Pool to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancer(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 047bace25..e4afe2e80 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -23,6 +23,7 @@ rules: - groups - images - keypairs + - listeners - loadbalancers - networks - ports @@ -54,6 +55,7 @@ rules: - groups/status - images/status - keypairs/status + - listeners/status - loadbalancers/status - networks/status - ports/status diff --git a/config/samples/openstack_v1alpha1_listener.yaml b/config/samples/openstack_v1alpha1_listener.yaml new file mode 100644 index 000000000..ed0d69c10 --- /dev/null +++ b/config/samples/openstack_v1alpha1_listener.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-sample +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Sample Listener + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/listener/actuator.go b/internal/controllers/listener/actuator.go new file mode 100644 index 000000000..1855e74ef --- /dev/null +++ b/internal/controllers/listener/actuator.go @@ -0,0 +1,305 @@ +/* +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" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "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" + 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 + } + + // TODO(scaffolding) If you need to filter resources on fields that the List() function + // of gophercloud does not support, it's possible to perform client-side filtering. + // Check osclients.ResourceFilter + + listOpts := listeners.ListOpts{ + Name: getResourceName(orcObject), + Description: ptr.Deref(resourceSpec.Description, ""), + } + + return actuator.osClient.ListListeners(ctx, listOpts), true +} + +func (actuator listenerActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { + // TODO(scaffolding) If you need to filter resources on fields that the List() function + // of gophercloud does not support, it's possible to perform client-side filtering. + // Check osclients.ResourceFilter + var reconcileStatus progress.ReconcileStatus + + loadBalancer := &orcv1alpha1.LoadBalancer{} + if filter.LoadBalancerRef != nil { + loadBalancerKey := client.ObjectKey{Name: string(*filter.LoadBalancerRef), Namespace: obj.Namespace} + if err := actuator.k8sClient.Get(ctx, loadBalancerKey, loadBalancer); err != nil { + if apierrors.IsNotFound(err) { + reconcileStatus = reconcileStatus.WithReconcileStatus( + progress.WaitingOnObject("LoadBalancer", loadBalancerKey.Name, progress.WaitingOnCreation)) + } else { + reconcileStatus = reconcileStatus.WithReconcileStatus( + progress.WrapError(fmt.Errorf("fetching loadBalancer %s: %w", loadBalancerKey.Name, err))) + } + } else { + if !orcv1alpha1.IsAvailable(loadBalancer) || loadBalancer.Status.ID == nil { + reconcileStatus = reconcileStatus.WithReconcileStatus( + progress.WaitingOnObject("LoadBalancer", loadBalancerKey.Name, progress.WaitingOnReady)) + } + } + } + + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + + listOpts := listeners.ListOpts{ + Name: string(ptr.Deref(filter.Name, "")), + Description: string(ptr.Deref(filter.Description, "")), + LoadBalancer: ptr.Deref(loadBalancer.Status.ID, ""), + // TODO(scaffolding): Add more import filters + } + + return actuator.osClient.ListListeners(ctx, listOpts), nil +} + +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, "") + } + + var poolID string + if resource.PoolRef != nil { + pool, poolDepRS := poolDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Pool) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(poolDepRS) + if pool != nil { + poolID = ptr.Deref(pool.Status.ID, "") + } + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + createOpts := listeners.CreateOpts{ + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + LoadBalancerID: loadBalancerID, + PoolID: poolID, + // TODO(scaffolding): Add more fields + } + + 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.Status == ListenerStatusDeleting { + 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) + + // TODO(scaffolding): add handler for all fields supporting mutability + + 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..c20334050 --- /dev/null +++ b/internal/controllers/listener/controller.go @@ -0,0 +1,135 @@ +/* +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 poolDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ListenerList, *orcv1alpha1.Pool]( + "spec.resource.poolRef", + func(listener *orcv1alpha1.Listener) []string { + resource := listener.Spec.Resource + if resource == nil || resource.PoolRef == nil { + return nil + } + return []string{string(*resource.PoolRef)} + }, + 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 + } + + poolWatchEventHandler, err := poolDependency.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{})), + ). + Watches(&orcv1alpha1.Pool{}, poolWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Pool{})), + ). + // 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), + poolDependency.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..3bb126b6a --- /dev/null +++ b/internal/controllers/listener/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 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" +) +// TODO(scaffolding): these are just examples. Change them to the controller's need. +// Ideally, these constants are defined in gophercloud. +const ListenerStatusAvailable = "available" +const ListenerStatusInUse = "in-use" +const ListenerStatusDeleting = "deleting" + +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 + } else { + return metav1.ConditionUnknown, nil + } + } + // TODO(scaffolding): add conditions for returning available, for instance: + + if osResource.Status == ListenerStatusAvailable || osResource.Status == ListenerStatusInUse { + return metav1.ConditionTrue, nil + } + + // Otherwise we should continue to poll + return metav1.ConditionFalse, progress.WaitingOnOpenStack(progress.WaitingOnReady, listenerAvailablePollingPeriod) +} + +func (listenerStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.ListenerResourceStatus(). + WithLoadBalancerID(osResource.LoadBalancerID). + WithPoolID(osResource.PoolID). + WithName(osResource.Name) + + // TODO(scaffolding): add all of the fields supported in the ListenerResourceStatus struct + // If a zero-value isn't expected in the response, place it behind a conditional + + if osResource.Description != "" { + resourceStatus.WithDescription(osResource.Description) + } + + 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..5855c57af --- /dev/null +++ b/internal/controllers/listener/tests/listener-create-full/00-assert.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-create-full +status: + resource: + name: listener-create-full-override + description: Listener from "create full" test + # TODO(scaffolding): Add all fields the resource supports + 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 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Pool + name: listener-create-full + ref: pool +assertAll: + - celExpr: "listener.status.id != ''" + - celExpr: "listener.status.resource.loadBalancerID == loadBalancer.status.id" + - celExpr: "listener.status.resource.poolID == pool.status.id" + # TODO(scaffolding): Add more checks 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..34e0685d5 --- /dev/null +++ b/internal/controllers/listener/tests/listener-create-full/00-create-resource.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: listener-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Pool +metadata: + name: listener-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + name: listener-create-full-override + description: Listener from "create full" test + loadBalancerRef: listener-create-full + poolRef: listener-create-full + # TODO(scaffolding): Add all fields the resource supports 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..5d1a5aefb --- /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: + resource: + name: listener-create-minimal + # TODO(scaffolding): Add all fields the resource supports + 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.loadBalancerID == loadBalancer.status.id" + # TODO(scaffolding): Add more checks 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..a5bea9291 --- /dev/null +++ b/internal/controllers/listener/tests/listener-create-minimal/00-create-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: listener-create-minimal +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-create-minimal +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Only add the mandatory fields. It's possible the resource + # doesn't have mandatory fields, in that case, leave it empty. + resource: + loadBalancerRef: listener-create-full 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..4fccca8a0 --- /dev/null +++ b/internal/controllers/listener/tests/listener-dependency/00-assert.yaml @@ -0,0 +1,45 @@ +--- +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 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-dependency-no-pool +status: + conditions: + - type: Available + message: Waiting for Pool/listener-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Pool/listener-dependency 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..e10798349 --- /dev/null +++ b/internal/controllers/listener/tests/listener-dependency/00-create-resources-missing-deps.yaml @@ -0,0 +1,56 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: listener-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-dependency-no-loadbalancer +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + loadBalancerRef: listener-dependency-pending + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-dependency-no-pool +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + loadBalancerRef: listener-dependency + poolRef: listener-dependency + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-dependency-no-secret +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: listener-dependency + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: + loadBalancerRef: listener-dependency 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..bf3067c0d --- /dev/null +++ b/internal/controllers/listener/tests/listener-dependency/01-assert.yaml @@ -0,0 +1,45 @@ +--- +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 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-dependency-no-pool +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..a273f2717 --- /dev/null +++ b/internal/controllers/listener/tests/listener-dependency/01-create-dependencies.yaml @@ -0,0 +1,32 @@ +--- +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: LoadBalancer +metadata: + name: listener-dependency-pending +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Pool +metadata: + name: listener-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} 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..3bb51a7d5 --- /dev/null +++ b/internal/controllers/listener/tests/listener-dependency/02-assert.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LoadBalancer + name: listener-dependency + ref: loadBalancer + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Pool + name: listener-dependency + ref: pool + - 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: "pool.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/listener' in pool.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..cb0a379b7 --- /dev/null +++ b/internal/controllers/listener/tests/listener-dependency/02-delete-dependencies.yaml @@ -0,0 +1,11 @@ +--- +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 pool 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..1a746f251 --- /dev/null +++ b/internal/controllers/listener/tests/listener-dependency/03-assert.yaml @@ -0,0 +1,11 @@ +--- +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 pool 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..f46d240f4 --- /dev/null +++ b/internal/controllers/listener/tests/listener-dependency/03-delete-resources.yaml @@ -0,0 +1,13 @@ +--- +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 +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Listener + name: listener-dependency-no-pool 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..d7692c540 --- /dev/null +++ b/internal/controllers/listener/tests/listener-import-dependency/01-create-trap-resource.yaml @@ -0,0 +1,42 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: listener-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: listener-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +# 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: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + loadBalancerRef: listener-import-dependency-not-this-one + loadBalancerRef: listener-import-dependency-not-this-one + # TODO(scaffolding): Add the necessary fields to create the resource 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..199f7923c --- /dev/null +++ b/internal/controllers/listener/tests/listener-import-dependency/02-create-resource.yaml @@ -0,0 +1,41 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: listener-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: listener-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + loadBalancerRef: listener-import-dependency-external + loadBalancerRef: listener-import-dependency-external + # TODO(scaffolding): Add the necessary fields to create the resource 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..c403c3320 --- /dev/null +++ b/internal/controllers/listener/tests/listener-import-error/00-create-resources.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: listener-import-error +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-import-error-external-1 +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Listener from "import error" test + loadBalancerRef: listener-import-error + # TODO(scaffolding): add any required field +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-import-error-external-2 +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Listener from "import error" test + loadBalancerRef: listener-import-error + # TODO(scaffolding): add any required field 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..d241a7fc9 --- /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: + description: Listener from "import error" test 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..1020515d2 --- /dev/null +++ b/internal/controllers/listener/tests/listener-import/00-import-resource.yaml @@ -0,0 +1,15 @@ +--- +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 + description: Listener listener-import-external from "listener-import" test + # TODO(scaffolding): Add all fields supported by the filter 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..c20bb753e --- /dev/null +++ b/internal/controllers/listener/tests/listener-import/01-assert.yaml @@ -0,0 +1,34 @@ +--- +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 + description: Listener listener-import-external from "listener-import" test + # TODO(scaffolding): Add fields necessary to match filter +--- +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..c5d457bd1 --- /dev/null +++ b/internal/controllers/listener/tests/listener-import/01-create-trap-resource.yaml @@ -0,0 +1,31 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: listener-import-external-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +# 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: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Listener listener-import-external from "listener-import" test + loadBalancerRef: listener-import-external-not-this-one + # TODO(scaffolding): Add fields necessary to match filter 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..9652ba751 --- /dev/null +++ b/internal/controllers/listener/tests/listener-import/02-assert.yaml @@ -0,0 +1,33 @@ +--- +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 + description: Listener listener-import-external from "listener-import" test + # TODO(scaffolding): Add all fields the resource supports 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..08d1a67c2 --- /dev/null +++ b/internal/controllers/listener/tests/listener-import/02-create-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: listener-import +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-import-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Listener listener-import-external from "listener-import" test + loadBalancerRef: listener-import + # TODO(scaffolding): Add fields necessary to match filter 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..0878b92ee --- /dev/null +++ b/internal/controllers/listener/tests/listener-update/00-assert.yaml @@ -0,0 +1,26 @@ +--- +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)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-update +status: + resource: + name: listener-update + # TODO(scaffolding): Add matches for more fields + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success 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..f69a61637 --- /dev/null +++ b/internal/controllers/listener/tests/listener-update/00-minimal-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: listener-update +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-update +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created or updated + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Only add the mandatory fields. It's possible the resource + # doesn't have mandatory fields, in that case, leave it empty. + resource: + loadBalancerRef: listener-update 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..96b13f91f --- /dev/null +++ b/internal/controllers/listener/tests/listener-update/01-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-update +status: + resource: + name: listener-update-updated + description: listener-update-updated + # TODO(scaffolding): match all fields that were modified + 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..f700fd352 --- /dev/null +++ b/internal/controllers/listener/tests/listener-update/01-updated-resource.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-update +spec: + resource: + name: listener-update-updated + description: listener-update-updated + # TODO(scaffolding): update all mutable fields 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..5e7c99428 --- /dev/null +++ b/internal/controllers/listener/tests/listener-update/02-assert.yaml @@ -0,0 +1,26 @@ +--- +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)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: listener-update +status: + resource: + name: listener-update + # TODO(scaffolding): validate that updated fields were all reverted to their original value + 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/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/website/docs/crd-reference.md b/website/docs/crd-reference.md index 5f8db1516..b346ef54c 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -1619,6 +1619,8 @@ _Appears in:_ - [FloatingIPResourceSpec](#floatingipresourcespec) - [GroupFilter](#groupfilter) - [GroupResourceSpec](#groupresourcespec) +- [ListenerFilter](#listenerfilter) +- [ListenerResourceSpec](#listenerresourcespec) - [LoadBalancerFilter](#loadbalancerfilter) - [LoadBalancerResourceSpec](#loadbalancerresourcespec) - [NetworkFilter](#networkfilter) @@ -1642,6 +1644,12 @@ _Appears in:_ + + + + + + #### LoadBalancer @@ -2194,6 +2202,8 @@ _Appears in:_ - [ImageResourceSpec](#imageresourcespec) - [KeyPairFilter](#keypairfilter) - [KeyPairResourceSpec](#keypairresourcespec) +- [ListenerFilter](#listenerfilter) +- [ListenerResourceSpec](#listenerresourcespec) - [LoadBalancerFilter](#loadbalancerfilter) - [LoadBalancerResourceSpec](#loadbalancerresourcespec) - [NetworkFilter](#networkfilter) From 579b3f2a54a24a3b0d76b2b441c979cc10d1f95d Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Sun, 28 Dec 2025 10:58:55 +0200 Subject: [PATCH 6/6] listener controller lbaas --- PROJECT | 8 + api/v1alpha1/listener_types.go | 298 ++++++- api/v1alpha1/zz_generated.deepcopy.go | 325 +++++++- .../zz_generated.listener-resource.go | 177 ++++ cmd/manager/main.go | 2 + cmd/models-schema/zz_generated.openapi.go | 756 +++++++++++++++++- cmd/resource-generator/main.go | 3 + .../openstack.k-orc.cloud_listeners.yaml | 632 +++++++++++++++ config/crd/kustomization.yaml | 1 + config/samples/kustomization.yaml | 1 + .../samples/openstack_v1alpha1_listener.yaml | 9 +- internal/controllers/listener/actuator.go | 135 ++-- internal/controllers/listener/controller.go | 21 - internal/controllers/listener/status.go | 67 +- .../tests/listener-create-full/00-assert.yaml | 16 +- .../00-create-resource.yaml | 43 +- .../listener-create-minimal/00-assert.yaml | 8 +- .../00-create-resource.yaml | 37 +- .../tests/listener-dependency/00-assert.yaml | 15 - .../00-create-resources-missing-deps.yaml | 39 +- .../tests/listener-dependency/01-assert.yaml | 15 - .../01-create-dependencies.yaml | 27 +- .../tests/listener-dependency/02-assert.yaml | 6 - .../02-delete-dependencies.yaml | 2 - .../tests/listener-dependency/03-assert.yaml | 2 - .../03-delete-resources.yaml | 3 - .../01-create-trap-resource.yaml | 28 +- .../02-create-resource.yaml | 30 +- .../00-create-resources.yaml | 42 +- .../01-import-resource.yaml | 2 +- .../listener-import/00-import-resource.yaml | 2 - .../tests/listener-import/01-assert.yaml | 2 - .../01-create-trap-resource.yaml | 35 +- .../tests/listener-import/02-assert.yaml | 2 - .../listener-import/02-create-resource.yaml | 35 +- .../tests/listener-update/00-assert.yaml | 23 +- .../listener-update/00-minimal-resource.yaml | 35 +- .../tests/listener-update/01-assert.yaml | 1 - .../listener-update/01-updated-resource.yaml | 4 +- .../tests/listener-update/02-assert.yaml | 3 +- .../listener/zz_generated.adapter.go | 88 ++ .../listener/zz_generated.controller.go | 45 ++ internal/osclients/mock/doc.go | 3 + internal/osclients/mock/listener.go | 131 +++ internal/scope/mock.go | 7 + internal/scope/provider.go | 4 + internal/scope/scope.go | 1 + kuttl-test.yaml | 1 + .../api/v1alpha1/listener.go | 281 +++++++ .../api/v1alpha1/listenerfilter.go | 123 +++ .../api/v1alpha1/listenerhsts.go | 57 ++ .../api/v1alpha1/listenerimport.go | 48 ++ .../api/v1alpha1/listenerresourcespec.go | 266 ++++++ .../api/v1alpha1/listenerresourcestatus.go | 193 +++++ .../api/v1alpha1/listenerspec.go | 79 ++ .../api/v1alpha1/listenerstatus.go | 66 ++ .../applyconfiguration/internal/internal.go | 274 +++++++ pkg/clients/applyconfiguration/utils.go | 16 + .../typed/api/v1alpha1/api_client.go | 5 + .../api/v1alpha1/fake/fake_api_client.go | 4 + .../typed/api/v1alpha1/fake/fake_listener.go | 51 ++ .../typed/api/v1alpha1/generated_expansion.go | 2 + .../clientset/typed/api/v1alpha1/listener.go | 74 ++ .../api/v1alpha1/interface.go | 7 + .../externalversions/api/v1alpha1/listener.go | 102 +++ .../informers/externalversions/generic.go | 2 + .../api/v1alpha1/expansion_generated.go | 8 + pkg/clients/listers/api/v1alpha1/listener.go | 70 ++ website/docs/crd-reference.md | 248 ++++++ 69 files changed, 4823 insertions(+), 325 deletions(-) create mode 100644 api/v1alpha1/zz_generated.listener-resource.go create mode 100644 config/crd/bases/openstack.k-orc.cloud_listeners.yaml create mode 100644 internal/controllers/listener/zz_generated.adapter.go create mode 100644 internal/controllers/listener/zz_generated.controller.go create mode 100644 internal/osclients/mock/listener.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/listener.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/listenerfilter.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/listenerhsts.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/listenerimport.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/listenerresourcespec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/listenerresourcestatus.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/listenerspec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/listenerstatus.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_listener.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/listener.go create mode 100644 pkg/clients/informers/externalversions/api/v1alpha1/listener.go create mode 100644 pkg/clients/listers/api/v1alpha1/listener.go diff --git a/PROJECT b/PROJECT index d3f41ffce..b56b59130 100644 --- a/PROJECT +++ b/PROJECT @@ -56,6 +56,14 @@ 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 diff --git a/api/v1alpha1/listener_types.go b/api/v1alpha1/listener_types.go index 88a9cb7de..6cf3ca034 100644 --- a/api/v1alpha1/listener_types.go +++ b/api/v1alpha1/listener_types.go @@ -16,6 +16,51 @@ 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 @@ -29,50 +74,195 @@ type ListenerResourceSpec struct { // +optional Description *string `json:"description,omitempty"` - // loadBalancerRef is a reference to the ORC LoadBalancer which this resource is associated with. + // 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"` - // poolRef is a reference to the ORC Pool which this resource is associated with. + // 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 - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="poolRef is immutable" - PoolRef *KubernetesNameRef `json:"poolRef,omitempty"` + ConnectionLimit *int32 `json:"connectionLimit,omitempty"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the CreateOpts structure from - // github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/listeners - // - // Until you have implemented mutability for the field, you must add a CEL validation - // preventing the field being modified: - // `// +kubebuilder:validation:XValidation:rule="self == oldSelf",message=" is immutable"` + // 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 +// ListenerFilter defines an existing resource by its properties. // +kubebuilder:validation:MinProperties:=1 type ListenerFilter struct { - // name of the existing resource + // name of the existing resource. // +optional Name *OpenStackName `json:"name,omitempty"` - // description of the existing resource + // description of the existing resource. // +kubebuilder:validation:MinLength:=1 // +kubebuilder:validation:MaxLength:=255 // +optional Description *string `json:"description,omitempty"` - // loadBalancerRef is a reference to the ORC LoadBalancer which this resource is associated with. + // loadBalancerRef filters by the LoadBalancer this listener belongs to. // +optional LoadBalancerRef *KubernetesNameRef `json:"loadBalancerRef,omitempty"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the ListOpts structure from - // github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/listeners + // 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. Might not be unique. + // name is a human-readable name for the resource. // +kubebuilder:validation:MaxLength=1024 // +optional Name string `json:"name,omitempty"` @@ -82,17 +272,75 @@ type ListenerResourceStatus struct { // +optional Description string `json:"description,omitempty"` - // loadBalancerID is the ID of the LoadBalancer to which the resource is associated. + // loadBalancerID is the ID of the LoadBalancer this listener belongs to. // +kubebuilder:validation:MaxLength=1024 // +optional LoadBalancerID string `json:"loadBalancerID,omitempty"` - // poolID is the ID of the Pool to which the resource is associated. + // 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 - PoolID string `json:"poolID,omitempty"` + 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"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the Listener structure from - // github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/listeners + // 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/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 0e7dfd7bd..abecc4099 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1975,6 +1975,33 @@ 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 @@ -1993,6 +2020,36 @@ func (in *ListenerFilter) DeepCopyInto(out *ListenerFilter) { *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. @@ -2005,6 +2062,93 @@ func (in *ListenerFilter) DeepCopy() *ListenerFilter { 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 @@ -2018,11 +2162,103 @@ func (in *ListenerResourceSpec) DeepCopyInto(out *ListenerResourceSpec) { *out = new(string) **out = **in } - if in.PoolRef != nil { - in, out := &in.PoolRef, &out.PoolRef + 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. @@ -2038,6 +2274,28 @@ func (in *ListenerResourceSpec) DeepCopy() *ListenerResourceSpec { // 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. @@ -2050,6 +2308,69 @@ func (in *ListenerResourceStatus) DeepCopy() *ListenerResourceStatus { 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 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/cmd/manager/main.go b/cmd/manager/main.go index 0b761fe98..995010bd4 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -34,6 +34,7 @@ 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" @@ -127,6 +128,7 @@ func main() { keypair.New(scopeFactory), group.New(scopeFactory), role.New(scopeFactory), + listener.New(scopeFactory), loadbalancer.New(scopeFactory), } diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index f57a47457..00ec7d045 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -100,9 +100,15 @@ 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), @@ -3770,92 +3776,307 @@ 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", + 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", + Description: "name of the existing resource.", Type: []string{"string"}, Format: "", }, }, "description": { SchemaProps: spec.SchemaProps{ - Description: "description of the existing resource", + Description: "description of the existing resource.", Type: []string{"string"}, Format: "", }, }, "loadBalancerRef": { SchemaProps: spec.SchemaProps{ - Description: "loadBalancerRef is a reference to the ORC LoadBalancer which this resource is associated with.", + 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_ListenerResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_openstack_resource_controller_v2_api_v1alpha1_ListenerHSTS(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "ListenerResourceSpec contains the desired state of the resource.", + Description: "ListenerHSTS represents HTTP Strict Transport Security configuration.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "name": { + "maxAge": { 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"}, + 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: "", }, }, - "description": { + "preload": { SchemaProps: spec.SchemaProps{ - Description: "description is a human-readable description for the resource.", + 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: "", }, }, - "loadBalancerRef": { + "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: "loadBalancerRef is a reference to the ORC LoadBalancer which this resource is associated with.", + 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: "", }, }, - "poolRef": { + "apiVersion": { SchemaProps: spec.SchemaProps{ - Description: "poolRef is a reference to the ORC Pool which this resource is associated with.", + 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{"loadBalancerRef"}, + 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_ListenerResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_openstack_resource_controller_v2_api_v1alpha1_ListenerResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "ListenerResourceStatus represents the observed state of the resource.", + Description: "ListenerResourceSpec contains the desired 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.", + 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: "", }, @@ -3867,23 +4088,506 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ListenerResourceStatus Format: "", }, }, - "loadBalancerID": { + "loadBalancerRef": { SchemaProps: spec.SchemaProps{ - Description: "loadBalancerID is the ID of the LoadBalancer to which the resource is associated.", + Description: "loadBalancerRef is a reference to the LoadBalancer this listener belongs to.", Type: []string{"string"}, Format: "", }, }, - "poolID": { + "protocol": { SchemaProps: spec.SchemaProps{ - Description: "poolID is the ID of the Pool to which the resource is associated.", + 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"}, } } diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index d5f2a5b85..442a37227 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -165,6 +165,9 @@ var resources []templateFields = []templateFields{ { Name: "LoadBalancer", }, + { + Name: "Listener", + }, } // These resources won't be generated 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/kustomization.yaml b/config/crd/kustomization.yaml index 8012052f5..bc9e50c1d 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -9,6 +9,7 @@ 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 diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 075c016fa..085af8a7d 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -7,6 +7,7 @@ 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 diff --git a/config/samples/openstack_v1alpha1_listener.yaml b/config/samples/openstack_v1alpha1_listener.yaml index ed0d69c10..f3c7081ea 100644 --- a/config/samples/openstack_v1alpha1_listener.yaml +++ b/config/samples/openstack_v1alpha1_listener.yaml @@ -5,10 +5,13 @@ metadata: name: listener-sample spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: - description: Sample Listener - # TODO(scaffolding): Add all fields the resource supports + name: my-listener + description: Sample HTTP Listener + loadBalancerRef: loadbalancer-sample + protocol: HTTP + protocolPort: 80 + adminStateUp: true diff --git a/internal/controllers/listener/actuator.go b/internal/controllers/listener/actuator.go index 1855e74ef..adae49d89 100644 --- a/internal/controllers/listener/actuator.go +++ b/internal/controllers/listener/actuator.go @@ -23,7 +23,6 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/listeners" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -33,6 +32,7 @@ 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" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" ) @@ -45,8 +45,10 @@ type ( 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 @@ -76,55 +78,34 @@ func (actuator listenerActuator) ListOSResourcesForAdoption(ctx context.Context, return nil, false } - // TODO(scaffolding) If you need to filter resources on fields that the List() function - // of gophercloud does not support, it's possible to perform client-side filtering. - // Check osclients.ResourceFilter - listOpts := listeners.ListOpts{ - Name: getResourceName(orcObject), - Description: ptr.Deref(resourceSpec.Description, ""), + 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) { - // TODO(scaffolding) If you need to filter resources on fields that the List() function - // of gophercloud does not support, it's possible to perform client-side filtering. - // Check osclients.ResourceFilter var reconcileStatus progress.ReconcileStatus - loadBalancer := &orcv1alpha1.LoadBalancer{} - if filter.LoadBalancerRef != nil { - loadBalancerKey := client.ObjectKey{Name: string(*filter.LoadBalancerRef), Namespace: obj.Namespace} - if err := actuator.k8sClient.Get(ctx, loadBalancerKey, loadBalancer); err != nil { - if apierrors.IsNotFound(err) { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("LoadBalancer", loadBalancerKey.Name, progress.WaitingOnCreation)) - } else { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WrapError(fmt.Errorf("fetching loadBalancer %s: %w", loadBalancerKey.Name, err))) - } - } else { - if !orcv1alpha1.IsAvailable(loadBalancer) || loadBalancer.Status.ID == nil { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("LoadBalancer", loadBalancerKey.Name, progress.WaitingOnReady)) - } - } - } + 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) - if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { - return nil, reconcileStatus + var loadBalancerID string + if loadBalancer != nil { + loadBalancerID = ptr.Deref(loadBalancer.Status.ID, "") } listOpts := listeners.ListOpts{ - Name: string(ptr.Deref(filter.Name, "")), - Description: string(ptr.Deref(filter.Description, "")), - LoadBalancer: ptr.Deref(loadBalancer.Status.ID, ""), - // TODO(scaffolding): Add more import filters + Name: string(ptr.Deref(filter.Name, "")), + LoadbalancerID: loadBalancerID, } - return actuator.osClient.ListListeners(ctx, listOpts), nil + return actuator.osClient.ListListeners(ctx, listOpts), reconcileStatus } func (actuator listenerActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) { @@ -138,37 +119,65 @@ func (actuator listenerActuator) CreateResource(ctx context.Context, obj orcObje 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, "") - } - - var poolID string - if resource.PoolRef != nil { - pool, poolDepRS := poolDependency.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Pool) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, - ) - reconcileStatus = reconcileStatus.WithReconcileStatus(poolDepRS) - if pool != nil { - poolID = ptr.Deref(pool.Status.ID, "") - } + 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, - PoolID: poolID, - // TODO(scaffolding): Add more fields + 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) @@ -184,7 +193,7 @@ func (actuator listenerActuator) CreateResource(ctx context.Context, obj orcObje } func (actuator listenerActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { - if resource.Status == ListenerStatusDeleting { + if resource.ProvisioningStatus == ListenerProvisioningStatusPendingDelete { return progress.WaitingOnOpenStack(progress.WaitingOnReady, listenerDeletingPollingPeriod) } return progress.WrapError(actuator.osClient.DeleteListener(ctx, resource.ID)) @@ -204,8 +213,6 @@ func (actuator listenerActuator) updateResource(ctx context.Context, obj orcObje handleNameUpdate(&updateOpts, obj, osResource) handleDescriptionUpdate(&updateOpts, resource, osResource) - // TODO(scaffolding): add handler for all fields supporting mutability - needsUpdate, err := needsUpdate(updateOpts) if err != nil { return progress.WrapError( diff --git a/internal/controllers/listener/controller.go b/internal/controllers/listener/controller.go index c20334050..a120dba08 100644 --- a/internal/controllers/listener/controller.go +++ b/internal/controllers/listener/controller.go @@ -63,18 +63,6 @@ var loadBalancerDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1. finalizer, externalObjectFieldOwner, ) -var poolDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ListenerList, *orcv1alpha1.Pool]( - "spec.resource.poolRef", - func(listener *orcv1alpha1.Listener) []string { - resource := listener.Spec.Resource - if resource == nil || resource.PoolRef == nil { - return nil - } - return []string{string(*resource.PoolRef)} - }, - finalizer, externalObjectFieldOwner, -) - var loadBalancerImportDependency = dependency.NewDependency[*orcv1alpha1.ListenerList, *orcv1alpha1.LoadBalancer]( "spec.import.filter.loadBalancerRef", func(listener *orcv1alpha1.Listener) []string { @@ -96,11 +84,6 @@ func (c listenerReconcilerConstructor) SetupWithManager(ctx context.Context, mgr return err } - poolWatchEventHandler, err := poolDependency.WatchEventHandler(log, k8sClient) - if err != nil { - return err - } - loadBalancerImportWatchEventHandler, err := loadBalancerImportDependency.WatchEventHandler(log, k8sClient) if err != nil { return err @@ -111,9 +94,6 @@ func (c listenerReconcilerConstructor) SetupWithManager(ctx context.Context, mgr Watches(&orcv1alpha1.LoadBalancer{}, loadBalancerWatchEventHandler, builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.LoadBalancer{})), ). - Watches(&orcv1alpha1.Pool{}, poolWatchEventHandler, - builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Pool{})), - ). // 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{})), @@ -122,7 +102,6 @@ func (c listenerReconcilerConstructor) SetupWithManager(ctx context.Context, mgr if err := errors.Join( loadBalancerDependency.AddToManager(ctx, mgr), - poolDependency.AddToManager(ctx, mgr), loadBalancerImportDependency.AddToManager(ctx, mgr), credentialsDependency.AddToManager(ctx, mgr), credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency), diff --git a/internal/controllers/listener/status.go b/internal/controllers/listener/status.go index 3bb126b6a..159324162 100644 --- a/internal/controllers/listener/status.go +++ b/internal/controllers/listener/status.go @@ -25,11 +25,15 @@ import ( "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" ) -// TODO(scaffolding): these are just examples. Change them to the controller's need. -// Ideally, these constants are defined in gophercloud. -const ListenerStatusAvailable = "available" -const ListenerStatusInUse = "in-use" -const ListenerStatusDeleting = "deleting" + +// Octavia provisioning status values +const ( + ListenerProvisioningStatusActive = "ACTIVE" + ListenerProvisioningStatusError = "ERROR" + ListenerProvisioningStatusPendingCreate = "PENDING_CREATE" + ListenerProvisioningStatusPendingUpdate = "PENDING_UPDATE" + ListenerProvisioningStatusPendingDelete = "PENDING_DELETE" +) type listenerStatusWriter struct{} @@ -46,32 +50,59 @@ func (listenerStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.Liste if osResource == nil { if orcObject.Status.ID == nil { return metav1.ConditionFalse, nil - } else { - return metav1.ConditionUnknown, nil } + return metav1.ConditionUnknown, nil } - // TODO(scaffolding): add conditions for returning available, for instance: - if osResource.Status == ListenerStatusAvailable || osResource.Status == ListenerStatusInUse { + 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) } - - // Otherwise we should continue to poll - return metav1.ConditionFalse, progress.WaitingOnOpenStack(progress.WaitingOnReady, listenerAvailablePollingPeriod) } func (listenerStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { resourceStatus := orcapplyconfigv1alpha1.ListenerResourceStatus(). - WithLoadBalancerID(osResource.LoadBalancerID). - WithPoolID(osResource.PoolID). - WithName(osResource.Name) - - // TODO(scaffolding): add all of the fields supported in the ListenerResourceStatus struct - // If a zero-value isn't expected in the response, place it behind a conditional + 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 index 5855c57af..bfe41cfbb 100644 --- a/internal/controllers/listener/tests/listener-create-full/00-assert.yaml +++ b/internal/controllers/listener/tests/listener-create-full/00-assert.yaml @@ -4,10 +4,6 @@ kind: Listener metadata: name: listener-create-full status: - resource: - name: listener-create-full-override - description: Listener from "create full" test - # TODO(scaffolding): Add all fields the resource supports conditions: - type: Available status: "True" @@ -27,12 +23,12 @@ resourceRefs: kind: LoadBalancer name: listener-create-full ref: loadBalancer - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Pool - name: listener-create-full - ref: pool 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.poolID == pool.status.id" - # TODO(scaffolding): Add more checks + - 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 index 34e0685d5..124257b30 100644 --- a/internal/controllers/listener/tests/listener-create-full/00-create-resource.yaml +++ b/internal/controllers/listener/tests/listener-create-full/00-create-resource.yaml @@ -1,29 +1,40 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LoadBalancer +kind: Network metadata: name: listener-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: {} --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Pool +kind: Subnet metadata: name: listener-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + 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 @@ -31,13 +42,23 @@ metadata: name: listener-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: name: listener-create-full-override - description: Listener from "create full" test + description: Listener from create full test loadBalancerRef: listener-create-full - poolRef: listener-create-full - # TODO(scaffolding): Add all fields the resource supports + 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-minimal/00-assert.yaml b/internal/controllers/listener/tests/listener-create-minimal/00-assert.yaml index 5d1a5aefb..c85b6fadd 100644 --- a/internal/controllers/listener/tests/listener-create-minimal/00-assert.yaml +++ b/internal/controllers/listener/tests/listener-create-minimal/00-assert.yaml @@ -4,9 +4,6 @@ kind: Listener metadata: name: listener-create-minimal status: - resource: - name: listener-create-minimal - # TODO(scaffolding): Add all fields the resource supports conditions: - type: Available status: "True" @@ -28,5 +25,8 @@ resourceRefs: ref: loadBalancer assertAll: - celExpr: "listener.status.id != ''" + - celExpr: "listener.status.resource.name == 'listener-create-minimal'" - celExpr: "listener.status.resource.loadBalancerID == loadBalancer.status.id" - # TODO(scaffolding): Add more checks + - 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 index a5bea9291..e06345a85 100644 --- a/internal/controllers/listener/tests/listener-create-minimal/00-create-resource.yaml +++ b/internal/controllers/listener/tests/listener-create-minimal/00-create-resource.yaml @@ -1,28 +1,51 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LoadBalancer +kind: Network metadata: name: listener-create-minimal spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource 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: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Only add the mandatory fields. It's possible the resource - # doesn't have mandatory fields, in that case, leave it empty. resource: - loadBalancerRef: listener-create-full + loadBalancerRef: listener-create-minimal + protocol: HTTP + protocolPort: 80 diff --git a/internal/controllers/listener/tests/listener-dependency/00-assert.yaml b/internal/controllers/listener/tests/listener-dependency/00-assert.yaml index 4fccca8a0..d7fd1357c 100644 --- a/internal/controllers/listener/tests/listener-dependency/00-assert.yaml +++ b/internal/controllers/listener/tests/listener-dependency/00-assert.yaml @@ -28,18 +28,3 @@ status: message: Waiting for LoadBalancer/listener-dependency-pending to be created status: "True" reason: Progressing ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Listener -metadata: - name: listener-dependency-no-pool -status: - conditions: - - type: Available - message: Waiting for Pool/listener-dependency to be created - status: "False" - reason: Progressing - - type: Progressing - message: Waiting for Pool/listener-dependency 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 index e10798349..f2c20272f 100644 --- 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 @@ -1,45 +1,54 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LoadBalancer +kind: Network metadata: name: listener-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: {} --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Listener +kind: Subnet metadata: - name: listener-dependency-no-loadbalancer + name: listener-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: - loadBalancerRef: listener-dependency-pending - # TODO(scaffolding): Add the necessary fields to create the 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-pool + name: listener-dependency-no-loadbalancer spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: - loadBalancerRef: listener-dependency - poolRef: listener-dependency - # TODO(scaffolding): Add the necessary fields to create the resource + loadBalancerRef: listener-dependency-pending + protocol: HTTP + protocolPort: 80 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Listener @@ -47,10 +56,10 @@ metadata: name: listener-dependency-no-secret spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: listener-dependency managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: loadBalancerRef: listener-dependency + protocol: HTTP + protocolPort: 81 diff --git a/internal/controllers/listener/tests/listener-dependency/01-assert.yaml b/internal/controllers/listener/tests/listener-dependency/01-assert.yaml index bf3067c0d..f88bb904d 100644 --- a/internal/controllers/listener/tests/listener-dependency/01-assert.yaml +++ b/internal/controllers/listener/tests/listener-dependency/01-assert.yaml @@ -28,18 +28,3 @@ status: message: OpenStack resource is up to date status: "False" reason: Success ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Listener -metadata: - name: listener-dependency-no-pool -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 index a273f2717..f6ddcf702 100644 --- a/internal/controllers/listener/tests/listener-dependency/01-create-dependencies.yaml +++ b/internal/controllers/listener/tests/listener-dependency/01-create-dependencies.yaml @@ -6,27 +6,38 @@ commands: namespaced: true --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LoadBalancer +kind: Network metadata: name: listener-dependency-pending spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: {} --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Pool +kind: Subnet metadata: - name: listener-dependency + name: listener-dependency-pending spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + 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 index 3bb51a7d5..912a8ef97 100644 --- a/internal/controllers/listener/tests/listener-dependency/02-assert.yaml +++ b/internal/controllers/listener/tests/listener-dependency/02-assert.yaml @@ -6,10 +6,6 @@ resourceRefs: kind: LoadBalancer name: listener-dependency ref: loadBalancer - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Pool - name: listener-dependency - ref: pool - apiVersion: v1 kind: Secret name: listener-dependency @@ -17,7 +13,5 @@ resourceRefs: assertAll: - celExpr: "loadBalancer.metadata.deletionTimestamp != 0" - celExpr: "'openstack.k-orc.cloud/listener' in loadBalancer.metadata.finalizers" - - celExpr: "pool.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/listener' in pool.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 index cb0a379b7..3790fb04d 100644 --- a/internal/controllers/listener/tests/listener-dependency/02-delete-dependencies.yaml +++ b/internal/controllers/listener/tests/listener-dependency/02-delete-dependencies.yaml @@ -5,7 +5,5 @@ 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 pool 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 index 1a746f251..803c4a6a6 100644 --- a/internal/controllers/listener/tests/listener-dependency/03-assert.yaml +++ b/internal/controllers/listener/tests/listener-dependency/03-assert.yaml @@ -5,7 +5,5 @@ commands: # Dependencies that were prevented deletion before should now be gone - script: "! kubectl get loadbalancer listener-dependency --namespace $NAMESPACE" skipLogOutput: true -- script: "! kubectl get pool 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 index f46d240f4..7b812616d 100644 --- a/internal/controllers/listener/tests/listener-dependency/03-delete-resources.yaml +++ b/internal/controllers/listener/tests/listener-dependency/03-delete-resources.yaml @@ -8,6 +8,3 @@ delete: - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Listener name: listener-dependency-no-loadbalancer -- apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Listener - name: listener-dependency-no-pool 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 index d7692c540..8f68fd027 100644 --- 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 @@ -1,29 +1,40 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LoadBalancer +kind: Network metadata: name: listener-import-dependency-not-this-one spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource 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: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + 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 @@ -32,11 +43,10 @@ metadata: name: listener-import-dependency-not-this-one spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: loadBalancerRef: listener-import-dependency-not-this-one - loadBalancerRef: listener-import-dependency-not-this-one - # TODO(scaffolding): Add the necessary fields to create the resource + protocol: HTTP + protocolPort: 80 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 index 199f7923c..628afa93f 100644 --- a/internal/controllers/listener/tests/listener-import-dependency/02-create-resource.yaml +++ b/internal/controllers/listener/tests/listener-import-dependency/02-create-resource.yaml @@ -1,29 +1,40 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LoadBalancer +kind: Network metadata: name: listener-import-dependency-external spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource 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: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + vipSubnetRef: listener-import-dependency-external --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Listener @@ -31,11 +42,10 @@ metadata: name: listener-import-dependency-external spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created - cloudName: openstack-admin + cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: loadBalancerRef: listener-import-dependency-external - loadBalancerRef: listener-import-dependency-external - # TODO(scaffolding): Add the necessary fields to create the resource + protocol: HTTP + protocolPort: 80 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 index c403c3320..3a4ccdca0 100644 --- a/internal/controllers/listener/tests/listener-import-error/00-create-resources.yaml +++ b/internal/controllers/listener/tests/listener-import-error/00-create-resources.yaml @@ -1,31 +1,55 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LoadBalancer +kind: Network metadata: name: listener-import-error spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource 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: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: - description: Listener from "import error" test + name: listener-import-error-match loadBalancerRef: listener-import-error - # TODO(scaffolding): add any required field + protocol: HTTP + protocolPort: 80 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Listener @@ -33,11 +57,11 @@ metadata: name: listener-import-error-external-2 spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: - description: Listener from "import error" test + name: listener-import-error-match loadBalancerRef: listener-import-error - # TODO(scaffolding): add any required field + protocol: HTTP + protocolPort: 81 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 index d241a7fc9..f01bb2bf7 100644 --- a/internal/controllers/listener/tests/listener-import-error/01-import-resource.yaml +++ b/internal/controllers/listener/tests/listener-import-error/01-import-resource.yaml @@ -10,4 +10,4 @@ spec: managementPolicy: unmanaged import: filter: - description: Listener from "import error" test + name: listener-import-error-match diff --git a/internal/controllers/listener/tests/listener-import/00-import-resource.yaml b/internal/controllers/listener/tests/listener-import/00-import-resource.yaml index 1020515d2..b3a0b8cc3 100644 --- a/internal/controllers/listener/tests/listener-import/00-import-resource.yaml +++ b/internal/controllers/listener/tests/listener-import/00-import-resource.yaml @@ -11,5 +11,3 @@ spec: import: filter: name: listener-import-external - description: Listener listener-import-external from "listener-import" test - # TODO(scaffolding): Add all fields supported by the filter diff --git a/internal/controllers/listener/tests/listener-import/01-assert.yaml b/internal/controllers/listener/tests/listener-import/01-assert.yaml index c20bb753e..87a8ab42c 100644 --- a/internal/controllers/listener/tests/listener-import/01-assert.yaml +++ b/internal/controllers/listener/tests/listener-import/01-assert.yaml @@ -15,8 +15,6 @@ status: reason: Success resource: name: listener-import-external-not-this-one - description: Listener listener-import-external from "listener-import" test - # TODO(scaffolding): Add fields necessary to match filter --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Listener 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 index c5d457bd1..e55296211 100644 --- a/internal/controllers/listener/tests/listener-import/01-create-trap-resource.yaml +++ b/internal/controllers/listener/tests/listener-import/01-create-trap-resource.yaml @@ -1,16 +1,40 @@ --- 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: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + 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) @@ -21,11 +45,10 @@ metadata: name: listener-import-external-not-this-one spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: - description: Listener listener-import-external from "listener-import" test loadBalancerRef: listener-import-external-not-this-one - # TODO(scaffolding): Add fields necessary to match filter + 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 index 9652ba751..9dd988f28 100644 --- a/internal/controllers/listener/tests/listener-import/02-assert.yaml +++ b/internal/controllers/listener/tests/listener-import/02-assert.yaml @@ -29,5 +29,3 @@ status: reason: Success resource: name: listener-import-external - description: Listener listener-import-external from "listener-import" test - # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/listener/tests/listener-import/02-create-resource.yaml b/internal/controllers/listener/tests/listener-import/02-create-resource.yaml index 08d1a67c2..6c907e539 100644 --- a/internal/controllers/listener/tests/listener-import/02-create-resource.yaml +++ b/internal/controllers/listener/tests/listener-import/02-create-resource.yaml @@ -1,28 +1,51 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LoadBalancer +kind: Network metadata: name: listener-import spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource 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: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: - description: Listener listener-import-external from "listener-import" test loadBalancerRef: listener-import - # TODO(scaffolding): Add fields necessary to match filter + protocol: HTTP + protocolPort: 80 diff --git a/internal/controllers/listener/tests/listener-update/00-assert.yaml b/internal/controllers/listener/tests/listener-update/00-assert.yaml index 0878b92ee..0fc1812c7 100644 --- a/internal/controllers/listener/tests/listener-update/00-assert.yaml +++ b/internal/controllers/listener/tests/listener-update/00-assert.yaml @@ -1,14 +1,4 @@ --- -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)" ---- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Listener metadata: @@ -16,7 +6,6 @@ metadata: status: resource: name: listener-update - # TODO(scaffolding): Add matches for more fields conditions: - type: Available status: "True" @@ -24,3 +13,15 @@ status: - 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 index f69a61637..ef75fc181 100644 --- a/internal/controllers/listener/tests/listener-update/00-minimal-resource.yaml +++ b/internal/controllers/listener/tests/listener-update/00-minimal-resource.yaml @@ -1,28 +1,51 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LoadBalancer +kind: Network metadata: name: listener-update spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource 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: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created or updated cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Only add the mandatory fields. It's possible the resource - # doesn't have mandatory fields, in that case, leave it empty. resource: loadBalancerRef: listener-update + protocol: HTTP + protocolPort: 8080 diff --git a/internal/controllers/listener/tests/listener-update/01-assert.yaml b/internal/controllers/listener/tests/listener-update/01-assert.yaml index 96b13f91f..0c466cc21 100644 --- a/internal/controllers/listener/tests/listener-update/01-assert.yaml +++ b/internal/controllers/listener/tests/listener-update/01-assert.yaml @@ -7,7 +7,6 @@ status: resource: name: listener-update-updated description: listener-update-updated - # TODO(scaffolding): match all fields that were modified conditions: - type: Available status: "True" diff --git a/internal/controllers/listener/tests/listener-update/01-updated-resource.yaml b/internal/controllers/listener/tests/listener-update/01-updated-resource.yaml index f700fd352..17e3f59e8 100644 --- a/internal/controllers/listener/tests/listener-update/01-updated-resource.yaml +++ b/internal/controllers/listener/tests/listener-update/01-updated-resource.yaml @@ -7,4 +7,6 @@ spec: resource: name: listener-update-updated description: listener-update-updated - # TODO(scaffolding): update all mutable fields + 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 index 5e7c99428..883711357 100644 --- a/internal/controllers/listener/tests/listener-update/02-assert.yaml +++ b/internal/controllers/listener/tests/listener-update/02-assert.yaml @@ -7,7 +7,7 @@ resourceRefs: name: listener-update ref: listener assertAll: - - celExpr: "!has(listener.status.resource.description)" + - celExpr: "!has(listener.status.resource.description) || listener.status.resource.description == ''" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Listener @@ -16,7 +16,6 @@ metadata: status: resource: name: listener-update - # TODO(scaffolding): validate that updated fields were all reverted to their original value conditions: - type: Available status: "True" 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/osclients/mock/doc.go b/internal/osclients/mock/doc.go index 85629c050..e28fcd062 100644 --- a/internal/osclients/mock/doc.go +++ b/internal/osclients/mock/doc.go @@ -44,6 +44,9 @@ 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" 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/scope/mock.go b/internal/scope/mock.go index 61c460564..e36cbc95f 100644 --- a/internal/scope/mock.go +++ b/internal/scope/mock.go @@ -40,6 +40,7 @@ type MockScopeFactory struct { IdentityClient *mock.MockIdentityClient ImageClient *mock.MockImageClient KeyPairClient *mock.MockKeyPairClient + ListenerClient *mock.MockListenerClient LoadBalancerClient *mock.MockLoadBalancerClient NetworkClient *mock.MockNetworkClient RoleClient *mock.MockRoleClient @@ -57,6 +58,7 @@ 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) @@ -71,6 +73,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { IdentityClient: identityClient, ImageClient: imageClient, KeyPairClient: keypairClient, + ListenerClient: listenerClient, LoadBalancerClient: loadBalancerClient, NetworkClient: networkClient, RoleClient: roleClient, @@ -135,6 +138,10 @@ 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 } diff --git a/internal/scope/provider.go b/internal/scope/provider.go index f1cf1a682..85500f662 100644 --- a/internal/scope/provider.go +++ b/internal/scope/provider.go @@ -181,6 +181,10 @@ 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) } diff --git a/internal/scope/scope.go b/internal/scope/scope.go index 27d799b88..736bd21ef 100644 --- a/internal/scope/scope.go +++ b/internal/scope/scope.go @@ -59,6 +59,7 @@ 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/kuttl-test.yaml b/kuttl-test.yaml index b7adad88e..44deb3f05 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -8,6 +8,7 @@ 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/ 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/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 38887057b..486491408 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -998,6 +998,280 @@ 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: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index d93a3e538..c529bf6d3 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -160,6 +160,22 @@ 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"): 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 4556105c5..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,7 @@ type OpenstackV1alpha1Interface interface { GroupsGetter ImagesGetter KeyPairsGetter + ListenersGetter LoadBalancersGetter NetworksGetter PortsGetter @@ -79,6 +80,10 @@ 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) } 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 b0a138fe9..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,10 @@ 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) } 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/generated_expansion.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go index afba3d5b8..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,8 @@ type ImageExpansion interface{} type KeyPairExpansion interface{} +type ListenerExpansion interface{} + type LoadBalancerExpansion interface{} type NetworkExpansion 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/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go index b3ca37d17..b845d63eb 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go @@ -36,6 +36,8 @@ 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. @@ -107,6 +109,11 @@ 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} 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/generic.go b/pkg/clients/informers/externalversions/generic.go index 50fec064c..82648461c 100644 --- a/pkg/clients/informers/externalversions/generic.go +++ b/pkg/clients/informers/externalversions/generic.go @@ -65,6 +65,8 @@ 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"): diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go index ff0ecf224..99baa2003 100644 --- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go +++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go @@ -66,6 +66,14 @@ 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{} 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/website/docs/crd-reference.md b/website/docs/crd-reference.md index b346ef54c..7cb616f3f 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -16,6 +16,7 @@ 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) @@ -170,6 +171,7 @@ _Appears in:_ - [GroupSpec](#groupspec) - [ImageSpec](#imagespec) - [KeyPairSpec](#keypairspec) +- [ListenerSpec](#listenerspec) - [LoadBalancerSpec](#loadbalancerspec) - [NetworkSpec](#networkspec) - [PortSpec](#portspec) @@ -1644,9 +1646,253 @@ _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) @@ -1873,6 +2119,7 @@ _Appears in:_ - [GroupSpec](#groupspec) - [ImageSpec](#imagespec) - [KeyPairSpec](#keypairspec) +- [ListenerSpec](#listenerspec) - [LoadBalancerSpec](#loadbalancerspec) - [NetworkSpec](#networkspec) - [PortSpec](#portspec) @@ -1908,6 +2155,7 @@ _Appears in:_ - [GroupSpec](#groupspec) - [ImageSpec](#imagespec) - [KeyPairSpec](#keypairspec) +- [ListenerSpec](#listenerspec) - [LoadBalancerSpec](#loadbalancerspec) - [NetworkSpec](#networkspec) - [PortSpec](#portspec)