From 5c1876aaa44007ea1900b6c6d24bcbe9626d0f0f Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Thu, 25 Dec 2025 13:45:06 +0200 Subject: [PATCH 1/7] 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 da07b7ee..43e864a0 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 4eaa9cb328d4ce60eaa31e184729955c17614c93 Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Sun, 28 Dec 2025 15:21:44 +0200 Subject: [PATCH 2/7] scaffolding for 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 | 140 +++++++ api/v1alpha1/zz_generated.deepcopy.go | 110 ++++++ cmd/models-schema/zz_generated.openapi.go | 182 +++++++++ config/rbac/role.yaml | 2 + .../openstack_v1alpha1_loadbalancer.yaml | 14 + internal/controllers/loadbalancer/actuator.go | 353 ++++++++++++++++++ .../controllers/loadbalancer/actuator_test.go | 119 ++++++ .../controllers/loadbalancer/controller.go | 261 +++++++++++++ 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 ++ .../00-assert.yaml | 23 ++ .../00-import-resource.yaml | 68 ++++ .../00-secret.yaml | 6 + .../01-assert.yaml | 38 ++ .../01-create-trap-resource.yaml | 70 ++++ .../02-assert.yaml | 49 +++ .../02-create-resource.yaml | 69 ++++ .../03-assert.yaml | 12 + .../03-delete-import-dependencies.yaml | 13 + .../04-assert.yaml | 6 + .../04-delete-resource.yaml | 7 + .../loadbalancer-import-dependency/README.md | 29 ++ .../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 | 10 + 65 files changed, 2827 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-dependency/00-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/00-import-resource.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/00-secret.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/01-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/01-create-trap-resource.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/02-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/02-create-resource.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/03-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/03-delete-import-dependencies.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/04-assert.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/04-delete-resource.yaml create mode 100644 internal/controllers/loadbalancer/tests/loadbalancer-import-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 00000000..e583804e --- /dev/null +++ b/api/v1alpha1/loadbalancer_types.go @@ -0,0 +1,140 @@ +/* +Copyright 2026 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"` + + // vipNetworkRef is a reference to the ORC VipNetwork which this resource is associated with. + // +optional + VipNetworkRef *KubernetesNameRef `json:"vipNetworkRef,omitempty"` + + // projectRef is a reference to the ORC Project which this resource is associated with. + // +optional + ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` + + // vipSubnetRef is a reference to the ORC VipSubnet which this resource is associated with. + // +optional + VipSubnetRef *KubernetesNameRef `json:"vipSubnetRef,omitempty"` + + // vipPortRef is a reference to the ORC VipPort which this resource is associated with. + // +optional + VipPortRef *KubernetesNameRef `json:"vipPortRef,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 1b90b21c..d038ab58 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1975,6 +1975,116 @@ 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 + } + if in.VipNetworkRef != nil { + in, out := &in.VipNetworkRef, &out.VipNetworkRef + *out = new(KubernetesNameRef) + **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.VipPortRef != nil { + in, out := &in.VipPortRef, &out.VipPortRef + *out = new(KubernetesNameRef) + **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 80dc9b6c..405a319a 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,185 @@ 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: "", + }, + }, + "vipNetworkRef": { + SchemaProps: spec.SchemaProps{ + Description: "vipNetworkRef is a reference to the ORC VipNetwork 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: "", + }, + }, + "vipSubnetRef": { + SchemaProps: spec.SchemaProps{ + Description: "vipSubnetRef is a reference to the ORC VipSubnet which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "vipPortRef": { + SchemaProps: spec.SchemaProps{ + Description: "vipPortRef is a reference to the ORC VipPort which this resource is associated with.", + 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 5a0a7443..047bace2 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 00000000..bb18845f --- /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 00000000..6e92caef --- /dev/null +++ b/internal/controllers/loadbalancer/actuator.go @@ -0,0 +1,353 @@ +/* +Copyright 2026 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" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" +) + +// OpenStack resource types +type ( + osResourceT = 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 + var reconcileStatus progress.ReconcileStatus + + vipNetwork, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.VipNetworkRef, "VipNetwork", + func(dep *orcv1alpha1.VipNetwork) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + vipSubnet, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.VipSubnetRef, "VipSubnet", + func(dep *orcv1alpha1.VipSubnet) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + vipPort, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.VipPortRef, "VipPort", + func(dep *orcv1alpha1.VipPort) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + + listOpts := loadbalancers.ListOpts{ + Name: string(ptr.Deref(filter.Name, "")), + Description: string(ptr.Deref(filter.Description, "")), + VipNetworkID: ptr.Deref(vipNetwork.Status.ID, ""), + ProjectID: ptr.Deref(project.Status.ID, ""), + VipSubnetID: ptr.Deref(vipSubnet.Status.ID, ""), + VipPortID: ptr.Deref(vipPort.Status.ID, ""), + // TODO(scaffolding): Add more import filters + } + + return actuator.osClient.ListLoadBalancers(ctx, listOpts), reconcileStatus +} + +func (actuator loadbalancerActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) { + resource := obj.Spec.Resource + + if resource == nil { + // Should have been caught by API validation + return nil, progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Creation requested, but spec.resource is not set")) + } + var reconcileStatus progress.ReconcileStatus + + var 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 00000000..85260694 --- /dev/null +++ b/internal/controllers/loadbalancer/actuator_test.go @@ -0,0 +1,119 @@ +/* +Copyright 2026 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 00000000..492aa5a7 --- /dev/null +++ b/internal/controllers/loadbalancer/controller.go @@ -0,0 +1,261 @@ +/* +Copyright 2026 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, +) + +var vipNetworkImportDependency = dependency.NewDependency[*orcv1alpha1.LoadBalancerList, *orcv1alpha1.VipNetwork]( + "spec.import.filter.vipNetworkRef", + func(loadbalancer *orcv1alpha1.LoadBalancer) []string { + resource := loadbalancer.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.VipNetworkRef == nil { + return nil + } + return []string{string(*resource.Filter.VipNetworkRef)} + }, +) + +var projectImportDependency = dependency.NewDependency[*orcv1alpha1.LoadBalancerList, *orcv1alpha1.Project]( + "spec.import.filter.projectRef", + func(loadbalancer *orcv1alpha1.LoadBalancer) []string { + resource := loadbalancer.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.ProjectRef == nil { + return nil + } + return []string{string(*resource.Filter.ProjectRef)} + }, +) + +var vipSubnetImportDependency = dependency.NewDependency[*orcv1alpha1.LoadBalancerList, *orcv1alpha1.VipSubnet]( + "spec.import.filter.vipSubnetRef", + func(loadbalancer *orcv1alpha1.LoadBalancer) []string { + resource := loadbalancer.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.VipSubnetRef == nil { + return nil + } + return []string{string(*resource.Filter.VipSubnetRef)} + }, +) + +var vipPortImportDependency = dependency.NewDependency[*orcv1alpha1.LoadBalancerList, *orcv1alpha1.VipPort]( + "spec.import.filter.vipPortRef", + func(loadbalancer *orcv1alpha1.LoadBalancer) []string { + resource := loadbalancer.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.VipPortRef == nil { + return nil + } + return []string{string(*resource.Filter.VipPortRef)} + }, +) + +// 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 + } + + vipNetworkImportWatchEventHandler, err := vipNetworkImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + projectImportWatchEventHandler, err := projectImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + vipSubnetImportWatchEventHandler, err := vipSubnetImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + vipPortImportWatchEventHandler, err := vipPortImportDependency.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{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.VipNetwork{}, vipNetworkImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.VipNetwork{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Project{}, projectImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.VipSubnet{}, vipSubnetImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.VipSubnet{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.VipPort{}, vipPortImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.VipPort{})), + ). + 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), + vipNetworkImportDependency.AddToManager(ctx, mgr), + projectImportDependency.AddToManager(ctx, mgr), + vipSubnetImportDependency.AddToManager(ctx, mgr), + vipPortImportDependency.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 00000000..dbf0b654 --- /dev/null +++ b/internal/controllers/loadbalancer/status.go @@ -0,0 +1,68 @@ +/* +Copyright 2026 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 00000000..51a73c32 --- /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 00000000..1f3d9fd6 --- /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 00000000..045711ee --- /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 00000000..44bd88df --- /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 00000000..9230c2c7 --- /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 00000000..750dbd63 --- /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 00000000..045711ee --- /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 00000000..c116ae4b --- /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 00000000..1620791b --- /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 00000000..07e0ca5b --- /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 00000000..3b21fdb8 --- /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 00000000..3410659c --- /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 00000000..045711ee --- /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 00000000..5db640da --- /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 00000000..db46fcfd --- /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 00000000..9f9aa44f --- /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 00000000..601628e3 --- /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 00000000..c87e7cb8 --- /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 00000000..7fe29cc2 --- /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 00000000..b1a05802 --- /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-dependency/00-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/00-assert.yaml new file mode 100644 index 00000000..c084e235 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/00-assert.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for VipNetwork/loadbalancer-import-dependency to be ready + Waiting for Project/loadbalancer-import-dependency to be ready + Waiting for VipSubnet/loadbalancer-import-dependency to be ready + Waiting for VipPort/loadbalancer-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for VipNetwork/loadbalancer-import-dependency to be ready + Waiting for Project/loadbalancer-import-dependency to be ready + Waiting for VipSubnet/loadbalancer-import-dependency to be ready + Waiting for VipPort/loadbalancer-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/00-import-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/00-import-resource.yaml new file mode 100644 index 00000000..0f94ea83 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/00-import-resource.yaml @@ -0,0 +1,68 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: VipNetwork +metadata: + name: loadbalancer-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: loadbalancer-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: loadbalancer-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: loadbalancer-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: VipSubnet +metadata: + name: loadbalancer-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: loadbalancer-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: VipPort +metadata: + name: loadbalancer-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: loadbalancer-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + vipNetworkRef: loadbalancer-import-dependency + projectRef: loadbalancer-import-dependency + vipSubnetRef: loadbalancer-import-dependency + vipPortRef: loadbalancer-import-dependency diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/00-secret.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/00-secret.yaml new file mode 100644 index 00000000..045711ee --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-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/loadbalancer/tests/loadbalancer-import-dependency/01-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/01-assert.yaml new file mode 100644 index 00000000..5f40565b --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/01-assert.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-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: LoadBalancer +metadata: + name: loadbalancer-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for VipNetwork/loadbalancer-import-dependency to be ready + Waiting for Project/loadbalancer-import-dependency to be ready + Waiting for VipSubnet/loadbalancer-import-dependency to be ready + Waiting for VipPort/loadbalancer-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for VipNetwork/loadbalancer-import-dependency to be ready + Waiting for Project/loadbalancer-import-dependency to be ready + Waiting for VipSubnet/loadbalancer-import-dependency to be ready + Waiting for VipPort/loadbalancer-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/01-create-trap-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/01-create-trap-resource.yaml new file mode 100644 index 00000000..1c28e72b --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/01-create-trap-resource.yaml @@ -0,0 +1,70 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: VipNetwork +metadata: + name: loadbalancer-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: Project +metadata: + name: loadbalancer-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: VipSubnet +metadata: + name: loadbalancer-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: VipPort +metadata: + name: loadbalancer-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 `loadbalancer-import-dependency-not-this-one` should not be picked by the import filter +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-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: + vipNetworkRef: loadbalancer-import-dependency-not-this-one + projectRef: loadbalancer-import-dependency-not-this-one + vipSubnetRef: loadbalancer-import-dependency-not-this-one + vipPortRef: loadbalancer-import-dependency-not-this-one + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/02-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/02-assert.yaml new file mode 100644 index 00000000..bb1dd685 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/02-assert.yaml @@ -0,0 +1,49 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LoadBalancer + name: loadbalancer-import-dependency + ref: loadbalancer1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LoadBalancer + name: loadbalancer-import-dependency-not-this-one + ref: loadbalancer2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: VipNetwork + name: loadbalancer-import-dependency + ref: vipNetwork + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: loadbalancer-import-dependency + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: VipSubnet + name: loadbalancer-import-dependency + ref: vipSubnet + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: VipPort + name: loadbalancer-import-dependency + ref: vipPort +assertAll: + - celExpr: "loadbalancer1.status.id != loadbalancer2.status.id" + - celExpr: "loadbalancer1.status.resource.vipNetworkID == vipNetwork.status.id" + - celExpr: "loadbalancer1.status.resource.projectID == project.status.id" + - celExpr: "loadbalancer1.status.resource.vipSubnetID == vipSubnet.status.id" + - celExpr: "loadbalancer1.status.resource.vipPortID == vipPort.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: loadbalancer-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/loadbalancer/tests/loadbalancer-import-dependency/02-create-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/02-create-resource.yaml new file mode 100644 index 00000000..1d5ff803 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/02-create-resource.yaml @@ -0,0 +1,69 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: VipNetwork +metadata: + name: loadbalancer-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: Project +metadata: + name: loadbalancer-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: VipSubnet +metadata: + name: loadbalancer-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: VipPort +metadata: + name: loadbalancer-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: loadbalancer-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: + vipNetworkRef: loadbalancer-import-dependency-external + projectRef: loadbalancer-import-dependency-external + vipSubnetRef: loadbalancer-import-dependency-external + vipPortRef: loadbalancer-import-dependency-external + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/03-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/03-assert.yaml new file mode 100644 index 00000000..8a3a6bcc --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/03-assert.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get vipnetwork loadbalancer-import-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get project loadbalancer-import-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get vipsubnet loadbalancer-import-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get vipport loadbalancer-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/03-delete-import-dependencies.yaml new file mode 100644 index 00000000..06db478c --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/03-delete-import-dependencies.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We should be able to delete the import dependencies + - command: kubectl delete vipnetwork loadbalancer-import-dependency + namespaced: true + - command: kubectl delete project loadbalancer-import-dependency + namespaced: true + - command: kubectl delete vipsubnet loadbalancer-import-dependency + namespaced: true + - command: kubectl delete vipport loadbalancer-import-dependency + namespaced: true diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/04-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/04-assert.yaml new file mode 100644 index 00000000..0aa18653 --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/04-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get loadbalancer loadbalancer-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/04-delete-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/04-delete-resource.yaml new file mode 100644 index 00000000..4d83e9af --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/04-delete-resource.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LoadBalancer + name: loadbalancer-import-dependency diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/README.md b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/README.md new file mode 100644 index 00000000..15004d8c --- /dev/null +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/README.md @@ -0,0 +1,29 @@ +# Check dependency handling for imported LoadBalancer + +## Step 00 + +Import a LoadBalancer that references other imported resources. The referenced imported resources have no matching resources yet. +Verify the LoadBalancer is waiting for the dependency to be ready. + +## Step 01 + +Create a LoadBalancer matching the import filter, except for referenced resources, and verify that it's not being imported. + +## Step 02 + +Create the referenced resources and a LoadBalancer matching the import filters. + +Verify that the observed status on the imported LoadBalancer corresponds to the spec of the created LoadBalancer. + +## 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 LoadBalancer and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-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 00000000..310f9510 --- /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 00000000..37ab64de --- /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 00000000..045711ee --- /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 00000000..1629ec94 --- /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 00000000..23918c0b --- /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 00000000..3f9d4cf8 --- /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 00000000..86f108f6 --- /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 00000000..b5f3c6de --- /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 00000000..045711ee --- /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 00000000..aa4e17c0 --- /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 00000000..fea259b6 --- /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 00000000..7678b3e4 --- /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 00000000..5c55a3b6 --- /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 00000000..09022442 --- /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 00000000..bc7dffe8 --- /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 00000000..b0a66b49 --- /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 00000000..045711ee --- /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 00000000..9cd82b42 --- /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 00000000..4b0cebf2 --- /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 00000000..82a2efd4 --- /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 00000000..2c6c253f --- /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 00000000..9dafb19c --- /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 00000000..d2857458 --- /dev/null +++ b/internal/osclients/loadbalancer.go @@ -0,0 +1,104 @@ +/* +Copyright 2026 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 da7cd698..a36720a0 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -1616,6 +1616,8 @@ _Appears in:_ - [FloatingIPResourceSpec](#floatingipresourcespec) - [GroupFilter](#groupfilter) - [GroupResourceSpec](#groupresourcespec) +- [LoadBalancerFilter](#loadbalancerfilter) +- [LoadBalancerResourceSpec](#loadbalancerresourcespec) - [NetworkFilter](#networkfilter) - [NetworkResourceSpec](#networkresourcespec) - [PortFilter](#portfilter) @@ -1637,6 +1639,12 @@ _Appears in:_ + + + + + + #### MAC _Underlying type:_ _string_ @@ -2009,6 +2017,8 @@ _Appears in:_ - [ImageResourceSpec](#imageresourcespec) - [KeyPairFilter](#keypairfilter) - [KeyPairResourceSpec](#keypairresourcespec) +- [LoadBalancerFilter](#loadbalancerfilter) +- [LoadBalancerResourceSpec](#loadbalancerresourcespec) - [NetworkFilter](#networkfilter) - [NetworkResourceSpec](#networkresourcespec) - [PortFilter](#portfilter) From ba7d28d97af837d607076c8c12900be04fd2a058 Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Thu, 25 Dec 2025 11:28:54 +0200 Subject: [PATCH 3/7] 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 | 182 ++++-- api/v1alpha1/zz_generated.deepcopy.go | 214 ++++++- .../zz_generated.loadbalancer-resource.go | 177 ++++++ cmd/manager/main.go | 2 + cmd/models-schema/zz_generated.openapi.go | 483 +++++++++++++++- 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 | 148 +++-- .../controllers/loadbalancer/actuator_test.go | 66 +++ .../controllers/loadbalancer/controller.go | 30 +- internal/controllers/loadbalancer/status.go | 37 +- .../loadbalancer-create-full/00-assert.yaml | 20 +- .../00-create-resource.yaml | 56 +- .../00-assert.yaml | 10 +- .../00-create-resource.yaml | 31 +- .../loadbalancer-dependency/00-assert.yaml | 42 +- .../00-create-resources-missing-deps.yaml | 52 +- .../loadbalancer-dependency/01-assert.yaml | 25 - .../01-create-dependencies.yaml | 40 +- .../loadbalancer-dependency/02-assert.yaml | 12 +- .../02-delete-dependencies.yaml | 6 +- .../loadbalancer-dependency/03-assert.yaml | 4 +- .../03-delete-resources.yaml | 3 - .../00-assert.yaml | 12 +- .../00-import-resource.yaml | 19 +- .../01-assert.yaml | 12 +- .../01-create-trap-resource.yaml | 49 +- .../02-assert.yaml | 18 +- .../02-create-resource.yaml | 47 +- .../03-assert.yaml | 6 +- .../03-delete-import-dependencies.yaml | 6 +- .../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 + 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 | 177 ++++++ website/docs/design/dependency-resolver.md | 391 +++++++++++++ 78 files changed, 4431 insertions(+), 434 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 8d6e2c12..d3f41ffc 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 e583804e..d7bb4c1b 100644 --- a/api/v1alpha1/loadbalancer_types.go +++ b/api/v1alpha1/loadbalancer_types.go @@ -16,7 +16,21 @@ limitations under the License. package v1alpha1 +// Octavia provisioning status values +const ( + LoadbalancerProvisioningStatusActive = "ACTIVE" + LoadbalancerProvisioningStatusError = "ERROR" + LoadbalancerProvisioningStatusPendingCreate = "PENDING_CREATE" + LoadbalancerProvisioningStatusPendingUpdate = "PENDING_UPDATE" + LoadbalancerProvisioningStatusPendingDelete = "PENDING_DELETE" +) + +// +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 +43,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 +70,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,25 +112,66 @@ type LoadBalancerFilter struct { // +optional Description *string `json:"description,omitempty"` - // vipNetworkRef is a reference to the ORC VipNetwork which this resource is associated with. - // +optional - VipNetworkRef *KubernetesNameRef `json:"vipNetworkRef,omitempty"` - - // projectRef is a reference to the ORC Project which this resource is associated with. + // 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 is a reference to the ORC VipSubnet which this resource is associated with. + // vipSubnetRef filters by the subnet on which the load balancer's address is allocated. // +optional VipSubnetRef *KubernetesNameRef `json:"vipSubnetRef,omitempty"` - // vipPortRef is a reference to the ORC VipPort which this resource is associated with. + // 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"` - // 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 + // 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. @@ -109,20 +186,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 @@ -134,7 +211,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 d038ab58..3f19b036 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,11 +2015,6 @@ func (in *LoadBalancerFilter) DeepCopyInto(out *LoadBalancerFilter) { *out = new(string) **out = **in } - if in.VipNetworkRef != nil { - in, out := &in.VipNetworkRef, &out.VipNetworkRef - *out = new(KubernetesNameRef) - **out = **in - } if in.ProjectRef != nil { in, out := &in.ProjectRef, &out.ProjectRef *out = new(KubernetesNameRef) @@ -2003,11 +2025,36 @@ func (in *LoadBalancerFilter) DeepCopyInto(out *LoadBalancerFilter) { *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. @@ -2020,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 @@ -2033,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 } @@ -2058,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. @@ -2073,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. @@ -2085,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 00000000..56a88268 --- /dev/null +++ b/api/v1alpha1/zz_generated.loadbalancer-resource.go @@ -0,0 +1,177 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright 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 293aa2ba..0b761fe9 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 405a319a..46915301 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,37 +3838,217 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LoadBalancerFilter(ref Format: "", }, }, - "vipNetworkRef": { + "projectRef": { SchemaProps: spec.SchemaProps{ - Description: "vipNetworkRef is a reference to the ORC VipNetwork which this resource is associated with.", + Description: "projectRef is a reference to the ORC Project this resource is associated with. Typically, only used by admin.", Type: []string{"string"}, Format: "", }, }, - "projectRef": { + "vipSubnetRef": { SchemaProps: spec.SchemaProps{ - Description: "projectRef is a reference to the ORC Project which this resource is associated with.", + Description: "vipSubnetRef filters by the subnet on which the load balancer's address is allocated.", Type: []string{"string"}, Format: "", }, }, - "vipSubnetRef": { + "vipNetworkRef": { SchemaProps: spec.SchemaProps{ - Description: "vipSubnetRef is a reference to the ORC VipSubnet which this resource is associated with.", + 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 is a reference to the ORC VipPort which this resource is associated with.", + 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"}, } } @@ -3838,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: "", }, @@ -3873,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: "", + }, + }, + }, + }, + }, }, }, }, @@ -3900,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: "", }, @@ -3935,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 b3f4ece7..ecd71a16 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -160,6 +160,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 00000000..08f8470e --- /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 33b8c85e..8012052f 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 dac467c6..075c016f 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 bb18845f..9510e2d0 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 6e92caef..dc240b28 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" @@ -35,6 +37,14 @@ import ( 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 + + // The frequency to poll when waiting for the load balancer to be deleted + loadbalancerDeletingPollingPeriod = 15 * time.Second +) + // OpenStack resource types type ( osResourceT = loadbalancers.LoadBalancer @@ -71,49 +81,41 @@ 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( + vipNetwork, rs := dependency.FetchDependency[*orcv1alpha1.Network, orcv1alpha1.Network]( ctx, actuator.k8sClient, obj.Namespace, - filter.VipNetworkRef, "VipNetwork", - func(dep *orcv1alpha1.VipNetwork) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + filter.VipNetworkRef, "Network", + func(n *orcv1alpha1.Network) bool { return orcv1alpha1.IsAvailable(n) && n.Status.ID != nil }, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) - project, rs := dependency.FetchDependency( + project, rs := dependency.FetchDependency[*orcv1alpha1.Project, orcv1alpha1.Project]( ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", - func(dep *orcv1alpha1.Project) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + func(n *orcv1alpha1.Project) bool { return orcv1alpha1.IsAvailable(n) && n.Status.ID != nil }, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) - vipSubnet, rs := dependency.FetchDependency( + vipSubnet, rs := dependency.FetchDependency[*orcv1alpha1.Subnet, orcv1alpha1.Subnet]( ctx, actuator.k8sClient, obj.Namespace, - filter.VipSubnetRef, "VipSubnet", - func(dep *orcv1alpha1.VipSubnet) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + filter.VipSubnetRef, "Subnet", + func(n *orcv1alpha1.Subnet) bool { return orcv1alpha1.IsAvailable(n) && n.Status.ID != nil }, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) - vipPort, rs := dependency.FetchDependency( + vipPort, rs := dependency.FetchDependency[*orcv1alpha1.Port, orcv1alpha1.Port]( ctx, actuator.k8sClient, obj.Namespace, - filter.VipPortRef, "VipPort", - func(dep *orcv1alpha1.VipPort) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + filter.VipPortRef, "Port", + func(n *orcv1alpha1.Port) bool { return orcv1alpha1.IsAvailable(n) && n.Status.ID != nil }, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) @@ -122,13 +124,15 @@ func (actuator loadbalancerActuator) ListOSResourcesForImport(ctx context.Contex } listOpts := loadbalancers.ListOpts{ - Name: string(ptr.Deref(filter.Name, "")), - Description: string(ptr.Deref(filter.Description, "")), - VipNetworkID: ptr.Deref(vipNetwork.Status.ID, ""), - ProjectID: ptr.Deref(project.Status.ID, ""), - VipSubnetID: ptr.Deref(vipSubnet.Status.ID, ""), - VipPortID: ptr.Deref(vipPort.Status.ID, ""), - // 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: ptr.Deref(vipNetwork.Status.ID, ""), + ProjectID: ptr.Deref(project.Status.ID, ""), + VipSubnetID: ptr.Deref(vipSubnet.Status.ID, ""), + VipPortID: ptr.Deref(vipPort.Status.ID, ""), } return actuator.osClient.ListLoadBalancers(ctx, listOpts), reconcileStatus @@ -144,8 +148,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 @@ -153,12 +157,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 @@ -166,12 +170,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 @@ -179,7 +183,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, "") } } @@ -211,15 +215,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) @@ -235,7 +251,21 @@ func (actuator loadbalancerActuator) CreateResource(ctx context.Context, obj orc } func (actuator loadbalancerActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { - return progress.WrapError(actuator.osClient.DeleteLoadBalancer(ctx, resource.ID)) + switch resource.ProvisioningStatus { + case orcv1alpha1.LoadbalancerProvisioningStatusPendingDelete: + return progress.WaitingOnOpenStack(progress.WaitingOnReady, loadbalancerDeletingPollingPeriod) + case orcv1alpha1.LoadbalancerProvisioningStatusPendingCreate, orcv1alpha1.LoadbalancerProvisioningStatusPendingUpdate: + // We can't delete a loadbalancer that's in a pending state, so we need to wait for it to become ACTIVE + return progress.WaitingOnOpenStack(progress.WaitingOnReady, loadbalancerActivePollingPeriod) + } + + err := actuator.osClient.DeleteLoadBalancer(ctx, resource.ID) + // 409 Conflict means the loadbalancer is already in PENDING_DELETE state. + // Treat this as success and let the controller poll for deletion completion. + if orcerrors.IsConflict(err) { + return progress.WaitingOnOpenStack(progress.WaitingOnReady, loadbalancerDeletingPollingPeriod) + } + return progress.WrapError(err) } func (actuator loadbalancerActuator) updateResource(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { @@ -251,8 +281,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 { @@ -284,7 +314,7 @@ func needsUpdate(updateOpts loadbalancers.UpdateOpts) (bool, error) { return false, err } - updateMap, ok := updateOptsMap["load_balancer"].(map[string]any) + updateMap, ok := updateOptsMap["loadbalancer"].(map[string]any) if !ok { updateMap = make(map[string]any) } @@ -306,6 +336,28 @@ func handleDescriptionUpdate(updateOpts *loadbalancers.UpdateOpts, resource *res } } +func handleAdminStateUpdate(updateOpts *loadbalancers.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + // Default to true if not specified (OpenStack default) + adminStateUp := ptr.Deref(resource.AdminStateUp, true) + 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/actuator_test.go b/internal/controllers/loadbalancer/actuator_test.go index 85260694..07d70a38 100644 --- a/internal/controllers/loadbalancer/actuator_test.go +++ b/internal/controllers/loadbalancer/actuator_test.go @@ -117,3 +117,69 @@ func TestHandleDescriptionUpdate(t *testing.T) { } } + +func TestHandleAdminStateUpdate(t *testing.T) { + ptrToBool := ptr.To[bool] + testCases := []struct { + name string + newValue *bool + existingValue bool + expectChange bool + }{ + {name: "Identical true", newValue: ptrToBool(true), existingValue: true, expectChange: false}, + {name: "Identical false", newValue: ptrToBool(false), existingValue: false, expectChange: false}, + {name: "Different true to false", newValue: ptrToBool(false), existingValue: true, expectChange: true}, + {name: "Different false to true", newValue: ptrToBool(true), existingValue: false, expectChange: true}, + {name: "No value provided, existing is set to false", newValue: nil, existingValue: false, expectChange: true}, + {name: "No value provided, existing is default (true)", newValue: nil, existingValue: true, expectChange: false}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.LoadBalancerResourceSpec{AdminStateUp: tt.newValue} + osResource := &osResourceT{AdminStateUp: tt.existingValue} + + updateOpts := loadbalancers.UpdateOpts{} + handleAdminStateUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + } +} + +func TestHandleTagsUpdate(t *testing.T) { + testCases := []struct { + name string + newValue []orcv1alpha1.LoadBalancerTag + existingValue []string + expectChange bool + }{ + {name: "Identical empty", newValue: nil, existingValue: nil, expectChange: false}, + {name: "Identical single", newValue: []orcv1alpha1.LoadBalancerTag{"tag1"}, existingValue: []string{"tag1"}, expectChange: false}, + {name: "Identical multiple", newValue: []orcv1alpha1.LoadBalancerTag{"tag1", "tag2"}, existingValue: []string{"tag1", "tag2"}, expectChange: false}, + {name: "Identical different order", newValue: []orcv1alpha1.LoadBalancerTag{"tag2", "tag1"}, existingValue: []string{"tag1", "tag2"}, expectChange: false}, + {name: "Different add tag", newValue: []orcv1alpha1.LoadBalancerTag{"tag1", "tag2"}, existingValue: []string{"tag1"}, expectChange: true}, + {name: "Different remove tag", newValue: []orcv1alpha1.LoadBalancerTag{"tag1"}, existingValue: []string{"tag1", "tag2"}, expectChange: true}, + {name: "Different replace tag", newValue: []orcv1alpha1.LoadBalancerTag{"tag1", "tag3"}, existingValue: []string{"tag1", "tag2"}, expectChange: true}, + {name: "Add tags to empty", newValue: []orcv1alpha1.LoadBalancerTag{"tag1"}, existingValue: nil, expectChange: true}, + {name: "Remove all tags", newValue: nil, existingValue: []string{"tag1"}, expectChange: true}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.LoadBalancerResourceSpec{Tags: tt.newValue} + osResource := &osResourceT{Tags: tt.existingValue} + + updateOpts := loadbalancers.UpdateOpts{} + handleTagsUpdate(&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 index 492aa5a7..c8dde600 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, ) @@ -111,7 +111,7 @@ var projectDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.LoadB finalizer, externalObjectFieldOwner, ) -var vipNetworkImportDependency = dependency.NewDependency[*orcv1alpha1.LoadBalancerList, *orcv1alpha1.VipNetwork]( +var vipNetworkImportDependency = dependency.NewDependency[*orcv1alpha1.LoadBalancerList, *orcv1alpha1.Network]( "spec.import.filter.vipNetworkRef", func(loadbalancer *orcv1alpha1.LoadBalancer) []string { resource := loadbalancer.Spec.Import @@ -133,7 +133,7 @@ var projectImportDependency = dependency.NewDependency[*orcv1alpha1.LoadBalancer }, ) -var vipSubnetImportDependency = dependency.NewDependency[*orcv1alpha1.LoadBalancerList, *orcv1alpha1.VipSubnet]( +var vipSubnetImportDependency = dependency.NewDependency[*orcv1alpha1.LoadBalancerList, *orcv1alpha1.Subnet]( "spec.import.filter.vipSubnetRef", func(loadbalancer *orcv1alpha1.LoadBalancer) []string { resource := loadbalancer.Spec.Import @@ -144,7 +144,7 @@ var vipSubnetImportDependency = dependency.NewDependency[*orcv1alpha1.LoadBalanc }, ) -var vipPortImportDependency = dependency.NewDependency[*orcv1alpha1.LoadBalancerList, *orcv1alpha1.VipPort]( +var vipPortImportDependency = dependency.NewDependency[*orcv1alpha1.LoadBalancerList, *orcv1alpha1.Port]( "spec.import.filter.vipPortRef", func(loadbalancer *orcv1alpha1.LoadBalancer) []string { resource := loadbalancer.Spec.Import @@ -223,20 +223,20 @@ func (c loadbalancerReconcilerConstructor) SetupWithManager(ctx context.Context, builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), ). // A second watch is necessary because we need a different handler that omits deletion guards - Watches(&orcv1alpha1.VipNetwork{}, vipNetworkImportWatchEventHandler, - builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.VipNetwork{})), + Watches(&orcv1alpha1.Network{}, vipNetworkImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Network{})), ). // A second watch is necessary because we need a different handler that omits deletion guards Watches(&orcv1alpha1.Project{}, projectImportWatchEventHandler, builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), ). // A second watch is necessary because we need a different handler that omits deletion guards - Watches(&orcv1alpha1.VipSubnet{}, vipSubnetImportWatchEventHandler, - builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.VipSubnet{})), + Watches(&orcv1alpha1.Subnet{}, vipSubnetImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Subnet{})), ). // A second watch is necessary because we need a different handler that omits deletion guards - Watches(&orcv1alpha1.VipPort{}, vipPortImportWatchEventHandler, - builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.VipPort{})), + Watches(&orcv1alpha1.Port{}, vipPortImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Port{})), ). For(&orcv1alpha1.LoadBalancer{}) diff --git a/internal/controllers/loadbalancer/status.go b/internal/controllers/loadbalancer/status.go index dbf0b654..df084113 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 51a73c32..95ed0643 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,11 @@ status: resource: name: loadbalancer-create-full-override description: LoadBalancer from "create full" test - # TODO(scaffolding): Add all fields the resource supports + adminStateUp: true + provisioningStatus: ACTIVE + tags: + - tag1 + - tag2 conditions: - type: Available status: "True" @@ -35,19 +39,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: + # Dynamic checks that require CEL - cross-resource comparisons - 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 != ''" 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 1f3d9fd6..93f84f66 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,18 @@ metadata: name: loadbalancer-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created - cloudName: openstack + # using openstack-admin to create the LB in another project + cloudName: openstack-admin 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 9230c2c7..e59fe853 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-create-minimal/00-assert.yaml @@ -6,7 +6,7 @@ metadata: status: resource: name: loadbalancer-create-minimal - # TODO(scaffolding): Add all fields the resource supports + provisioningStatus: ACTIVE conditions: - type: Available status: "True" @@ -22,6 +22,12 @@ resourceRefs: kind: LoadBalancer name: loadbalancer-create-minimal ref: loadbalancer + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Subnet + name: loadbalancer-create-minimal + ref: subnet assertAll: + # Dynamic checks that require CEL - cross-resource comparisons and non-empty checks - celExpr: "loadbalancer.status.id != ''" - # TODO(scaffolding): Add more checks + - celExpr: "loadbalancer.status.resource.vipSubnetID == subnet.status.id" + - celExpr: "loadbalancer.status.resource.vipAddress != ''" 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 750dbd63..d527aa28 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 3b21fdb8..86647066 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/00-assert.yaml @@ -1,4 +1,31 @@ --- +# Wait for infrastructure to be ready first +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: loadbalancer-dependency-infra +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: loadbalancer-dependency-subnet +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer metadata: @@ -61,21 +88,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 3410659c..14046fb6 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,60 +1,65 @@ - --- +# Create network/subnet infrastructure for tests that need existing subnet apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LoadBalancer +kind: Network metadata: - name: loadbalancer-dependency-no-subnet + name: loadbalancer-dependency-infra +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + 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 resource: - subnetRef: loadbalancer-dependency - # TODO(scaffolding): Add the necessary fields to create the resource + networkRef: loadbalancer-dependency-infra + ipVersion: 4 + cidr: 10.0.99.0/24 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer metadata: - name: loadbalancer-dependency-no-network + 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: - networkRef: loadbalancer-dependency - # TODO(scaffolding): Add the necessary fields to create the resource + vipSubnetRef: loadbalancer-dependency --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer metadata: - name: loadbalancer-dependency-no-port + 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: - portRef: loadbalancer-dependency - # TODO(scaffolding): Add the necessary fields to create the resource + vipNetworkRef: loadbalancer-dependency --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer metadata: - name: loadbalancer-dependency-no-flavor + 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: - 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 +67,13 @@ metadata: name: loadbalancer-dependency-no-project spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created - cloudName: openstack + # using openstack-admin to create the LB in another project + cloudName: openstack-admin 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 +81,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 5db640da..ec74c08e 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 db46fcfd..9b2f2387 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,36 @@ 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 +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: 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 9f9aa44f..85000071 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 601628e3..869b3d36 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-dependency/02-delete-dependencies.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-dependency/02-delete-dependencies.yaml @@ -5,11 +5,13 @@ 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 + - command: kubectl delete network loadbalancer-dependency-infra --wait=false namespaced: true - - command: kubectl delete flavor loadbalancer-dependency --wait=false + - command: kubectl delete port loadbalancer-dependency --wait=false namespaced: true - command: kubectl delete project 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 index c87e7cb8..26f0781b 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 7fe29cc2..b4eb49b0 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-dependency/00-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/00-assert.yaml index c084e235..5f28dade 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/00-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/00-assert.yaml @@ -7,17 +7,17 @@ status: conditions: - type: Available message: |- - Waiting for VipNetwork/loadbalancer-import-dependency to be ready + Waiting for Network/loadbalancer-import-dependency to be ready Waiting for Project/loadbalancer-import-dependency to be ready - Waiting for VipSubnet/loadbalancer-import-dependency to be ready - Waiting for VipPort/loadbalancer-import-dependency to be ready + Waiting for Subnet/loadbalancer-import-dependency to be ready + Waiting for Port/loadbalancer-import-dependency to be ready status: "False" reason: Progressing - type: Progressing message: |- - Waiting for VipNetwork/loadbalancer-import-dependency to be ready + Waiting for Network/loadbalancer-import-dependency to be ready Waiting for Project/loadbalancer-import-dependency to be ready - Waiting for VipSubnet/loadbalancer-import-dependency to be ready - Waiting for VipPort/loadbalancer-import-dependency to be ready + Waiting for Subnet/loadbalancer-import-dependency to be ready + Waiting for Port/loadbalancer-import-dependency to be ready status: "True" reason: Progressing diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/00-import-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/00-import-resource.yaml index 0f94ea83..c236d41b 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/00-import-resource.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/00-import-resource.yaml @@ -1,16 +1,17 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: VipNetwork +kind: Network metadata: name: loadbalancer-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: filter: name: loadbalancer-import-dependency-external + projectRef: loadbalancer-import-dependency --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project @@ -18,7 +19,7 @@ metadata: name: loadbalancer-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: @@ -26,30 +27,32 @@ spec: name: loadbalancer-import-dependency-external --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: VipSubnet +kind: Subnet metadata: name: loadbalancer-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: filter: name: loadbalancer-import-dependency-external + projectRef: loadbalancer-import-dependency --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: VipPort +kind: Port metadata: name: loadbalancer-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: filter: name: loadbalancer-import-dependency-external + projectRef: loadbalancer-import-dependency --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer @@ -57,7 +60,7 @@ metadata: name: loadbalancer-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/01-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/01-assert.yaml index 5f40565b..18e4398f 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/01-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/01-assert.yaml @@ -22,17 +22,17 @@ status: conditions: - type: Available message: |- - Waiting for VipNetwork/loadbalancer-import-dependency to be ready + Waiting for Network/loadbalancer-import-dependency to be ready Waiting for Project/loadbalancer-import-dependency to be ready - Waiting for VipSubnet/loadbalancer-import-dependency to be ready - Waiting for VipPort/loadbalancer-import-dependency to be ready + Waiting for Subnet/loadbalancer-import-dependency to be ready + Waiting for Port/loadbalancer-import-dependency to be ready status: "False" reason: Progressing - type: Progressing message: |- - Waiting for VipNetwork/loadbalancer-import-dependency to be ready + Waiting for Network/loadbalancer-import-dependency to be ready Waiting for Project/loadbalancer-import-dependency to be ready - Waiting for VipSubnet/loadbalancer-import-dependency to be ready - Waiting for VipPort/loadbalancer-import-dependency to be ready + Waiting for Subnet/loadbalancer-import-dependency to be ready + Waiting for Port/loadbalancer-import-dependency to be ready status: "True" reason: Progressing diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/01-create-trap-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/01-create-trap-resource.yaml index 1c28e72b..9a89f451 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/01-create-trap-resource.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/01-create-trap-resource.yaml @@ -1,55 +1,57 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: VipNetwork +kind: Project metadata: name: loadbalancer-import-dependency-not-this-one 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 -kind: Project +kind: Network metadata: name: loadbalancer-import-dependency-not-this-one 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: {} + resource: + projectRef: loadbalancer-import-dependency-not-this-one --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: VipSubnet +kind: Subnet metadata: name: loadbalancer-import-dependency-not-this-one 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: {} + resource: + networkRef: loadbalancer-import-dependency-not-this-one + projectRef: loadbalancer-import-dependency-not-this-one + ipVersion: 4 + cidr: 10.200.0.0/24 --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: VipPort +kind: Port metadata: name: loadbalancer-import-dependency-not-this-one 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: {} + resource: + networkRef: loadbalancer-import-dependency-not-this-one + projectRef: loadbalancer-import-dependency-not-this-one + addresses: + - subnetRef: loadbalancer-import-dependency-not-this-one + ip: 10.200.0.10 --- # This `loadbalancer-import-dependency-not-this-one` should not be picked by the import filter apiVersion: openstack.k-orc.cloud/v1alpha1 @@ -58,13 +60,10 @@ metadata: name: loadbalancer-import-dependency-not-this-one 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 resource: - vipNetworkRef: loadbalancer-import-dependency-not-this-one - projectRef: loadbalancer-import-dependency-not-this-one - vipSubnetRef: loadbalancer-import-dependency-not-this-one vipPortRef: loadbalancer-import-dependency-not-this-one - # TODO(scaffolding): Add the necessary fields to create the resource + vipSubnetRef: loadbalancer-import-dependency-not-this-one + projectRef: loadbalancer-import-dependency-not-this-one diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/02-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/02-assert.yaml index bb1dd685..0e3edc29 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/02-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/02-assert.yaml @@ -11,27 +11,27 @@ resourceRefs: name: loadbalancer-import-dependency-not-this-one ref: loadbalancer2 - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: VipNetwork + kind: Network name: loadbalancer-import-dependency - ref: vipNetwork + ref: network - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project name: loadbalancer-import-dependency ref: project - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: VipSubnet + kind: Subnet name: loadbalancer-import-dependency - ref: vipSubnet + ref: subnet - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: VipPort + kind: Port name: loadbalancer-import-dependency - ref: vipPort + ref: port assertAll: - celExpr: "loadbalancer1.status.id != loadbalancer2.status.id" - - celExpr: "loadbalancer1.status.resource.vipNetworkID == vipNetwork.status.id" + - celExpr: "loadbalancer1.status.resource.vipNetworkID == network.status.id" - celExpr: "loadbalancer1.status.resource.projectID == project.status.id" - - celExpr: "loadbalancer1.status.resource.vipSubnetID == vipSubnet.status.id" - - celExpr: "loadbalancer1.status.resource.vipPortID == vipPort.status.id" + - celExpr: "loadbalancer1.status.resource.vipSubnetID == subnet.status.id" + - celExpr: "loadbalancer1.status.resource.vipPortID == port.status.id" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/02-create-resource.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/02-create-resource.yaml index 1d5ff803..6044ebec 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/02-create-resource.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/02-create-resource.yaml @@ -1,55 +1,57 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: VipNetwork +kind: Project metadata: name: loadbalancer-import-dependency-external 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 -kind: Project +kind: Network metadata: name: loadbalancer-import-dependency-external 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: {} + resource: + projectRef: loadbalancer-import-dependency-external --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: VipSubnet +kind: Subnet metadata: name: loadbalancer-import-dependency-external 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: {} + resource: + networkRef: loadbalancer-import-dependency-external + projectRef: loadbalancer-import-dependency-external + ipVersion: 4 + cidr: 10.201.0.0/24 --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: VipPort +kind: Port metadata: name: loadbalancer-import-dependency-external 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: {} + resource: + networkRef: loadbalancer-import-dependency-external + projectRef: loadbalancer-import-dependency-external + addresses: + - subnetRef: loadbalancer-import-dependency-external + ip: 10.201.0.10 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer @@ -57,13 +59,10 @@ metadata: name: loadbalancer-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: - vipNetworkRef: loadbalancer-import-dependency-external - projectRef: loadbalancer-import-dependency-external - vipSubnetRef: loadbalancer-import-dependency-external vipPortRef: loadbalancer-import-dependency-external - # TODO(scaffolding): Add the necessary fields to create the resource + vipSubnetRef: loadbalancer-import-dependency-external + projectRef: loadbalancer-import-dependency-external diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/03-assert.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/03-assert.yaml index 8a3a6bcc..0ff111fc 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/03-assert.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/03-assert.yaml @@ -2,11 +2,11 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert commands: -- script: "! kubectl get vipnetwork loadbalancer-import-dependency --namespace $NAMESPACE" +- script: "! kubectl get network loadbalancer-import-dependency --namespace $NAMESPACE" skipLogOutput: true - script: "! kubectl get project loadbalancer-import-dependency --namespace $NAMESPACE" skipLogOutput: true -- script: "! kubectl get vipsubnet loadbalancer-import-dependency --namespace $NAMESPACE" +- script: "! kubectl get subnet loadbalancer-import-dependency --namespace $NAMESPACE" skipLogOutput: true -- script: "! kubectl get vipport loadbalancer-import-dependency --namespace $NAMESPACE" +- script: "! kubectl get port loadbalancer-import-dependency --namespace $NAMESPACE" skipLogOutput: true diff --git a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/03-delete-import-dependencies.yaml index 06db478c..e6be2bd7 100644 --- a/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/03-delete-import-dependencies.yaml +++ b/internal/controllers/loadbalancer/tests/loadbalancer-import-dependency/03-delete-import-dependencies.yaml @@ -3,11 +3,11 @@ apiVersion: kuttl.dev/v1beta1 kind: TestStep commands: # We should be able to delete the import dependencies - - command: kubectl delete vipnetwork loadbalancer-import-dependency + - command: kubectl delete network loadbalancer-import-dependency namespaced: true - command: kubectl delete project loadbalancer-import-dependency namespaced: true - - command: kubectl delete vipsubnet loadbalancer-import-dependency + - command: kubectl delete subnet loadbalancer-import-dependency namespaced: true - - command: kubectl delete vipport loadbalancer-import-dependency + - command: kubectl delete port loadbalancer-import-dependency namespaced: true 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 310f9510..3f41cc65 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 37ab64de..94cf2dd6 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 b5f3c6de..93c311ae 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 aa4e17c0..d7d034bd 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 fea259b6..6b4f82a3 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 7678b3e4..17e021a1 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 5c55a3b6..a0ca7b24 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 bc7dffe8..554c1157 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 b0a66b49..cfa04301 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 045711ee..5d74c652 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 9cd82b42..bdfe8f07 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 4b0cebf2..b8fc612d 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 82a2efd4..bd430a3d 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: "!has(loadbalancer.status.resource.tags)" --- 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 00000000..f4f51793 --- /dev/null +++ b/internal/controllers/loadbalancer/zz_generated.adapter.go @@ -0,0 +1,88 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright 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 00000000..cc3bdf8d --- /dev/null +++ b/internal/controllers/loadbalancer/zz_generated.controller.go @@ -0,0 +1,45 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright 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 d2857458..e7ec912c 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 0da3b97b..d3d3c73b 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 00000000..aae159cd --- /dev/null +++ b/internal/osclients/mock/loadbalancer.go @@ -0,0 +1,131 @@ +/* +Copyright 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 ef959fae..61c46056 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 65670ba6..f1cf1a68 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 7da50dc8..27d799b8 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/kuttl-test.yaml b/kuttl-test.yaml index d499782e..b7adad88 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 00000000..0fb8d2f2 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancer.go @@ -0,0 +1,281 @@ +/* +Copyright 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 00000000..d5c92a62 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerfilter.go @@ -0,0 +1,159 @@ +/* +Copyright 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 00000000..df3c50e1 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerimport.go @@ -0,0 +1,48 @@ +/* +Copyright 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 00000000..064e29e6 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerresourcespec.go @@ -0,0 +1,144 @@ +/* +Copyright 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 00000000..9da0447f --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerresourcestatus.go @@ -0,0 +1,158 @@ +/* +Copyright 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 00000000..db531a01 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerspec.go @@ -0,0 +1,79 @@ +/* +Copyright 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 00000000..28c977f4 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/loadbalancerstatus.go @@ -0,0 +1,66 @@ +/* +Copyright 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 6a989754..98476203 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 478b73a0..5ede314a 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 4317c8aa..2dd876e2 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 595446f0..51690611 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 00000000..8e50b630 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_loadbalancer.go @@ -0,0 +1,53 @@ +/* +Copyright 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 558e399d..9b9ea5c5 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 00000000..9437a449 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/loadbalancer.go @@ -0,0 +1,74 @@ +/* +Copyright 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 2e06781a..44ac917d 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 00000000..d47c3788 --- /dev/null +++ b/pkg/clients/informers/externalversions/api/v1alpha1/loadbalancer.go @@ -0,0 +1,102 @@ +/* +Copyright 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 1bb2313e..2a0d4a54 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 1380d037..a5baf2ac 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 00000000..f231564f --- /dev/null +++ b/pkg/clients/listers/api/v1alpha1/loadbalancer.go @@ -0,0 +1,70 @@ +/* +Copyright 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 a36720a0..a1e0619f 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) @@ -1639,10 +1642,182 @@ _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) + #### MAC @@ -1690,6 +1865,7 @@ _Appears in:_ - [GroupSpec](#groupspec) - [ImageSpec](#imagespec) - [KeyPairSpec](#keypairspec) +- [LoadBalancerSpec](#loadbalancerspec) - [NetworkSpec](#networkspec) - [PortSpec](#portspec) - [ProjectSpec](#projectspec) @@ -1724,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 00000000..fcfb510c --- /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 3a2b38ae47357363d64f2ad06bb42988cc061442 Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Sun, 28 Dec 2025 15:43:23 +0200 Subject: [PATCH 4/7] 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 | 293 ++++++++++++++++++ .../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, 2197 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 00000000..88a9cb7d --- /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 3f19b036..f24112e9 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 46915301..b473470a 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 047bace2..e4afe2e8 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 00000000..ed0d69c1 --- /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 00000000..d751bd0e --- /dev/null +++ b/internal/controllers/listener/actuator.go @@ -0,0 +1,293 @@ +/* +Copyright 2025 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package listener + +import ( + "context" + "iter" + "time" + + "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/listeners" + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" + "github.com/k-orc/openstack-resource-controller/v2/internal/logging" + "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" +) + +// OpenStack resource types +type ( + osResourceT = listeners.Listener + + createResourceActuator = interfaces.CreateResourceActuator[orcObjectPT, orcObjectT, filterT, osResourceT] + deleteResourceActuator = interfaces.DeleteResourceActuator[orcObjectPT, orcObjectT, osResourceT] + resourceReconciler = interfaces.ResourceReconciler[orcObjectPT, osResourceT] + helperFactory = interfaces.ResourceHelperFactory[orcObjectPT, orcObjectT, resourceSpecT, filterT, osResourceT] +) +// The frequency to poll when waiting for the resource to become available +const listenerAvailablePollingPeriod = 15 * time.Second +// The frequency to poll when waiting for the resource to be deleted +const listenerDeletingPollingPeriod = 15 * time.Second + +type listenerActuator struct { + osClient osclients.ListenerClient + k8sClient client.Client +} + +var _ createResourceActuator = listenerActuator{} +var _ deleteResourceActuator = listenerActuator{} + +func (listenerActuator) GetResourceID(osResource *osResourceT) string { + return osResource.ID +} + +func (actuator listenerActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) { + resource, err := actuator.osClient.GetListener(ctx, id) + if err != nil { + return nil, progress.WrapError(err) + } + return resource, nil +} + +func (actuator listenerActuator) ListOSResourcesForAdoption(ctx context.Context, orcObject orcObjectPT) (iter.Seq2[*osResourceT, error], bool) { + resourceSpec := orcObject.Spec.Resource + if resourceSpec == nil { + return nil, false + } + + // 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, rs := dependency.FetchDependency[*orcv1alpha1.LoadBalancer, orcv1alpha1.LoadBalancer]( + ctx, actuator.k8sClient, obj.Namespace, + filter.LoadBalancerRef, "LoadBalancer", + func(dep *orcv1alpha1.LoadBalancer) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + + listOpts := listeners.ListOpts{ + Name: string(ptr.Deref(filter.Name, "")), + Description: string(ptr.Deref(filter.Description, "")), + LoadBalancerID: ptr.Deref(loadBalancer.Status.ID, ""), + // TODO(scaffolding): Add more import filters + } + + return actuator.osClient.ListListeners(ctx, listOpts), reconcileStatus +} + +func (actuator listenerActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) { + resource := obj.Spec.Resource + + if resource == nil { + // Should have been caught by API validation + return nil, progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Creation requested, but spec.resource is not set")) + } + var reconcileStatus progress.ReconcileStatus + + var loadBalancerID string + loadBalancer, loadBalancerDepRS := loadBalancerDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.LoadBalancer) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(loadBalancerDepRS) + if loadBalancer != nil { + loadBalancerID = ptr.Deref(loadBalancer.Status.ID, "") + } + + 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 00000000..a9503748 --- /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 00000000..c2033405 --- /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 00000000..3bb126b6 --- /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 00000000..5855c57a --- /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 00000000..34e0685d --- /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 00000000..045711ee --- /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 00000000..f208be34 --- /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 00000000..5d1a5aef --- /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 00000000..a5bea929 --- /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 00000000..045711ee --- /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 00000000..d7c78a79 --- /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 00000000..1620791b --- /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 00000000..16cad70a --- /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 00000000..4fccca8a --- /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 00000000..e1079834 --- /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 00000000..045711ee --- /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 00000000..bf3067c0 --- /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 00000000..a273f271 --- /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 00000000..3bb51a7d --- /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 00000000..cb0a379b --- /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 00000000..1a746f25 --- /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 00000000..f46d240f --- /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 00000000..cff5a360 --- /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 00000000..51f43785 --- /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 00000000..06d36cc7 --- /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 00000000..045711ee --- /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 00000000..989d8c22 --- /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 00000000..d7692c54 --- /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 00000000..48fd24a8 --- /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 00000000..199f7923 --- /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 00000000..ef8f4923 --- /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 00000000..072a3ec1 --- /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 00000000..395312ed --- /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 00000000..f2a5c653 --- /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 00000000..9af528e4 --- /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 00000000..46649904 --- /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 00000000..c403c332 --- /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 00000000..045711ee --- /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 00000000..2dcb9dc2 --- /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 00000000..d241a7fc --- /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 00000000..5e40fed6 --- /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 00000000..a6b5f4de --- /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 00000000..1020515d --- /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 00000000..045711ee --- /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 00000000..c20bb753 --- /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 00000000..c5d457bd --- /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 00000000..9652ba75 --- /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 00000000..08d1a67c --- /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 00000000..190eaba6 --- /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 00000000..0878b92e --- /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 00000000..f69a6163 --- /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 00000000..045711ee --- /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 00000000..96b13f91 --- /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 00000000..f700fd35 --- /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 00000000..5e7c9942 --- /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 00000000..2c6c253f --- /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 00000000..d9d45390 --- /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 00000000..848e93a2 --- /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 a1e0619f..4bc720c1 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 7bbb64e420cfa5c771133d951e94c11ad72d7dbb Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Sun, 28 Dec 2025 10:58:55 +0200 Subject: [PATCH 5/7] implement octavia listener controller implement listener controller for octavia loadbalancer service. this contoller is a controller of its own to allow proper importing of loadbalancers --- 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 | 122 ++- internal/controllers/listener/controller.go | 21 - internal/controllers/listener/status.go | 67 +- .../tests/listener-create-full/00-assert.yaml | 14 +- .../00-create-resource.yaml | 43 +- .../listener-create-minimal/00-assert.yaml | 6 +- .../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 | 24 +- .../listener-update/00-minimal-resource.yaml | 18 +- .../listener-update/00-prerequisites.yaml | 37 + .../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 ++++++ 70 files changed, 4842 insertions(+), 310 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 d3f41ffc..b56b5913 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 88a9cb7d..6cf3ca03 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 f24112e9..66e27af5 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 00000000..34cc6afa --- /dev/null +++ b/api/v1alpha1/zz_generated.listener-resource.go @@ -0,0 +1,177 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright 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 0b761fe9..995010bd 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 b473470a..8078d636 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 ecd71a16..a4ad2fae 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -163,6 +163,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 00000000..75bb29f0 --- /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 8012052f..bc9e50c1 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 075c016f..085af8a7 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 ed0d69c1..f3c7081e 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 d751bd0e..8f878a69 100644 --- a/internal/controllers/listener/actuator.go +++ b/internal/controllers/listener/actuator.go @@ -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,22 +78,16 @@ 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), + Protocol: string(resourceSpec.Protocol), + ProtocolPort: int(resourceSpec.ProtocolPort), } 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, rs := dependency.FetchDependency[*orcv1alpha1.LoadBalancer, orcv1alpha1.LoadBalancer]( @@ -107,9 +103,7 @@ func (actuator listenerActuator) ListOSResourcesForImport(ctx context.Context, o listOpts := listeners.ListOpts{ Name: string(ptr.Deref(filter.Name, "")), - Description: string(ptr.Deref(filter.Description, "")), - LoadBalancerID: ptr.Deref(loadBalancer.Status.ID, ""), - // TODO(scaffolding): Add more import filters + LoadbalancerID: ptr.Deref(loadBalancer.Status.ID, ""), } return actuator.osClient.ListListeners(ctx, listOpts), reconcileStatus @@ -126,42 +120,74 @@ 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) if err != nil { - // We should require the spec to be updated before retrying a create which returned a conflict + // 409 Conflict typically means the LoadBalancer is in a pending state (immutable). + // Wait for it to become available and retry. + if orcerrors.IsConflict(err) { + return nil, progress.WaitingOnOpenStack(progress.WaitingOnReady, listenerAvailablePollingPeriod) + } if !orcerrors.IsRetryable(err) { err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) } @@ -172,10 +198,22 @@ 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 { + switch resource.ProvisioningStatus { + case ListenerProvisioningStatusPendingDelete: + return progress.WaitingOnOpenStack(progress.WaitingOnReady, listenerDeletingPollingPeriod) + case ListenerProvisioningStatusPendingCreate, ListenerProvisioningStatusPendingUpdate: + // We can't delete a loadbalancer that's in a pending state, so we need to wait for it to become ACTIVE return progress.WaitingOnOpenStack(progress.WaitingOnReady, listenerDeletingPollingPeriod) } - return progress.WrapError(actuator.osClient.DeleteListener(ctx, resource.ID)) + + err := actuator.osClient.DeleteListener(ctx, resource.ID) + // 409 Conflict means the loadbalancer is already in PENDING_DELETE state. + // Treat this as success and let the controller poll for deletion completion. + if orcerrors.IsConflict(err) { + return progress.WaitingOnOpenStack(progress.WaitingOnReady, listenerDeletingPollingPeriod) + } + + return progress.WrapError(err) } func (actuator listenerActuator) updateResource(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { @@ -192,8 +230,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 c2033405..a120dba0 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 3bb126b6..15932416 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 5855c57a..66f0b2c4 100644 --- a/internal/controllers/listener/tests/listener-create-full/00-assert.yaml +++ b/internal/controllers/listener/tests/listener-create-full/00-assert.yaml @@ -6,8 +6,11 @@ metadata: status: resource: name: listener-create-full-override - description: Listener from "create full" test - # TODO(scaffolding): Add all fields the resource supports + description: Listener from create full test + protocol: HTTPS + protocolPort: 443 + connectionLimit: 1000 + provisioningStatus: ACTIVE conditions: - type: Available status: "True" @@ -27,12 +30,7 @@ resourceRefs: kind: LoadBalancer name: listener-create-full ref: loadBalancer - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Pool - name: listener-create-full - ref: pool assertAll: + # Dynamic checks that require CEL - 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 index 34e0685d..124257b3 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 5d1a5aef..bf71294c 100644 --- a/internal/controllers/listener/tests/listener-create-minimal/00-assert.yaml +++ b/internal/controllers/listener/tests/listener-create-minimal/00-assert.yaml @@ -6,7 +6,9 @@ metadata: status: resource: name: listener-create-minimal - # TODO(scaffolding): Add all fields the resource supports + protocol: HTTP + protocolPort: 80 + provisioningStatus: ACTIVE conditions: - type: Available status: "True" @@ -27,6 +29,6 @@ resourceRefs: name: listener-create-minimal ref: loadBalancer assertAll: + # Dynamic checks that require CEL - 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 index a5bea929..e06345a8 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 4fccca8a..d7fd1357 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 e1079834..f2c20272 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 bf3067c0..f88bb904 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 a273f271..f6ddcf70 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 3bb51a7d..912a8ef9 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 cb0a379b..3790fb04 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 1a746f25..803c4a6a 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 f46d240f..7b812616 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 d7692c54..8f68fd02 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 199f7923..628afa93 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 c403c332..3a4ccdca 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 d241a7fc..f01bb2bf 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 1020515d..b3a0b8cc 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 c20bb753..87a8ab42 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 c5d457bd..e5529621 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 9652ba75..9dd988f2 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 08d1a67c..6c907e53 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 0878b92e..886b317e 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,8 @@ metadata: status: resource: name: listener-update - # TODO(scaffolding): Add matches for more fields + protocol: HTTP + protocolPort: 8080 conditions: - type: Available status: "True" @@ -24,3 +15,14 @@ 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: + # Dynamic checks that require CEL - checking for absence or empty + - celExpr: "!has(listener.status.resource.description) || listener.status.resource.description == ''" 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 f69a6163..89516b2e 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,14 @@ --- 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 + protocol: HTTP + protocolPort: 8080 diff --git a/internal/controllers/listener/tests/listener-update/00-prerequisites.yaml b/internal/controllers/listener/tests/listener-update/00-prerequisites.yaml index 045711ee..f2d149e1 100644 --- a/internal/controllers/listener/tests/listener-update/00-prerequisites.yaml +++ b/internal/controllers/listener/tests/listener-update/00-prerequisites.yaml @@ -4,3 +4,40 @@ 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: listener-update +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: listener-update +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: listener-update + ipVersion: 4 + cidr: 10.0.2.0/24 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: listener-update +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + vipSubnetRef: listener-update \ No newline at end of file diff --git a/internal/controllers/listener/tests/listener-update/01-assert.yaml b/internal/controllers/listener/tests/listener-update/01-assert.yaml index 96b13f91..0c466cc2 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 f700fd35..17e3f59e 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 5e7c9942..88371135 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 00000000..d53f1aac --- /dev/null +++ b/internal/controllers/listener/zz_generated.adapter.go @@ -0,0 +1,88 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright 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 00000000..f32cf3a0 --- /dev/null +++ b/internal/controllers/listener/zz_generated.controller.go @@ -0,0 +1,45 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright 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 d3d3c73b..ae2e1fa2 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 00000000..739da956 --- /dev/null +++ b/internal/osclients/mock/listener.go @@ -0,0 +1,131 @@ +/* +Copyright 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 61c46056..e36cbc95 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 f1cf1a68..85500f66 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 27d799b8..736bd21e 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 b7adad88..44deb3f0 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 00000000..c8f35eff --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/listener.go @@ -0,0 +1,281 @@ +/* +Copyright 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 00000000..25f608a6 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/listenerfilter.go @@ -0,0 +1,123 @@ +/* +Copyright 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 00000000..33f76290 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/listenerhsts.go @@ -0,0 +1,57 @@ +/* +Copyright 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 00000000..7124e351 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/listenerimport.go @@ -0,0 +1,48 @@ +/* +Copyright 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 00000000..78257e04 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/listenerresourcespec.go @@ -0,0 +1,266 @@ +/* +Copyright 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 00000000..f21272cb --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/listenerresourcestatus.go @@ -0,0 +1,193 @@ +/* +Copyright 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 00000000..38b6cf26 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/listenerspec.go @@ -0,0 +1,79 @@ +/* +Copyright 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 00000000..12b222ef --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/listenerstatus.go @@ -0,0 +1,66 @@ +/* +Copyright 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 98476203..a014f715 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 5ede314a..468b1284 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 2dd876e2..6efb7534 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 51690611..c883425f 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 00000000..e067121d --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_listener.go @@ -0,0 +1,51 @@ +/* +Copyright 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 9b9ea5c5..5c56b529 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 00000000..97a4a18a --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/listener.go @@ -0,0 +1,74 @@ +/* +Copyright 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 44ac917d..d70fcbb1 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 00000000..813d8e84 --- /dev/null +++ b/pkg/clients/informers/externalversions/api/v1alpha1/listener.go @@ -0,0 +1,102 @@ +/* +Copyright 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 2a0d4a54..91df84e0 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 a5baf2ac..5a0aec77 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 00000000..140d1698 --- /dev/null +++ b/pkg/clients/listers/api/v1alpha1/listener.go @@ -0,0 +1,70 @@ +/* +Copyright 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 4bc720c1..826d8961 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) From e8282dbff7891bfba149ab9eb98d981dcb1b4691 Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Tue, 30 Dec 2025 14:08:08 +0200 Subject: [PATCH 6/7] Scaffolding for the lbpool controller $ go run ./cmd/scaffold-controller -interactive=false \ -kind=LBPool \ -gophercloud-client=NewLoadBalancerV2 \ -gophercloud-module=github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/pools \ -gophercloud-type=Pool \ -openstack-json-object=pool \ -available-polling-period=15 \ -deleting-polling-period=15 \ -optional-create-dependency=LoadBalancer \ -optional-create-dependency=Listener \ -optional-create-dependency=Project \ -import-dependency=LoadBalancer \ -import-dependency=Listener \ -import-dependency=Project --- api/v1alpha1/lbpool_types.go | 116 +++++++ api/v1alpha1/zz_generated.deepcopy.go | 95 +++++ cmd/models-schema/zz_generated.openapi.go | 147 ++++++++ config/rbac/role.yaml | 2 + config/samples/openstack_v1alpha1_lbpool.yaml | 14 + internal/controllers/lbpool/actuator.go | 325 ++++++++++++++++++ internal/controllers/lbpool/actuator_test.go | 119 +++++++ internal/controllers/lbpool/controller.go | 198 +++++++++++ internal/controllers/lbpool/status.go | 78 +++++ .../tests/lbpool-create-full/00-assert.yaml | 43 +++ .../00-create-resource.yaml | 57 +++ .../tests/lbpool-create-full/00-secret.yaml | 6 + .../lbpool/tests/lbpool-create-full/README.md | 11 + .../lbpool-create-minimal/00-assert.yaml | 27 ++ .../00-create-resource.yaml | 14 + .../lbpool-create-minimal/00-secret.yaml | 6 + .../lbpool-create-minimal/01-assert.yaml | 11 + .../01-delete-secret.yaml | 7 + .../tests/lbpool-create-minimal/README.md | 15 + .../tests/lbpool-dependency/00-assert.yaml | 60 ++++ .../00-create-resources-missing-deps.yaml | 56 +++ .../tests/lbpool-dependency/00-secret.yaml | 6 + .../tests/lbpool-dependency/01-assert.yaml | 60 ++++ .../01-create-dependencies.yaml | 45 +++ .../tests/lbpool-dependency/02-assert.yaml | 29 ++ .../02-delete-dependencies.yaml | 13 + .../tests/lbpool-dependency/03-assert.yaml | 13 + .../03-delete-resources.yaml | 16 + .../lbpool/tests/lbpool-dependency/README.md | 21 ++ .../lbpool-import-dependency/00-assert.yaml | 21 ++ .../00-import-resource.yaml | 54 +++ .../lbpool-import-dependency/00-secret.yaml | 6 + .../lbpool-import-dependency/01-assert.yaml | 36 ++ .../01-create-trap-resource.yaml | 56 +++ .../lbpool-import-dependency/02-assert.yaml | 44 +++ .../02-create-resource.yaml | 55 +++ .../lbpool-import-dependency/03-assert.yaml | 10 + .../03-delete-import-dependencies.yaml | 11 + .../lbpool-import-dependency/04-assert.yaml | 6 + .../04-delete-resource.yaml | 7 + .../tests/lbpool-import-dependency/README.md | 29 ++ .../tests/lbpool-import-error/00-assert.yaml | 30 ++ .../00-create-resources.yaml | 28 ++ .../tests/lbpool-import-error/00-secret.yaml | 6 + .../tests/lbpool-import-error/01-assert.yaml | 15 + .../01-import-resource.yaml | 13 + .../tests/lbpool-import-error/README.md | 13 + .../lbpool/tests/lbpool-import/00-assert.yaml | 15 + .../lbpool-import/00-import-resource.yaml | 15 + .../lbpool/tests/lbpool-import/00-secret.yaml | 6 + .../lbpool/tests/lbpool-import/01-assert.yaml | 34 ++ .../01-create-trap-resource.yaml | 17 + .../lbpool/tests/lbpool-import/02-assert.yaml | 33 ++ .../lbpool-import/02-create-resource.yaml | 14 + .../lbpool/tests/lbpool-import/README.md | 18 + .../lbpool/tests/lbpool-update/00-assert.yaml | 26 ++ .../lbpool-update/00-minimal-resource.yaml | 14 + .../tests/lbpool-update/00-prerequisites.yaml | 6 + .../lbpool/tests/lbpool-update/01-assert.yaml | 17 + .../lbpool-update/01-updated-resource.yaml | 10 + .../lbpool/tests/lbpool-update/02-assert.yaml | 26 ++ .../lbpool-update/02-reverted-resource.yaml | 7 + .../lbpool/tests/lbpool-update/README.md | 17 + internal/osclients/lbpool.go | 104 ++++++ website/docs/crd-reference.md | 10 + 65 files changed, 2439 insertions(+) create mode 100644 api/v1alpha1/lbpool_types.go create mode 100644 config/samples/openstack_v1alpha1_lbpool.yaml create mode 100644 internal/controllers/lbpool/actuator.go create mode 100644 internal/controllers/lbpool/actuator_test.go create mode 100644 internal/controllers/lbpool/controller.go create mode 100644 internal/controllers/lbpool/status.go create mode 100644 internal/controllers/lbpool/tests/lbpool-create-full/00-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-create-full/00-create-resource.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-create-full/00-secret.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-create-full/README.md create mode 100644 internal/controllers/lbpool/tests/lbpool-create-minimal/00-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-create-minimal/00-create-resource.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-create-minimal/00-secret.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-create-minimal/01-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-create-minimal/01-delete-secret.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-create-minimal/README.md create mode 100644 internal/controllers/lbpool/tests/lbpool-dependency/00-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-dependency/00-create-resources-missing-deps.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-dependency/00-secret.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-dependency/01-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-dependency/01-create-dependencies.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-dependency/02-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-dependency/02-delete-dependencies.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-dependency/03-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-dependency/03-delete-resources.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-dependency/README.md create mode 100644 internal/controllers/lbpool/tests/lbpool-import-dependency/00-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import-dependency/00-import-resource.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import-dependency/00-secret.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import-dependency/01-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import-dependency/01-create-trap-resource.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import-dependency/02-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import-dependency/02-create-resource.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import-dependency/03-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import-dependency/03-delete-import-dependencies.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import-dependency/04-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import-dependency/04-delete-resource.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import-dependency/README.md create mode 100644 internal/controllers/lbpool/tests/lbpool-import-error/00-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import-error/00-create-resources.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import-error/00-secret.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import-error/01-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import-error/01-import-resource.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import-error/README.md create mode 100644 internal/controllers/lbpool/tests/lbpool-import/00-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import/00-import-resource.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import/00-secret.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import/01-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import/01-create-trap-resource.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import/02-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import/02-create-resource.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-import/README.md create mode 100644 internal/controllers/lbpool/tests/lbpool-update/00-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-update/00-minimal-resource.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-update/00-prerequisites.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-update/01-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-update/01-updated-resource.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-update/02-assert.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-update/02-reverted-resource.yaml create mode 100644 internal/controllers/lbpool/tests/lbpool-update/README.md create mode 100644 internal/osclients/lbpool.go diff --git a/api/v1alpha1/lbpool_types.go b/api/v1alpha1/lbpool_types.go new file mode 100644 index 00000000..48968cf0 --- /dev/null +++ b/api/v1alpha1/lbpool_types.go @@ -0,0 +1,116 @@ +/* +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 + +// LBPoolResourceSpec contains the desired state of the resource. +type LBPoolResourceSpec 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. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="loadBalancerRef is immutable" + LoadBalancerRef *KubernetesNameRef `json:"loadBalancerRef,omitempty"` + + // listenerRef is a reference to the ORC Listener which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="listenerRef is immutable" + ListenerRef *KubernetesNameRef `json:"listenerRef,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/pools + // + // 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"` +} + +// LBPoolFilter defines an existing resource by its properties +// +kubebuilder:validation:MinProperties:=1 +type LBPoolFilter 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"` + + // listenerRef is a reference to the ORC Listener which this resource is associated with. + // +optional + ListenerRef *KubernetesNameRef `json:"listenerRef,omitempty"` + + // projectRef is a reference to the ORC Project which this resource is associated with. + // +optional + ProjectRef *KubernetesNameRef `json:"projectRef,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/pools +} + +// LBPoolResourceStatus represents the observed state of the resource. +type LBPoolResourceStatus 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"` + + // listenerID is the ID of the Listener to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + ListenerID string `json:"listenerID,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 Pool structure from + // github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/pools +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 66e27af5..24a7f5cb 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1975,6 +1975,101 @@ 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 *LBPoolFilter) DeepCopyInto(out *LBPoolFilter) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(OpenStackName) + **out = **in + } + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } + if in.LoadBalancerRef != nil { + in, out := &in.LoadBalancerRef, &out.LoadBalancerRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.ListenerRef != nil { + in, out := &in.ListenerRef, &out.ListenerRef + *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 LBPoolFilter. +func (in *LBPoolFilter) DeepCopy() *LBPoolFilter { + if in == nil { + return nil + } + out := new(LBPoolFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LBPoolResourceSpec) DeepCopyInto(out *LBPoolResourceSpec) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(OpenStackName) + **out = **in + } + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } + if in.LoadBalancerRef != nil { + in, out := &in.LoadBalancerRef, &out.LoadBalancerRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.ListenerRef != nil { + in, out := &in.ListenerRef, &out.ListenerRef + *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 LBPoolResourceSpec. +func (in *LBPoolResourceSpec) DeepCopy() *LBPoolResourceSpec { + if in == nil { + return nil + } + out := new(LBPoolResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LBPoolResourceStatus) DeepCopyInto(out *LBPoolResourceStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LBPoolResourceStatus. +func (in *LBPoolResourceStatus) DeepCopy() *LBPoolResourceStatus { + if in == nil { + return nil + } + out := new(LBPoolResourceStatus) + in.DeepCopyInto(out) + 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 diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 8078d636..cb4861ca 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.LBPoolFilter": schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolResourceStatus(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), @@ -3776,6 +3779,150 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairStatus(ref comm } } +func schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LBPoolFilter 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: "", + }, + }, + "listenerRef": { + SchemaProps: spec.SchemaProps{ + Description: "listenerRef is a reference to the ORC Listener 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_LBPoolResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LBPoolResourceSpec 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: "", + }, + }, + "listenerRef": { + SchemaProps: spec.SchemaProps{ + Description: "listenerRef is a reference to the ORC Listener 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_LBPoolResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LBPoolResourceStatus 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: "", + }, + }, + "listenerID": { + SchemaProps: spec.SchemaProps{ + Description: "listenerID is the ID of the Listener 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_Listener(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index e4afe2e8..976ec48f 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -23,6 +23,7 @@ rules: - groups - images - keypairs + - lbpools - listeners - loadbalancers - networks @@ -55,6 +56,7 @@ rules: - groups/status - images/status - keypairs/status + - lbpools/status - listeners/status - loadbalancers/status - networks/status diff --git a/config/samples/openstack_v1alpha1_lbpool.yaml b/config/samples/openstack_v1alpha1_lbpool.yaml new file mode 100644 index 00000000..0e086a77 --- /dev/null +++ b/config/samples/openstack_v1alpha1_lbpool.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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 LBPool + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/lbpool/actuator.go b/internal/controllers/lbpool/actuator.go new file mode 100644 index 00000000..5e08ec7e --- /dev/null +++ b/internal/controllers/lbpool/actuator.go @@ -0,0 +1,325 @@ +/* +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 lbpool + +import ( + "context" + "iter" + "time" + + "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/pools" + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" + "github.com/k-orc/openstack-resource-controller/v2/internal/logging" + "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" +) + +// OpenStack resource types +type ( + osResourceT = pools.Pool + + 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 lbpoolAvailablePollingPeriod = 15 * time.Second +// The frequency to poll when waiting for the resource to be deleted +const lbpoolDeletingPollingPeriod = 15 * time.Second + +type lbpoolActuator struct { + osClient osclients.LBPoolClient + k8sClient client.Client +} + +var _ createResourceActuator = lbpoolActuator{} +var _ deleteResourceActuator = lbpoolActuator{} + +func (lbpoolActuator) GetResourceID(osResource *osResourceT) string { + return osResource.ID +} + +func (actuator lbpoolActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) { + resource, err := actuator.osClient.GetLBPool(ctx, id) + if err != nil { + return nil, progress.WrapError(err) + } + return resource, nil +} + +func (actuator lbpoolActuator) 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 := pools.ListOpts{ + Name: getResourceName(orcObject), + Description: ptr.Deref(resourceSpec.Description, ""), + } + + return actuator.osClient.ListLBPools(ctx, listOpts), true +} + +func (actuator lbpoolActuator) 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, rs := dependency.FetchDependency[*orcv1alpha1.LoadBalancer, orcv1alpha1.LoadBalancer]( + ctx, actuator.k8sClient, obj.Namespace, + filter.LoadBalancerRef, "LoadBalancer", + func(dep *orcv1alpha1.LoadBalancer) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + listener, rs := dependency.FetchDependency[*orcv1alpha1.Listener, orcv1alpha1.Listener]( + ctx, actuator.k8sClient, obj.Namespace, + filter.ListenerRef, "Listener", + func(dep *orcv1alpha1.Listener) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + project, rs := dependency.FetchDependency[*orcv1alpha1.Project, orcv1alpha1.Project]( + ctx, actuator.k8sClient, obj.Namespace, + filter.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + + listOpts := pools.ListOpts{ + Name: string(ptr.Deref(filter.Name, "")), + Description: string(ptr.Deref(filter.Description, "")), + LoadBalancerID: ptr.Deref(loadBalancer.Status.ID, ""), + ListenerID: ptr.Deref(listener.Status.ID, ""), + ProjectID: ptr.Deref(project.Status.ID, ""), + // TODO(scaffolding): Add more import filters + } + + return actuator.osClient.ListLBPools(ctx, listOpts), reconcileStatus +} + +func (actuator lbpoolActuator) 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 + if resource.LoadBalancerRef != nil { + 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 listenerID string + if resource.ListenerRef != nil { + listener, listenerDepRS := listenerDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Listener) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(listenerDepRS) + if listener != nil { + listenerID = ptr.Deref(listener.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 := pools.CreateOpts{ + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + LoadBalancerID: loadBalancerID, + ListenerID: listenerID, + ProjectID: projectID, + // TODO(scaffolding): Add more fields + } + + osResource, err := actuator.osClient.CreateLBPool(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 lbpoolActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { + if resource.Status == PoolStatusDeleting { + return progress.WaitingOnOpenStack(progress.WaitingOnReady, lbpoolDeletingPollingPeriod) + } + return progress.WrapError(actuator.osClient.DeleteLBPool(ctx, resource.ID)) +} + +func (actuator lbpoolActuator) 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 := pools.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.UpdateLBPool(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 pools.UpdateOpts) (bool, error) { + updateOptsMap, err := updateOpts.ToPoolUpdateMap() + if err != nil { + return false, err + } + + updateMap, ok := updateOptsMap["pool"].(map[string]any) + if !ok { + updateMap = make(map[string]any) + } + + return len(updateMap) > 0, nil +} + +func handleNameUpdate(updateOpts *pools.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { + name := getResourceName(obj) + if osResource.Name != name { + updateOpts.Name = &name + } +} + +func handleDescriptionUpdate(updateOpts *pools.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + description := ptr.Deref(resource.Description, "") + if osResource.Description != description { + updateOpts.Description = &description + } +} + +func (actuator lbpoolActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.updateResource, + }, nil +} + +type lbpoolHelperFactory struct{} + +var _ helperFactory = lbpoolHelperFactory{} + +func newActuator(ctx context.Context, orcObject *orcv1alpha1.LBPool, controller interfaces.ResourceController) (lbpoolActuator, 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 lbpoolActuator{}, reconcileStatus + } + + clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject) + if err != nil { + return lbpoolActuator{}, progress.WrapError(err) + } + osClient, err := clientScope.NewLBPoolClient() + if err != nil { + return lbpoolActuator{}, progress.WrapError(err) + } + + return lbpoolActuator{ + osClient: osClient, + k8sClient: controller.GetK8sClient(), + }, nil +} + +func (lbpoolHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { + return lbpoolAdapter{obj} +} + +func (lbpoolHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} + +func (lbpoolHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} diff --git a/internal/controllers/lbpool/actuator_test.go b/internal/controllers/lbpool/actuator_test.go new file mode 100644 index 00000000..57ce344f --- /dev/null +++ b/internal/controllers/lbpool/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 lbpool + +import ( + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/pools" + 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 pools.UpdateOpts + expectChange bool + }{ + { + name: "Empty base opts", + updateOpts: pools.UpdateOpts{}, + expectChange: false, + }, + { + name: "Updated opts", + updateOpts: pools.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.LBPool{} + resource.Name = "object-name" + resource.Spec = orcv1alpha1.LBPoolSpec{ + Resource: &orcv1alpha1.LBPoolResourceSpec{Name: tt.newValue}, + } + osResource := &osResourceT{Name: tt.existingValue} + + updateOpts := pools.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.LBPoolResourceSpec{Description: tt.newValue} + osResource := &osResourceT{Description: tt.existingValue} + + updateOpts := pools.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/lbpool/controller.go b/internal/controllers/lbpool/controller.go new file mode 100644 index 00000000..962ca88d --- /dev/null +++ b/internal/controllers/lbpool/controller.go @@ -0,0 +1,198 @@ +/* +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 lbpool + +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 = "lbpool" + +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=lbpools,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=lbpools/status,verbs=get;update;patch + +type lbpoolReconcilerConstructor struct { + scopeFactory scope.Factory +} + +func New(scopeFactory scope.Factory) interfaces.Controller { + return lbpoolReconcilerConstructor{scopeFactory: scopeFactory} +} + +func (lbpoolReconcilerConstructor) GetName() string { + return controllerName +} + +var loadBalancerDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.LBPoolList, *orcv1alpha1.LoadBalancer]( + "spec.resource.loadBalancerRef", + func(lbpool *orcv1alpha1.LBPool) []string { + resource := lbpool.Spec.Resource + if resource == nil || resource.LoadBalancerRef == nil { + return nil + } + return []string{string(*resource.LoadBalancerRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var listenerDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.LBPoolList, *orcv1alpha1.Listener]( + "spec.resource.listenerRef", + func(lbpool *orcv1alpha1.LBPool) []string { + resource := lbpool.Spec.Resource + if resource == nil || resource.ListenerRef == nil { + return nil + } + return []string{string(*resource.ListenerRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var projectDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.LBPoolList, *orcv1alpha1.Project]( + "spec.resource.projectRef", + func(lbpool *orcv1alpha1.LBPool) []string { + resource := lbpool.Spec.Resource + if resource == nil || resource.ProjectRef == nil { + return nil + } + return []string{string(*resource.ProjectRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var loadBalancerImportDependency = dependency.NewDependency[*orcv1alpha1.LBPoolList, *orcv1alpha1.LoadBalancer]( + "spec.import.filter.loadBalancerRef", + func(lbpool *orcv1alpha1.LBPool) []string { + resource := lbpool.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.LoadBalancerRef == nil { + return nil + } + return []string{string(*resource.Filter.LoadBalancerRef)} + }, +) + +var listenerImportDependency = dependency.NewDependency[*orcv1alpha1.LBPoolList, *orcv1alpha1.Listener]( + "spec.import.filter.listenerRef", + func(lbpool *orcv1alpha1.LBPool) []string { + resource := lbpool.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.ListenerRef == nil { + return nil + } + return []string{string(*resource.Filter.ListenerRef)} + }, +) + +var projectImportDependency = dependency.NewDependency[*orcv1alpha1.LBPoolList, *orcv1alpha1.Project]( + "spec.import.filter.projectRef", + func(lbpool *orcv1alpha1.LBPool) []string { + resource := lbpool.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.ProjectRef == nil { + return nil + } + return []string{string(*resource.Filter.ProjectRef)} + }, +) + +// SetupWithManager sets up the controller with the Manager. +func (c lbpoolReconcilerConstructor) 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 + } + + listenerWatchEventHandler, err := listenerDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + projectWatchEventHandler, err := projectDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + loadBalancerImportWatchEventHandler, err := loadBalancerImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + listenerImportWatchEventHandler, err := listenerImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + projectImportWatchEventHandler, err := projectImportDependency.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.Listener{}, listenerWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Listener{})), + ). + Watches(&orcv1alpha1.Project{}, projectWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), + ). + // 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{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Listener{}, listenerImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Listener{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Project{}, projectImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), + ). + For(&orcv1alpha1.LBPool{}) + + if err := errors.Join( + loadBalancerDependency.AddToManager(ctx, mgr), + listenerDependency.AddToManager(ctx, mgr), + projectDependency.AddToManager(ctx, mgr), + loadBalancerImportDependency.AddToManager(ctx, mgr), + listenerImportDependency.AddToManager(ctx, mgr), + projectImportDependency.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, lbpoolHelperFactory{}, lbpoolStatusWriter{}) + return builder.Complete(&r) +} diff --git a/internal/controllers/lbpool/status.go b/internal/controllers/lbpool/status.go new file mode 100644 index 00000000..6ecab9f5 --- /dev/null +++ b/internal/controllers/lbpool/status.go @@ -0,0 +1,78 @@ +/* +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 lbpool + +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 PoolStatusAvailable = "available" +const PoolStatusInUse = "in-use" +const PoolStatusDeleting = "deleting" + +type lbpoolStatusWriter struct{} + +type objectApplyT = orcapplyconfigv1alpha1.LBPoolApplyConfiguration +type statusApplyT = orcapplyconfigv1alpha1.LBPoolStatusApplyConfiguration + +var _ interfaces.ResourceStatusWriter[*orcv1alpha1.LBPool, *osResourceT, *objectApplyT, *statusApplyT] = lbpoolStatusWriter{} + +func (lbpoolStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT { + return orcapplyconfigv1alpha1.LBPool(name, namespace) +} + +func (lbpoolStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.LBPool, 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 == PoolStatusAvailable || osResource.Status == PoolStatusInUse { + return metav1.ConditionTrue, nil + } + + // Otherwise we should continue to poll + return metav1.ConditionFalse, progress.WaitingOnOpenStack(progress.WaitingOnReady, lbpoolAvailablePollingPeriod) +} + +func (lbpoolStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.LBPoolResourceStatus(). + WithLoadBalancerID(osResource.LoadBalancerID). + WithListenerID(osResource.ListenerID). + WithProjectID(osResource.ProjectID). + WithName(osResource.Name) + + // TODO(scaffolding): add all of the fields supported in the LBPoolResourceStatus 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/lbpool/tests/lbpool-create-full/00-assert.yaml b/internal/controllers/lbpool/tests/lbpool-create-full/00-assert.yaml new file mode 100644 index 00000000..52cb9011 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-create-full/00-assert.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-create-full +status: + resource: + name: lbpool-create-full-override + description: LBPool 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: LBPool + name: lbpool-create-full + ref: lbpool + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LoadBalancer + name: lbpool-create-full + ref: loadBalancer + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Listener + name: lbpool-create-full + ref: listener + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: lbpool-create-full + ref: project +assertAll: + - celExpr: "lbpool.status.id != ''" + - celExpr: "lbpool.status.resource.loadBalancerID == loadBalancer.status.id" + - celExpr: "lbpool.status.resource.listenerID == listener.status.id" + - celExpr: "lbpool.status.resource.projectID == project.status.id" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/lbpool/tests/lbpool-create-full/00-create-resource.yaml b/internal/controllers/lbpool/tests/lbpool-create-full/00-create-resource.yaml new file mode 100644 index 00000000..36d638eb --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-create-full/00-create-resource.yaml @@ -0,0 +1,57 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: lbpool-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: lbpool-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: lbpool-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: LBPool +metadata: + name: lbpool-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: lbpool-create-full-override + description: LBPool from "create full" test + loadBalancerRef: lbpool-create-full + listenerRef: lbpool-create-full + projectRef: lbpool-create-full + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/lbpool/tests/lbpool-create-full/00-secret.yaml b/internal/controllers/lbpool/tests/lbpool-create-full/00-secret.yaml new file mode 100644 index 00000000..045711ee --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-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/lbpool/tests/lbpool-create-full/README.md b/internal/controllers/lbpool/tests/lbpool-create-full/README.md new file mode 100644 index 00000000..8ec81b0a --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-create-full/README.md @@ -0,0 +1,11 @@ +# Create a LBPool with all the options + +## Step 00 + +Create a LBPool 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/lbpool/tests/lbpool-create-minimal/00-assert.yaml b/internal/controllers/lbpool/tests/lbpool-create-minimal/00-assert.yaml new file mode 100644 index 00000000..d6700eb7 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-create-minimal/00-assert.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-create-minimal +status: + resource: + name: lbpool-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: LBPool + name: lbpool-create-minimal + ref: lbpool +assertAll: + - celExpr: "lbpool.status.id != ''" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/lbpool/tests/lbpool-create-minimal/00-create-resource.yaml b/internal/controllers/lbpool/tests/lbpool-create-minimal/00-create-resource.yaml new file mode 100644 index 00000000..dc44f1a4 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-create-minimal/00-create-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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/lbpool/tests/lbpool-create-minimal/00-secret.yaml b/internal/controllers/lbpool/tests/lbpool-create-minimal/00-secret.yaml new file mode 100644 index 00000000..045711ee --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-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/lbpool/tests/lbpool-create-minimal/01-assert.yaml b/internal/controllers/lbpool/tests/lbpool-create-minimal/01-assert.yaml new file mode 100644 index 00000000..a3fcc0d3 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-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/lbpool' in secret.metadata.finalizers" diff --git a/internal/controllers/lbpool/tests/lbpool-create-minimal/01-delete-secret.yaml b/internal/controllers/lbpool/tests/lbpool-create-minimal/01-delete-secret.yaml new file mode 100644 index 00000000..1620791b --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-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/lbpool/tests/lbpool-create-minimal/README.md b/internal/controllers/lbpool/tests/lbpool-create-minimal/README.md new file mode 100644 index 00000000..4e2289ec --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-create-minimal/README.md @@ -0,0 +1,15 @@ +# Create a LBPool with the minimum options + +## Step 00 + +Create a minimal LBPool, 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/lbpool/tests/lbpool-dependency/00-assert.yaml b/internal/controllers/lbpool/tests/lbpool-dependency/00-assert.yaml new file mode 100644 index 00000000..b8dbad21 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-dependency/00-assert.yaml @@ -0,0 +1,60 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-dependency-no-secret +status: + conditions: + - type: Available + message: Waiting for Secret/lbpool-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Secret/lbpool-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-dependency-no-loadbalancer +status: + conditions: + - type: Available + message: Waiting for LoadBalancer/lbpool-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for LoadBalancer/lbpool-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-dependency-no-listener +status: + conditions: + - type: Available + message: Waiting for Listener/lbpool-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Listener/lbpool-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-dependency-no-project +status: + conditions: + - type: Available + message: Waiting for Project/lbpool-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Project/lbpool-dependency to be created + status: "True" + reason: Progressing diff --git a/internal/controllers/lbpool/tests/lbpool-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/lbpool/tests/lbpool-dependency/00-create-resources-missing-deps.yaml new file mode 100644 index 00000000..4fa47c94 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-dependency/00-create-resources-missing-deps.yaml @@ -0,0 +1,56 @@ + +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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: lbpool-dependency + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-dependency-no-listener +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + listenerRef: lbpool-dependency + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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: lbpool-dependency + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-dependency-no-secret +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: lbpool-dependency + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} diff --git a/internal/controllers/lbpool/tests/lbpool-dependency/00-secret.yaml b/internal/controllers/lbpool/tests/lbpool-dependency/00-secret.yaml new file mode 100644 index 00000000..045711ee --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-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/lbpool/tests/lbpool-dependency/01-assert.yaml b/internal/controllers/lbpool/tests/lbpool-dependency/01-assert.yaml new file mode 100644 index 00000000..ca4ab198 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-dependency/01-assert.yaml @@ -0,0 +1,60 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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: LBPool +metadata: + name: lbpool-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: LBPool +metadata: + name: lbpool-dependency-no-listener +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: LBPool +metadata: + name: lbpool-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/lbpool/tests/lbpool-dependency/01-create-dependencies.yaml b/internal/controllers/lbpool/tests/lbpool-dependency/01-create-dependencies.yaml new file mode 100644 index 00000000..feaab1bb --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-dependency/01-create-dependencies.yaml @@ -0,0 +1,45 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic lbpool-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: lbpool-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: lbpool-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: lbpool-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/lbpool/tests/lbpool-dependency/02-assert.yaml b/internal/controllers/lbpool/tests/lbpool-dependency/02-assert.yaml new file mode 100644 index 00000000..c6134d59 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-dependency/02-assert.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LoadBalancer + name: lbpool-dependency + ref: loadBalancer + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Listener + name: lbpool-dependency + ref: listener + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: lbpool-dependency + ref: project + - apiVersion: v1 + kind: Secret + name: lbpool-dependency + ref: secret +assertAll: + - celExpr: "loadBalancer.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/lbpool' in loadBalancer.metadata.finalizers" + - celExpr: "listener.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/lbpool' in listener.metadata.finalizers" + - celExpr: "project.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/lbpool' in project.metadata.finalizers" + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/lbpool' in secret.metadata.finalizers" diff --git a/internal/controllers/lbpool/tests/lbpool-dependency/02-delete-dependencies.yaml b/internal/controllers/lbpool/tests/lbpool-dependency/02-delete-dependencies.yaml new file mode 100644 index 00000000..695fc388 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-dependency/02-delete-dependencies.yaml @@ -0,0 +1,13 @@ +--- +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 lbpool-dependency --wait=false + namespaced: true + - command: kubectl delete listener lbpool-dependency --wait=false + namespaced: true + - command: kubectl delete project lbpool-dependency --wait=false + namespaced: true + - command: kubectl delete secret lbpool-dependency --wait=false + namespaced: true diff --git a/internal/controllers/lbpool/tests/lbpool-dependency/03-assert.yaml b/internal/controllers/lbpool/tests/lbpool-dependency/03-assert.yaml new file mode 100644 index 00000000..a29f76bd --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-dependency/03-assert.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +# Dependencies that were prevented deletion before should now be gone +- script: "! kubectl get loadbalancer lbpool-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get listener lbpool-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get project lbpool-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get secret lbpool-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/lbpool/tests/lbpool-dependency/03-delete-resources.yaml b/internal/controllers/lbpool/tests/lbpool-dependency/03-delete-resources.yaml new file mode 100644 index 00000000..3718c0cd --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-dependency/03-delete-resources.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LBPool + name: lbpool-dependency-no-secret +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LBPool + name: lbpool-dependency-no-loadbalancer +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LBPool + name: lbpool-dependency-no-listener +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LBPool + name: lbpool-dependency-no-project diff --git a/internal/controllers/lbpool/tests/lbpool-dependency/README.md b/internal/controllers/lbpool/tests/lbpool-dependency/README.md new file mode 100644 index 00000000..7291f342 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-dependency/README.md @@ -0,0 +1,21 @@ +# Creation and deletion dependencies + +## Step 00 + +Create LBPools referencing non-existing resources. Each LBPool is dependent on other non-existing resource. Verify that the LBPools are waiting for the needed resources to be created externally. + +## Step 01 + +Create the missing dependencies and verify all the LBPools 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 LBPools and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#dependency diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/00-assert.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/00-assert.yaml new file mode 100644 index 00000000..c789efe4 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/00-assert.yaml @@ -0,0 +1,21 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for LoadBalancer/lbpool-import-dependency to be ready + Waiting for Listener/lbpool-import-dependency to be ready + Waiting for Project/lbpool-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for LoadBalancer/lbpool-import-dependency to be ready + Waiting for Listener/lbpool-import-dependency to be ready + Waiting for Project/lbpool-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/00-import-resource.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/00-import-resource.yaml new file mode 100644 index 00000000..92c6ebba --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/00-import-resource.yaml @@ -0,0 +1,54 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: lbpool-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: lbpool-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Listener +metadata: + name: lbpool-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: lbpool-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: lbpool-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: lbpool-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + loadBalancerRef: lbpool-import-dependency + listenerRef: lbpool-import-dependency + projectRef: lbpool-import-dependency diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/00-secret.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/00-secret.yaml new file mode 100644 index 00000000..045711ee --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-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/lbpool/tests/lbpool-import-dependency/01-assert.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/01-assert.yaml new file mode 100644 index 00000000..123b8612 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/01-assert.yaml @@ -0,0 +1,36 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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: LBPool +metadata: + name: lbpool-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for LoadBalancer/lbpool-import-dependency to be ready + Waiting for Listener/lbpool-import-dependency to be ready + Waiting for Project/lbpool-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for LoadBalancer/lbpool-import-dependency to be ready + Waiting for Listener/lbpool-import-dependency to be ready + Waiting for Project/lbpool-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/01-create-trap-resource.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/01-create-trap-resource.yaml new file mode 100644 index 00000000..786ee354 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/01-create-trap-resource.yaml @@ -0,0 +1,56 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: lbpool-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: Listener +metadata: + name: lbpool-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: Project +metadata: + name: lbpool-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 `lbpool-import-dependency-not-this-one` should not be picked by the import filter +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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: lbpool-import-dependency-not-this-one + listenerRef: lbpool-import-dependency-not-this-one + projectRef: lbpool-import-dependency-not-this-one + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/02-assert.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/02-assert.yaml new file mode 100644 index 00000000..794e2fe0 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/02-assert.yaml @@ -0,0 +1,44 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LBPool + name: lbpool-import-dependency + ref: lbpool1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LBPool + name: lbpool-import-dependency-not-this-one + ref: lbpool2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LoadBalancer + name: lbpool-import-dependency + ref: loadBalancer + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Listener + name: lbpool-import-dependency + ref: listener + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: lbpool-import-dependency + ref: project +assertAll: + - celExpr: "lbpool1.status.id != lbpool2.status.id" + - celExpr: "lbpool1.status.resource.loadBalancerID == loadBalancer.status.id" + - celExpr: "lbpool1.status.resource.listenerID == listener.status.id" + - celExpr: "lbpool1.status.resource.projectID == project.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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/lbpool/tests/lbpool-import-dependency/02-create-resource.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/02-create-resource.yaml new file mode 100644 index 00000000..64829b2f --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/02-create-resource.yaml @@ -0,0 +1,55 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: lbpool-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: lbpool-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: Project +metadata: + name: lbpool-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: LBPool +metadata: + name: lbpool-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: lbpool-import-dependency-external + listenerRef: lbpool-import-dependency-external + projectRef: lbpool-import-dependency-external + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/03-assert.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/03-assert.yaml new file mode 100644 index 00000000..badd4558 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/03-assert.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get loadbalancer lbpool-import-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get listener lbpool-import-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get project lbpool-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/03-delete-import-dependencies.yaml new file mode 100644 index 00000000..24a62289 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/03-delete-import-dependencies.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We should be able to delete the import dependencies + - command: kubectl delete loadbalancer lbpool-import-dependency + namespaced: true + - command: kubectl delete listener lbpool-import-dependency + namespaced: true + - command: kubectl delete project lbpool-import-dependency + namespaced: true diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/04-assert.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/04-assert.yaml new file mode 100644 index 00000000..1911c6b1 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/04-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get lbpool lbpool-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/04-delete-resource.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/04-delete-resource.yaml new file mode 100644 index 00000000..af61a1c4 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/04-delete-resource.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LBPool + name: lbpool-import-dependency diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/README.md b/internal/controllers/lbpool/tests/lbpool-import-dependency/README.md new file mode 100644 index 00000000..e97e0b29 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/README.md @@ -0,0 +1,29 @@ +# Check dependency handling for imported LBPool + +## Step 00 + +Import a LBPool that references other imported resources. The referenced imported resources have no matching resources yet. +Verify the LBPool is waiting for the dependency to be ready. + +## Step 01 + +Create a LBPool matching the import filter, except for referenced resources, and verify that it's not being imported. + +## Step 02 + +Create the referenced resources and a LBPool matching the import filters. + +Verify that the observed status on the imported LBPool corresponds to the spec of the created LBPool. + +## 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 LBPool and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-dependency diff --git a/internal/controllers/lbpool/tests/lbpool-import-error/00-assert.yaml b/internal/controllers/lbpool/tests/lbpool-import-error/00-assert.yaml new file mode 100644 index 00000000..de19537b --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import-error/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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: LBPool +metadata: + name: lbpool-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/lbpool/tests/lbpool-import-error/00-create-resources.yaml b/internal/controllers/lbpool/tests/lbpool-import-error/00-create-resources.yaml new file mode 100644 index 00000000..05a1bc12 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import-error/00-create-resources.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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: LBPool from "import error" test + # TODO(scaffolding): add any required field +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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: LBPool from "import error" test + # TODO(scaffolding): add any required field diff --git a/internal/controllers/lbpool/tests/lbpool-import-error/00-secret.yaml b/internal/controllers/lbpool/tests/lbpool-import-error/00-secret.yaml new file mode 100644 index 00000000..045711ee --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-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/lbpool/tests/lbpool-import-error/01-assert.yaml b/internal/controllers/lbpool/tests/lbpool-import-error/01-assert.yaml new file mode 100644 index 00000000..2001f05f --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import-error/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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/lbpool/tests/lbpool-import-error/01-import-resource.yaml b/internal/controllers/lbpool/tests/lbpool-import-error/01-import-resource.yaml new file mode 100644 index 00000000..8135a327 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import-error/01-import-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-import-error +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + description: LBPool from "import error" test diff --git a/internal/controllers/lbpool/tests/lbpool-import-error/README.md b/internal/controllers/lbpool/tests/lbpool-import-error/README.md new file mode 100644 index 00000000..124c4937 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import-error/README.md @@ -0,0 +1,13 @@ +# Import LBPool with more than one matching resources + +## Step 00 + +Create two LBPools with identical specs. + +## Step 01 + +Ensure that an imported LBPool with a filter matching the resources returns an error. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/lbpool/tests/lbpool-import/00-assert.yaml b/internal/controllers/lbpool/tests/lbpool-import/00-assert.yaml new file mode 100644 index 00000000..a5cc9c53 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import/00-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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/lbpool/tests/lbpool-import/00-import-resource.yaml b/internal/controllers/lbpool/tests/lbpool-import/00-import-resource.yaml new file mode 100644 index 00000000..16b61c9d --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import/00-import-resource.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: lbpool-import-external + description: LBPool lbpool-import-external from "lbpool-import" test + # TODO(scaffolding): Add all fields supported by the filter diff --git a/internal/controllers/lbpool/tests/lbpool-import/00-secret.yaml b/internal/controllers/lbpool/tests/lbpool-import/00-secret.yaml new file mode 100644 index 00000000..045711ee --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-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/lbpool/tests/lbpool-import/01-assert.yaml b/internal/controllers/lbpool/tests/lbpool-import/01-assert.yaml new file mode 100644 index 00000000..db0c051f --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import/01-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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: lbpool-import-external-not-this-one + description: LBPool lbpool-import-external from "lbpool-import" test + # TODO(scaffolding): Add fields necessary to match filter +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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/lbpool/tests/lbpool-import/01-create-trap-resource.yaml b/internal/controllers/lbpool/tests/lbpool-import/01-create-trap-resource.yaml new file mode 100644 index 00000000..1559ff22 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import/01-create-trap-resource.yaml @@ -0,0 +1,17 @@ +--- +# This `lbpool-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: LBPool +metadata: + name: lbpool-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: LBPool lbpool-import-external from "lbpool-import" test + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/lbpool/tests/lbpool-import/02-assert.yaml b/internal/controllers/lbpool/tests/lbpool-import/02-assert.yaml new file mode 100644 index 00000000..19c25e30 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import/02-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LBPool + name: lbpool-import-external + ref: lbpool1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LBPool + name: lbpool-import-external-not-this-one + ref: lbpool2 +assertAll: + - celExpr: "lbpool1.status.id != lbpool2.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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: lbpool-import-external + description: LBPool lbpool-import-external from "lbpool-import" test + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/lbpool/tests/lbpool-import/02-create-resource.yaml b/internal/controllers/lbpool/tests/lbpool-import/02-create-resource.yaml new file mode 100644 index 00000000..b08284ef --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import/02-create-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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: LBPool lbpool-import-external from "lbpool-import" test + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/lbpool/tests/lbpool-import/README.md b/internal/controllers/lbpool/tests/lbpool-import/README.md new file mode 100644 index 00000000..b7564a6d --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-import/README.md @@ -0,0 +1,18 @@ +# Import LBPool + +## Step 00 + +Import a lbpool that matches all fields in the filter, and verify it is waiting for the external resource to be created. + +## Step 01 + +Create a lbpool 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 lbpool matching the filter and verify that the observed status on the imported lbpool corresponds to the spec of the created lbpool. +Also, confirm that it does not adopt any lbpool whose name is a superstring of its own. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/lbpool/tests/lbpool-update/00-assert.yaml b/internal/controllers/lbpool/tests/lbpool-update/00-assert.yaml new file mode 100644 index 00000000..6bde68bf --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-update/00-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LBPool + name: lbpool-update + ref: lbpool +assertAll: + - celExpr: "!has(lbpool.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-update +status: + resource: + name: lbpool-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/lbpool/tests/lbpool-update/00-minimal-resource.yaml b/internal/controllers/lbpool/tests/lbpool-update/00-minimal-resource.yaml new file mode 100644 index 00000000..e79b4208 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-update/00-minimal-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-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/lbpool/tests/lbpool-update/00-prerequisites.yaml b/internal/controllers/lbpool/tests/lbpool-update/00-prerequisites.yaml new file mode 100644 index 00000000..045711ee --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-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/lbpool/tests/lbpool-update/01-assert.yaml b/internal/controllers/lbpool/tests/lbpool-update/01-assert.yaml new file mode 100644 index 00000000..4ba2af26 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-update/01-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-update +status: + resource: + name: lbpool-update-updated + description: lbpool-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/lbpool/tests/lbpool-update/01-updated-resource.yaml b/internal/controllers/lbpool/tests/lbpool-update/01-updated-resource.yaml new file mode 100644 index 00000000..93b09d64 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-update/01-updated-resource.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-update +spec: + resource: + name: lbpool-update-updated + description: lbpool-update-updated + # TODO(scaffolding): update all mutable fields diff --git a/internal/controllers/lbpool/tests/lbpool-update/02-assert.yaml b/internal/controllers/lbpool/tests/lbpool-update/02-assert.yaml new file mode 100644 index 00000000..1021909c --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-update/02-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LBPool + name: lbpool-update + ref: lbpool +assertAll: + - celExpr: "!has(lbpool.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-update +status: + resource: + name: lbpool-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/lbpool/tests/lbpool-update/02-reverted-resource.yaml b/internal/controllers/lbpool/tests/lbpool-update/02-reverted-resource.yaml new file mode 100644 index 00000000..2c6c253f --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-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/lbpool/tests/lbpool-update/README.md b/internal/controllers/lbpool/tests/lbpool-update/README.md new file mode 100644 index 00000000..8e1f8391 --- /dev/null +++ b/internal/controllers/lbpool/tests/lbpool-update/README.md @@ -0,0 +1,17 @@ +# Update LBPool + +## Step 00 + +Create a LBPool 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/lbpool.go b/internal/osclients/lbpool.go new file mode 100644 index 00000000..a6ce5af1 --- /dev/null +++ b/internal/osclients/lbpool.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/pools" + "github.com/gophercloud/utils/v2/openstack/clientconfig" +) + +type LBPoolClient interface { + ListLBPools(ctx context.Context, listOpts pools.ListOptsBuilder) iter.Seq2[*pools.Pool, error] + CreateLBPool(ctx context.Context, opts pools.CreateOptsBuilder) (*pools.Pool, error) + DeleteLBPool(ctx context.Context, resourceID string) error + GetLBPool(ctx context.Context, resourceID string) (*pools.Pool, error) + UpdateLBPool(ctx context.Context, id string, opts pools.UpdateOptsBuilder) (*pools.Pool, error) +} + +type lbpoolClient struct{ client *gophercloud.ServiceClient } + +// NewLBPoolClient returns a new OpenStack client. +func NewLBPoolClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (LBPoolClient, 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 lbpool service client: %v", err) + } + + return &lbpoolClient{client}, nil +} + +func (c lbpoolClient) ListLBPools(ctx context.Context, listOpts pools.ListOptsBuilder) iter.Seq2[*pools.Pool, error] { + pager := pools.List(c.client, listOpts) + return func(yield func(*pools.Pool, error) bool) { + _ = pager.EachPage(ctx, yieldPage(pools.ExtractPools, yield)) + } +} + +func (c lbpoolClient) CreateLBPool(ctx context.Context, opts pools.CreateOptsBuilder) (*pools.Pool, error) { + return pools.Create(ctx, c.client, opts).Extract() +} + +func (c lbpoolClient) DeleteLBPool(ctx context.Context, resourceID string) error { + return pools.Delete(ctx, c.client, resourceID).ExtractErr() +} + +func (c lbpoolClient) GetLBPool(ctx context.Context, resourceID string) (*pools.Pool, error) { + return pools.Get(ctx, c.client, resourceID).Extract() +} + +func (c lbpoolClient) UpdateLBPool(ctx context.Context, id string, opts pools.UpdateOptsBuilder) (*pools.Pool, error) { + return pools.Update(ctx, c.client, id, opts).Extract() +} + +type lbpoolErrorClient struct{ error } + +// NewLBPoolErrorClient returns a LBPoolClient in which every method returns the given error. +func NewLBPoolErrorClient(e error) LBPoolClient { + return lbpoolErrorClient{e} +} + +func (e lbpoolErrorClient) ListLBPools(_ context.Context, _ pools.ListOptsBuilder) iter.Seq2[*pools.Pool, error] { + return func(yield func(*pools.Pool, error) bool) { + yield(nil, e.error) + } +} + +func (e lbpoolErrorClient) CreateLBPool(_ context.Context, _ pools.CreateOptsBuilder) (*pools.Pool, error) { + return nil, e.error +} + +func (e lbpoolErrorClient) DeleteLBPool(_ context.Context, _ string) error { + return e.error +} + +func (e lbpoolErrorClient) GetLBPool(_ context.Context, _ string) (*pools.Pool, error) { + return nil, e.error +} + +func (e lbpoolErrorClient) UpdateLBPool(_ context.Context, _ string, _ pools.UpdateOptsBuilder) (*pools.Pool, error) { + return nil, e.error +} diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 826d8961..398bf9b6 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -1621,6 +1621,8 @@ _Appears in:_ - [FloatingIPResourceSpec](#floatingipresourcespec) - [GroupFilter](#groupfilter) - [GroupResourceSpec](#groupresourcespec) +- [LBPoolFilter](#lbpoolfilter) +- [LBPoolResourceSpec](#lbpoolresourcespec) - [ListenerFilter](#listenerfilter) - [ListenerResourceSpec](#listenerresourcespec) - [LoadBalancerFilter](#loadbalancerfilter) @@ -1646,6 +1648,12 @@ _Appears in:_ + + + + + + #### Listener @@ -2450,6 +2458,8 @@ _Appears in:_ - [ImageResourceSpec](#imageresourcespec) - [KeyPairFilter](#keypairfilter) - [KeyPairResourceSpec](#keypairresourcespec) +- [LBPoolFilter](#lbpoolfilter) +- [LBPoolResourceSpec](#lbpoolresourcespec) - [ListenerFilter](#listenerfilter) - [ListenerResourceSpec](#listenerresourcespec) - [LoadBalancerFilter](#loadbalancerfilter) From 1866b64a11edf9ef8b1c3c51a27991169a3be1c7 Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Tue, 30 Dec 2025 17:49:13 +0200 Subject: [PATCH 7/7] implement loadbalancer pools - implement pool and member support as a "sub resource" decided to implement members as a sub resource as it can only exist as part of a pool and has no existent of its own --- PROJECT | 8 + api/v1alpha1/lbpool_types.go | 384 +++++++- api/v1alpha1/zz_generated.deepcopy.go | 354 +++++++ api/v1alpha1/zz_generated.lbpool-resource.go | 177 ++++ cmd/manager/main.go | 2 + cmd/models-schema/zz_generated.openapi.go | 903 +++++++++++++++++- cmd/resource-generator/main.go | 3 + .../bases/openstack.k-orc.cloud_lbpools.yaml | 751 +++++++++++++++ config/crd/kustomization.yaml | 1 + config/samples/kustomization.yaml | 1 + config/samples/openstack_v1alpha1_lbpool.yaml | 25 +- .../samples/openstack_v1alpha1_listener.yaml | 10 + internal/controllers/lbpool/actuator.go | 415 +++++++- internal/controllers/lbpool/actuator_test.go | 217 +++++ internal/controllers/lbpool/controller.go | 29 + internal/controllers/lbpool/status.go | 106 +- .../tests/lbpool-create-full/00-assert.yaml | 35 +- .../00-create-resource.yaml | 45 +- .../lbpool-create-minimal/00-assert.yaml | 15 +- .../00-create-resource.yaml | 45 +- .../tests/lbpool-dependency/00-assert.yaml | 4 +- .../00-create-resources-missing-deps.yaml | 78 +- .../01-create-dependencies.yaml | 43 +- .../tests/lbpool-dependency/02-assert.yaml | 2 +- .../02-delete-dependencies.yaml | 2 +- .../tests/lbpool-dependency/03-assert.yaml | 2 +- .../lbpool-import-dependency/00-assert.yaml | 10 +- .../00-import-resource.yaml | 28 - .../lbpool-import-dependency/01-assert.yaml | 10 +- .../01-create-trap-resource.yaml | 26 +- .../lbpool-import-dependency/02-assert.yaml | 12 +- .../02-create-resource.yaml | 28 +- .../lbpool-import-dependency/03-assert.yaml | 4 - .../03-delete-import-dependencies.yaml | 4 - .../00-create-resources.yaml | 49 +- .../01-import-resource.yaml | 2 +- .../lbpool-import/00-import-resource.yaml | 2 - .../lbpool/tests/lbpool-import/01-assert.yaml | 2 - .../01-create-trap-resource.yaml | 43 +- .../lbpool/tests/lbpool-import/02-assert.yaml | 2 - .../lbpool-import/02-create-resource.yaml | 43 +- .../lbpool/tests/lbpool-update/00-assert.yaml | 26 +- .../lbpool-update/00-minimal-resource.yaml | 46 +- .../lbpool/tests/lbpool-update/01-assert.yaml | 20 +- .../lbpool-update/01-updated-resource.yaml | 13 +- .../lbpool/tests/lbpool-update/02-assert.yaml | 29 +- .../lbpool/zz_generated.adapter.go | 88 ++ .../lbpool/zz_generated.controller.go | 45 + internal/controllers/listener/actuator.go | 4 +- internal/osclients/lbpool.go | 52 + internal/osclients/mock/doc.go | 3 + internal/osclients/mock/lbpool.go | 204 ++++ internal/scope/mock.go | 7 + internal/scope/provider.go | 4 + internal/scope/scope.go | 1 + kuttl-test.yaml | 1 + .../applyconfiguration/api/v1alpha1/lbpool.go | 281 ++++++ .../api/v1alpha1/lbpoolfilter.go | 141 +++ .../api/v1alpha1/lbpoolimport.go | 48 + .../api/v1alpha1/lbpoolmemberspec.go | 97 ++ .../api/v1alpha1/lbpoolmemberstatus.go | 120 +++ .../api/v1alpha1/lbpoolresourcespec.go | 207 ++++ .../api/v1alpha1/lbpoolresourcestatus.go | 234 +++++ .../api/v1alpha1/lbpoolsessionpersistence.go | 52 + .../api/v1alpha1/lbpoolspec.go | 79 ++ .../api/v1alpha1/lbpoolstatus.go | 66 ++ .../applyconfiguration/internal/internal.go | 333 +++++++ pkg/clients/applyconfiguration/utils.go | 20 + .../typed/api/v1alpha1/api_client.go | 5 + .../api/v1alpha1/fake/fake_api_client.go | 4 + .../typed/api/v1alpha1/fake/fake_lbpool.go | 51 + .../typed/api/v1alpha1/generated_expansion.go | 2 + .../clientset/typed/api/v1alpha1/lbpool.go | 74 ++ .../api/v1alpha1/interface.go | 7 + .../externalversions/api/v1alpha1/lbpool.go | 102 ++ .../informers/externalversions/generic.go | 2 + .../api/v1alpha1/expansion_generated.go | 8 + pkg/clients/listers/api/v1alpha1/lbpool.go | 70 ++ website/docs/crd-reference.md | 318 ++++++ 79 files changed, 6463 insertions(+), 323 deletions(-) create mode 100644 api/v1alpha1/zz_generated.lbpool-resource.go create mode 100644 config/crd/bases/openstack.k-orc.cloud_lbpools.yaml create mode 100644 internal/controllers/lbpool/zz_generated.adapter.go create mode 100644 internal/controllers/lbpool/zz_generated.controller.go create mode 100644 internal/osclients/mock/lbpool.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/lbpool.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/lbpoolfilter.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/lbpoolimport.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/lbpoolmemberspec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/lbpoolmemberstatus.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/lbpoolresourcespec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/lbpoolresourcestatus.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/lbpoolsessionpersistence.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/lbpoolspec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/lbpoolstatus.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_lbpool.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/lbpool.go create mode 100644 pkg/clients/informers/externalversions/api/v1alpha1/lbpool.go create mode 100644 pkg/clients/listers/api/v1alpha1/lbpool.go diff --git a/PROJECT b/PROJECT index b56b5913..9603b82d 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: LBPool + path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 + version: v1alpha1 - api: crdVersion: v1 namespaced: true diff --git a/api/v1alpha1/lbpool_types.go b/api/v1alpha1/lbpool_types.go index 48968cf0..82624912 100644 --- a/api/v1alpha1/lbpool_types.go +++ b/api/v1alpha1/lbpool_types.go @@ -16,7 +16,151 @@ limitations under the License. package v1alpha1 +// LBPoolProtocol represents the protocol used by a pool. +// +kubebuilder:validation:Enum=HTTP;HTTPS;PROXY;PROXYV2;SCTP;TCP;UDP +type LBPoolProtocol string + +const ( + LBPoolProtocolHTTP LBPoolProtocol = "HTTP" + LBPoolProtocolHTTPS LBPoolProtocol = "HTTPS" + LBPoolProtocolPROXY LBPoolProtocol = "PROXY" + LBPoolProtocolPROXYV2 LBPoolProtocol = "PROXYV2" + LBPoolProtocolSCTP LBPoolProtocol = "SCTP" + LBPoolProtocolTCP LBPoolProtocol = "TCP" + LBPoolProtocolUDP LBPoolProtocol = "UDP" +) + +// LBPoolLBAlgorithm represents the load balancing algorithm used by a pool. +// +kubebuilder:validation:Enum=LEAST_CONNECTIONS;ROUND_ROBIN;SOURCE_IP;SOURCE_IP_PORT +type LBPoolLBAlgorithm string + +const ( + LBPoolLBAlgorithmLeastConnections LBPoolLBAlgorithm = "LEAST_CONNECTIONS" + LBPoolLBAlgorithmRoundRobin LBPoolLBAlgorithm = "ROUND_ROBIN" + LBPoolLBAlgorithmSourceIP LBPoolLBAlgorithm = "SOURCE_IP" + LBPoolLBAlgorithmSourceIPPort LBPoolLBAlgorithm = "SOURCE_IP_PORT" +) + +// LBPoolSessionPersistenceType represents the type of session persistence. +// +kubebuilder:validation:Enum=APP_COOKIE;HTTP_COOKIE;SOURCE_IP +type LBPoolSessionPersistenceType string + +const ( + // LBPoolSessionPersistenceAppCookie relies on a cookie established by the backend application. + LBPoolSessionPersistenceAppCookie LBPoolSessionPersistenceType = "APP_COOKIE" + // LBPoolSessionPersistenceHTTPCookie causes the load balancer to create a cookie on first request. + LBPoolSessionPersistenceHTTPCookie LBPoolSessionPersistenceType = "HTTP_COOKIE" + // LBPoolSessionPersistenceSourceIP routes connections from the same source IP to the same member. + LBPoolSessionPersistenceSourceIP LBPoolSessionPersistenceType = "SOURCE_IP" +) + +// LBPoolSessionPersistence represents session persistence configuration for a pool. +type LBPoolSessionPersistence struct { + // type is the type of session persistence. + // +required + Type LBPoolSessionPersistenceType `json:"type,omitempty"` + + // cookieName is the name of the cookie if persistence type is APP_COOKIE. + // Required when type is APP_COOKIE. + // +kubebuilder:validation:MaxLength=255 + // +optional + CookieName *string `json:"cookieName,omitempty"` +} + +// +kubebuilder:validation:MinLength:=1 +// +kubebuilder:validation:MaxLength:=255 +type LBPoolTag string + +// LBPoolMemberSpec defines a member of an LB pool. +type LBPoolMemberSpec struct { + // name is a human-readable name for the member. + // +kubebuilder:validation:MaxLength=255 + // +optional + Name *string `json:"name,omitempty"` + + // address is the IP address of the member to receive traffic. + // +required + Address IPvAny `json:"address,omitempty"` + + // protocolPort is the port on which the member application is listening. + // +required + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + ProtocolPort int32 `json:"protocolPort,omitempty"` + + // subnetRef is a reference to the ORC Subnet where the member resides. + // +optional + SubnetRef *KubernetesNameRef `json:"subnetRef,omitempty"` + + // weight is the relative portion of traffic this member should receive. + // A member with weight 10 receives 5x the traffic of a member with weight 2. + // Default is 1. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=256 + // +optional + Weight *int32 `json:"weight,omitempty"` + + // backup indicates whether this is a backup member. Backup members only + // receive traffic when all non-backup members are down. + // +optional + Backup *bool `json:"backup,omitempty"` + + // adminStateUp is the administrative state of the member (up=true, down=false). + // +optional + AdminStateUp *bool `json:"adminStateUp,omitempty"` +} + +// LBPoolMemberStatus represents the observed state of a pool member. +type LBPoolMemberStatus struct { + // id is the unique identifier of the member in OpenStack. + // +kubebuilder:validation:MaxLength=1024 + // +optional + ID string `json:"id,omitempty"` + + // name is the human-readable name for the member. + // +kubebuilder:validation:MaxLength=255 + // +optional + Name string `json:"name,omitempty"` + + // address is the IP address of the member. + // +kubebuilder:validation:MaxLength=64 + // +optional + Address string `json:"address,omitempty"` + + // protocolPort is the port on which the member is listening. + // +optional + ProtocolPort int32 `json:"protocolPort,omitempty"` + + // subnetID is the ID of the subnet the member is on. + // +kubebuilder:validation:MaxLength=1024 + // +optional + SubnetID string `json:"subnetID,omitempty"` + + // weight is the weight of the member for load balancing. + // +optional + Weight int32 `json:"weight,omitempty"` + + // backup indicates whether this is a backup member. + // +optional + Backup bool `json:"backup,omitempty"` + + // adminStateUp is the administrative state of the member. + // +optional + AdminStateUp bool `json:"adminStateUp,omitempty"` + + // provisioningStatus is the provisioning status of the member. + // +kubebuilder:validation:MaxLength=64 + // +optional + ProvisioningStatus string `json:"provisioningStatus,omitempty"` + + // operatingStatus is the operating status of the member. + // +kubebuilder:validation:MaxLength=64 + // +optional + OperatingStatus string `json:"operatingStatus,omitempty"` +} + // LBPoolResourceSpec contains the desired state of the resource. +// +kubebuilder:validation:XValidation:rule="has(self.loadBalancerRef) || has(self.listenerRef)",message="either loadBalancerRef or listenerRef must be specified" type LBPoolResourceSpec struct { // name will be the name of the created resource. If not specified, the // name of the ORC object will be used. @@ -29,12 +173,26 @@ type LBPoolResourceSpec struct { // +optional Description *string `json:"description,omitempty"` - // loadBalancerRef is a reference to the ORC LoadBalancer which this resource is associated with. + // lbAlgorithm is the load balancing algorithm used to distribute traffic + // to the pool's members. + // +required + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="lbAlgorithm is immutable" + LBAlgorithm LBPoolLBAlgorithm `json:"lbAlgorithm,omitempty"` + + // protocol is the protocol used by the pool and its members for traffic. + // +required + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="protocol is immutable" + Protocol LBPoolProtocol `json:"protocol,omitempty"` + + // loadBalancerRef is a reference to the ORC LoadBalancer which this pool + // is associated with. Either loadBalancerRef or listenerRef must be specified. // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="loadBalancerRef is immutable" LoadBalancerRef *KubernetesNameRef `json:"loadBalancerRef,omitempty"` - // listenerRef is a reference to the ORC Listener which this resource is associated with. + // listenerRef is a reference to the ORC Listener which this pool is + // associated with as the default pool. Either loadBalancerRef or listenerRef + // must be specified. // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="listenerRef is immutable" ListenerRef *KubernetesNameRef `json:"listenerRef,omitempty"` @@ -44,13 +202,73 @@ type LBPoolResourceSpec 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/pools - // - // 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 pool, which is up (true) + // or down (false). + // +optional + AdminStateUp *bool `json:"adminStateUp,omitempty"` + + // sessionPersistence is the session persistence configuration for the pool. + // +optional + SessionPersistence *LBPoolSessionPersistence `json:"sessionPersistence,omitempty"` + + // tlsEnabled enables backend re-encryption when set to true. Requires + // TERMINATED_HTTPS or HTTPS protocol on the listener. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="tlsEnabled is immutable" + TLSEnabled *bool `json:"tlsEnabled,omitempty"` + + // tlsContainerRef is a reference to a secret containing a PKCS12 format + // certificate/key bundle for backend re-encryption. + // +kubebuilder:validation:MaxLength=255 + // +optional + TLSContainerRef *string `json:"tlsContainerRef,omitempty"` + + // caTLSContainerRef is a reference to a secret containing the CA + // certificate for backend re-encryption. + // +kubebuilder:validation:MaxLength=255 + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="caTLSContainerRef is immutable" + CATLSContainerRef *string `json:"caTLSContainerRef,omitempty"` + + // crlContainerRef is a reference to a secret containing the CA + // revocation list for backend re-encryption. + // +kubebuilder:validation:MaxLength=255 + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="crlContainerRef is immutable" + CRLContainerRef *string `json:"crlContainerRef,omitempty"` + + // tlsCiphers is a colon-separated list of ciphers for backend TLS connections. + // +kubebuilder:validation:MaxLength=2048 + // +optional + TLSCiphers *string `json:"tlsCiphers,omitempty"` + + // tlsVersions is a list of TLS protocol versions to be used for backend + // TLS connections. + // +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 backend TLS connections. + // Available protocols: h2, http/1.0, http/1.1. + // +listType=set + // +optional + // +kubebuilder:validation:MaxItems:=10 + // +kubebuilder:validation:items:MaxLength=32 + ALPNProtocols []string `json:"alpnProtocols,omitempty"` + + // tags is a list of tags which will be applied to the pool. + // +kubebuilder:validation:MaxItems:=64 + // +listType=set + // +optional + Tags []LBPoolTag `json:"tags,omitempty"` + + // members is a list of backend members for this pool. + // +kubebuilder:validation:MaxItems:=256 + // +listType=atomic + // +optional + Members []LBPoolMemberSpec `json:"members,omitempty"` } // LBPoolFilter defines an existing resource by its properties @@ -66,21 +284,54 @@ type LBPoolFilter struct { // +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 pool is associated with. // +optional LoadBalancerRef *KubernetesNameRef `json:"loadBalancerRef,omitempty"` - // listenerRef is a reference to the ORC Listener which this resource is associated with. + // listenerRef filters by the Listener this pool is associated with. // +optional ListenerRef *KubernetesNameRef `json:"listenerRef,omitempty"` - // projectRef is a reference to the ORC Project which this resource is associated with. + // projectRef filters by the Project this pool is associated with. // +optional ProjectRef *KubernetesNameRef `json:"projectRef,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/pools + // lbAlgorithm filters by the load balancing algorithm. + // +optional + LBAlgorithm *LBPoolLBAlgorithm `json:"lbAlgorithm,omitempty"` + + // protocol filters by the protocol used by the pool. + // +optional + Protocol *LBPoolProtocol `json:"protocol,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 []LBPoolTag `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 []LBPoolTag `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 []LBPoolTag `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 []LBPoolTag `json:"notTagsAny,omitempty"` } // LBPoolResourceStatus represents the observed state of the resource. @@ -95,22 +346,107 @@ type LBPoolResourceStatus struct { // +optional Description string `json:"description,omitempty"` - // loadBalancerID is the ID of the LoadBalancer to which the resource is associated. + // lbAlgorithm is the load balancing algorithm used by the pool. + // +kubebuilder:validation:MaxLength=64 + // +optional + LBAlgorithm string `json:"lbAlgorithm,omitempty"` + + // protocol is the protocol used by the pool. + // +kubebuilder:validation:MaxLength=64 + // +optional + Protocol string `json:"protocol,omitempty"` + + // loadBalancerIDs is the list of LoadBalancer IDs this pool is associated with. + // +listType=atomic + // +optional + // +kubebuilder:validation:MaxItems:=64 + // +kubebuilder:validation:items:MaxLength=1024 + LoadBalancerIDs []string `json:"loadBalancerIDs,omitempty"` + + // listenerIDs is the list of Listener IDs this pool is associated with. + // +listType=atomic + // +optional + // +kubebuilder:validation:MaxItems:=64 + // +kubebuilder:validation:items:MaxLength=1024 + ListenerIDs []string `json:"listenerIDs,omitempty"` + + // projectID is the ID of the Project this pool is associated with. + // +kubebuilder:validation:MaxLength=1024 + // +optional + ProjectID string `json:"projectID,omitempty"` + + // adminStateUp is the administrative state of the pool, + // which is up (true) or down (false). + // +optional + AdminStateUp *bool `json:"adminStateUp,omitempty"` + + // provisioningStatus is the provisioning status of the pool. // +kubebuilder:validation:MaxLength=1024 // +optional - LoadBalancerID string `json:"loadBalancerID,omitempty"` + ProvisioningStatus string `json:"provisioningStatus,omitempty"` - // listenerID is the ID of the Listener to which the resource is associated. + // operatingStatus is the operating status of the pool. // +kubebuilder:validation:MaxLength=1024 // +optional - ListenerID string `json:"listenerID,omitempty"` + OperatingStatus string `json:"operatingStatus,omitempty"` - // projectID is the ID of the Project to which the resource is associated. + // healthMonitorID is the ID of the health monitor associated with this pool. // +kubebuilder:validation:MaxLength=1024 // +optional - ProjectID string `json:"projectID,omitempty"` + HealthMonitorID string `json:"healthMonitorID,omitempty"` + + // members is the list of members in this pool with their details. + // +listType=atomic + // +optional + // +kubebuilder:validation:MaxItems:=256 + Members []LBPoolMemberStatus `json:"members,omitempty"` + + // sessionPersistence is the session persistence configuration. + // +optional + SessionPersistence *LBPoolSessionPersistence `json:"sessionPersistence,omitempty"` + + // tlsEnabled indicates whether backend re-encryption is enabled. + // +optional + TLSEnabled *bool `json:"tlsEnabled,omitempty"` + + // tlsContainerRef is the reference to the TLS container. + // +kubebuilder:validation:MaxLength=1024 + // +optional + TLSContainerRef string `json:"tlsContainerRef,omitempty"` + + // caTLSContainerRef is the reference to the CA TLS container. + // +kubebuilder:validation:MaxLength=1024 + // +optional + CATLSContainerRef string `json:"caTLSContainerRef,omitempty"` + + // crlContainerRef is the reference to the CRL container. + // +kubebuilder:validation:MaxLength=1024 + // +optional + CRLContainerRef string `json:"crlContainerRef,omitempty"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the Pool structure from - // github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/pools + // tlsCiphers is the list of TLS ciphers for backend connections. + // +kubebuilder:validation:MaxLength=2048 + // +optional + TLSCiphers string `json:"tlsCiphers,omitempty"` + + // tlsVersions is the list of TLS versions for backend connections. + // +listType=atomic + // +optional + // +kubebuilder:validation:MaxItems:=10 + // +kubebuilder:validation:items:MaxLength=32 + TLSVersions []string `json:"tlsVersions,omitempty"` + + // alpnProtocols is the list of ALPN protocols for backend connections. + // +listType=atomic + // +optional + // +kubebuilder:validation:MaxItems:=10 + // +kubebuilder:validation:items:MaxLength=32 + ALPNProtocols []string `json:"alpnProtocols,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 24a7f5cb..3394b9c2 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 *LBPool) DeepCopyInto(out *LBPool) { + *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 LBPool. +func (in *LBPool) DeepCopy() *LBPool { + if in == nil { + return nil + } + out := new(LBPool) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LBPool) 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 *LBPoolFilter) DeepCopyInto(out *LBPoolFilter) { *out = *in @@ -2003,6 +2030,36 @@ func (in *LBPoolFilter) DeepCopyInto(out *LBPoolFilter) { *out = new(KubernetesNameRef) **out = **in } + if in.LBAlgorithm != nil { + in, out := &in.LBAlgorithm, &out.LBAlgorithm + *out = new(LBPoolLBAlgorithm) + **out = **in + } + if in.Protocol != nil { + in, out := &in.Protocol, &out.Protocol + *out = new(LBPoolProtocol) + **out = **in + } + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]LBPoolTag, len(*in)) + copy(*out, *in) + } + if in.TagsAny != nil { + in, out := &in.TagsAny, &out.TagsAny + *out = make([]LBPoolTag, len(*in)) + copy(*out, *in) + } + if in.NotTags != nil { + in, out := &in.NotTags, &out.NotTags + *out = make([]LBPoolTag, len(*in)) + copy(*out, *in) + } + if in.NotTagsAny != nil { + in, out := &in.NotTagsAny, &out.NotTagsAny + *out = make([]LBPoolTag, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LBPoolFilter. @@ -2015,6 +2072,118 @@ func (in *LBPoolFilter) DeepCopy() *LBPoolFilter { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LBPoolImport) DeepCopyInto(out *LBPoolImport) { + *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(LBPoolFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LBPoolImport. +func (in *LBPoolImport) DeepCopy() *LBPoolImport { + if in == nil { + return nil + } + out := new(LBPoolImport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LBPoolList) DeepCopyInto(out *LBPoolList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]LBPool, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LBPoolList. +func (in *LBPoolList) DeepCopy() *LBPoolList { + if in == nil { + return nil + } + out := new(LBPoolList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LBPoolList) 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 *LBPoolMemberSpec) DeepCopyInto(out *LBPoolMemberSpec) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.SubnetRef != nil { + in, out := &in.SubnetRef, &out.SubnetRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.Weight != nil { + in, out := &in.Weight, &out.Weight + *out = new(int32) + **out = **in + } + if in.Backup != nil { + in, out := &in.Backup, &out.Backup + *out = new(bool) + **out = **in + } + if in.AdminStateUp != nil { + in, out := &in.AdminStateUp, &out.AdminStateUp + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LBPoolMemberSpec. +func (in *LBPoolMemberSpec) DeepCopy() *LBPoolMemberSpec { + if in == nil { + return nil + } + out := new(LBPoolMemberSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LBPoolMemberStatus) DeepCopyInto(out *LBPoolMemberStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LBPoolMemberStatus. +func (in *LBPoolMemberStatus) DeepCopy() *LBPoolMemberStatus { + if in == nil { + return nil + } + out := new(LBPoolMemberStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LBPoolResourceSpec) DeepCopyInto(out *LBPoolResourceSpec) { *out = *in @@ -2043,6 +2212,63 @@ func (in *LBPoolResourceSpec) DeepCopyInto(out *LBPoolResourceSpec) { *out = new(KubernetesNameRef) **out = **in } + if in.AdminStateUp != nil { + in, out := &in.AdminStateUp, &out.AdminStateUp + *out = new(bool) + **out = **in + } + if in.SessionPersistence != nil { + in, out := &in.SessionPersistence, &out.SessionPersistence + *out = new(LBPoolSessionPersistence) + (*in).DeepCopyInto(*out) + } + if in.TLSEnabled != nil { + in, out := &in.TLSEnabled, &out.TLSEnabled + *out = new(bool) + **out = **in + } + if in.TLSContainerRef != nil { + in, out := &in.TLSContainerRef, &out.TLSContainerRef + *out = new(string) + **out = **in + } + if in.CATLSContainerRef != nil { + in, out := &in.CATLSContainerRef, &out.CATLSContainerRef + *out = new(string) + **out = **in + } + if in.CRLContainerRef != nil { + in, out := &in.CRLContainerRef, &out.CRLContainerRef + *out = new(string) + **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.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]LBPoolTag, len(*in)) + copy(*out, *in) + } + if in.Members != nil { + in, out := &in.Members, &out.Members + *out = make([]LBPoolMemberSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LBPoolResourceSpec. @@ -2058,6 +2284,51 @@ func (in *LBPoolResourceSpec) DeepCopy() *LBPoolResourceSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LBPoolResourceStatus) DeepCopyInto(out *LBPoolResourceStatus) { *out = *in + if in.LoadBalancerIDs != nil { + in, out := &in.LoadBalancerIDs, &out.LoadBalancerIDs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ListenerIDs != nil { + in, out := &in.ListenerIDs, &out.ListenerIDs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.AdminStateUp != nil { + in, out := &in.AdminStateUp, &out.AdminStateUp + *out = new(bool) + **out = **in + } + if in.Members != nil { + in, out := &in.Members, &out.Members + *out = make([]LBPoolMemberStatus, len(*in)) + copy(*out, *in) + } + if in.SessionPersistence != nil { + in, out := &in.SessionPersistence, &out.SessionPersistence + *out = new(LBPoolSessionPersistence) + (*in).DeepCopyInto(*out) + } + if in.TLSEnabled != nil { + in, out := &in.TLSEnabled, &out.TLSEnabled + *out = new(bool) + **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.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 LBPoolResourceStatus. @@ -2070,6 +2341,89 @@ func (in *LBPoolResourceStatus) DeepCopy() *LBPoolResourceStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LBPoolSessionPersistence) DeepCopyInto(out *LBPoolSessionPersistence) { + *out = *in + if in.CookieName != nil { + in, out := &in.CookieName, &out.CookieName + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LBPoolSessionPersistence. +func (in *LBPoolSessionPersistence) DeepCopy() *LBPoolSessionPersistence { + if in == nil { + return nil + } + out := new(LBPoolSessionPersistence) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LBPoolSpec) DeepCopyInto(out *LBPoolSpec) { + *out = *in + if in.Import != nil { + in, out := &in.Import, &out.Import + *out = new(LBPoolImport) + (*in).DeepCopyInto(*out) + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(LBPoolResourceSpec) + (*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 LBPoolSpec. +func (in *LBPoolSpec) DeepCopy() *LBPoolSpec { + if in == nil { + return nil + } + out := new(LBPoolSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LBPoolStatus) DeepCopyInto(out *LBPoolStatus) { + *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(LBPoolResourceStatus) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LBPoolStatus. +func (in *LBPoolStatus) DeepCopy() *LBPoolStatus { + if in == nil { + return nil + } + out := new(LBPoolStatus) + in.DeepCopyInto(out) + 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 diff --git a/api/v1alpha1/zz_generated.lbpool-resource.go b/api/v1alpha1/zz_generated.lbpool-resource.go new file mode 100644 index 00000000..e0cefb3d --- /dev/null +++ b/api/v1alpha1/zz_generated.lbpool-resource.go @@ -0,0 +1,177 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright 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" +) + +// LBPoolImport specifies an existing resource which will be imported instead of +// creating a new one +// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MaxProperties:=1 +type LBPoolImport 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 *LBPoolFilter `json:"filter,omitempty"` +} + +// LBPoolSpec 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 LBPoolSpec struct { + // import refers to an existing OpenStack resource which will be imported instead of + // creating a new one. + // +optional + Import *LBPoolImport `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 *LBPoolResourceSpec `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"` +} + +// LBPoolStatus defines the observed state of an ORC resource. +type LBPoolStatus 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 *LBPoolResourceStatus `json:"resource,omitempty"` +} + +var _ ObjectWithConditions = &LBPool{} + +func (i *LBPool) 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" + +// LBPool is the Schema for an ORC resource. +type LBPool 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 LBPoolSpec `json:"spec,omitempty"` + + // status defines the observed state of the resource. + // +optional + Status LBPoolStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// LBPoolList contains a list of LBPool. +type LBPoolList struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items contains a list of LBPool. + // +required + Items []LBPool `json:"items"` +} + +func (l *LBPoolList) GetItems() []LBPool { + return l.Items +} + +func init() { + SchemeBuilder.Register(&LBPool{}, &LBPoolList{}) +} + +func (i *LBPool) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) { + if i == nil { + return nil, nil + } + + return &i.Namespace, &i.Spec.CloudCredentialsRef +} + +var _ CloudCredentialsRefProvider = &LBPool{} diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 995010bd..2cceaf10 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/lbpool" "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" @@ -130,6 +131,7 @@ func main() { role.New(scopeFactory), listener.New(scopeFactory), loadbalancer.New(scopeFactory), + lbpool.New(scopeFactory), } restConfig := ctrl.GetConfigOrDie() diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index cb4861ca..187e38b6 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -100,9 +100,17 @@ 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.LBPool": schema_openstack_resource_controller_v2_api_v1alpha1_LBPool(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolFilter": schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolImport": schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolList": schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolMemberSpec": schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolMemberSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolMemberStatus": schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolMemberStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolResourceSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolSessionPersistence": schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolSessionPersistence(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolSpec": schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolStatus": schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolStatus(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), @@ -3779,6 +3787,56 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairStatus(ref comm } } +func schema_openstack_resource_controller_v2_api_v1alpha1_LBPool(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LBPool 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.LBPoolSpec"), + }, + }, + "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.LBPoolStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -3802,124 +3860,905 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolFilter(ref commo }, "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 pool is associated with.", Type: []string{"string"}, Format: "", }, }, "listenerRef": { SchemaProps: spec.SchemaProps{ - Description: "listenerRef is a reference to the ORC Listener which this resource is associated with.", + Description: "listenerRef filters by the Listener this pool 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.", + Description: "projectRef filters by the Project this pool is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "lbAlgorithm": { + SchemaProps: spec.SchemaProps{ + Description: "lbAlgorithm filters by the load balancing algorithm.", + Type: []string{"string"}, + Format: "", + }, + }, + "protocol": { + SchemaProps: spec.SchemaProps{ + Description: "protocol filters by the protocol used by the pool.", + 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_LBPoolImport(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LBPoolImport 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.LBPoolFilter"), + }, + }, }, }, }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolFilter"}, } } -func schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "LBPoolResourceSpec contains the desired state of the resource.", + Description: "LBPoolList contains a list of LBPool.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "name": { + "kind": { 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.", + 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: "", }, }, - "description": { + "apiVersion": { SchemaProps: spec.SchemaProps{ - Description: "description is a human-readable description for the resource.", + 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: "", }, }, - "loadBalancerRef": { + "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 LBPool.", + 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.LBPool"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPool", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolMemberSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LBPoolMemberSpec defines a member of an LB pool.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { SchemaProps: spec.SchemaProps{ - Description: "loadBalancerRef is a reference to the ORC LoadBalancer which this resource is associated with.", + Description: "name is a human-readable name for the member.", Type: []string{"string"}, Format: "", }, }, - "listenerRef": { + "address": { SchemaProps: spec.SchemaProps{ - Description: "listenerRef is a reference to the ORC Listener which this resource is associated with.", + Description: "address is the IP address of the member to receive traffic.", Type: []string{"string"}, Format: "", }, }, - "projectRef": { + "protocolPort": { SchemaProps: spec.SchemaProps{ - Description: "projectRef is a reference to the ORC Project which this resource is associated with.", + Description: "protocolPort is the port on which the member application is listening.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "subnetRef": { + SchemaProps: spec.SchemaProps{ + Description: "subnetRef is a reference to the ORC Subnet where the member resides.", Type: []string{"string"}, Format: "", }, }, + "weight": { + SchemaProps: spec.SchemaProps{ + Description: "weight is the relative portion of traffic this member should receive. A member with weight 10 receives 5x the traffic of a member with weight 2. Default is 1.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "backup": { + SchemaProps: spec.SchemaProps{ + Description: "backup indicates whether this is a backup member. Backup members only receive traffic when all non-backup members are down.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "adminStateUp": { + SchemaProps: spec.SchemaProps{ + Description: "adminStateUp is the administrative state of the member (up=true, down=false).", + Type: []string{"boolean"}, + Format: "", + }, + }, }, + Required: []string{"address", "protocolPort"}, }, }, } } -func schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolMemberStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "LBPoolResourceStatus represents the observed state of the resource.", + Description: "LBPoolMemberStatus represents the observed state of a pool member.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "name": { + "id": { SchemaProps: spec.SchemaProps{ - Description: "name is a Human-readable name for the resource. Might not be unique.", + Description: "id is the unique identifier of the member in OpenStack.", Type: []string{"string"}, Format: "", }, }, - "description": { + "name": { SchemaProps: spec.SchemaProps{ - Description: "description is a human-readable description for the resource.", + Description: "name is the human-readable name for the member.", Type: []string{"string"}, Format: "", }, }, - "loadBalancerID": { + "address": { SchemaProps: spec.SchemaProps{ - Description: "loadBalancerID is the ID of the LoadBalancer to which the resource is associated.", + Description: "address is the IP address of the member.", Type: []string{"string"}, Format: "", }, }, - "listenerID": { + "protocolPort": { SchemaProps: spec.SchemaProps{ - Description: "listenerID is the ID of the Listener to which the resource is associated.", - Type: []string{"string"}, - Format: "", + Description: "protocolPort is the port on which the member is listening.", + Type: []string{"integer"}, + Format: "int32", }, }, - "projectID": { + "subnetID": { SchemaProps: spec.SchemaProps{ - Description: "projectID is the ID of the Project to which the resource is associated.", + Description: "subnetID is the ID of the subnet the member is on.", Type: []string{"string"}, Format: "", }, }, - }, - }, - }, + "weight": { + SchemaProps: spec.SchemaProps{ + Description: "weight is the weight of the member for load balancing.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "backup": { + SchemaProps: spec.SchemaProps{ + Description: "backup indicates whether this is a backup member.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "adminStateUp": { + SchemaProps: spec.SchemaProps{ + Description: "adminStateUp is the administrative state of the member.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "provisioningStatus": { + SchemaProps: spec.SchemaProps{ + Description: "provisioningStatus is the provisioning status of the member.", + Type: []string{"string"}, + Format: "", + }, + }, + "operatingStatus": { + SchemaProps: spec.SchemaProps{ + Description: "operatingStatus is the operating status of the member.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LBPoolResourceSpec 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: "", + }, + }, + "lbAlgorithm": { + SchemaProps: spec.SchemaProps{ + Description: "lbAlgorithm is the load balancing algorithm used to distribute traffic to the pool's members.", + Type: []string{"string"}, + Format: "", + }, + }, + "protocol": { + SchemaProps: spec.SchemaProps{ + Description: "protocol is the protocol used by the pool and its members for traffic.", + Type: []string{"string"}, + Format: "", + }, + }, + "loadBalancerRef": { + SchemaProps: spec.SchemaProps{ + Description: "loadBalancerRef is a reference to the ORC LoadBalancer which this pool is associated with. Either loadBalancerRef or listenerRef must be specified.", + Type: []string{"string"}, + Format: "", + }, + }, + "listenerRef": { + SchemaProps: spec.SchemaProps{ + Description: "listenerRef is a reference to the ORC Listener which this pool is associated with as the default pool. Either loadBalancerRef or listenerRef must be specified.", + Type: []string{"string"}, + Format: "", + }, + }, + "projectRef": { + SchemaProps: spec.SchemaProps{ + Description: "projectRef is a reference to the ORC Project which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "adminStateUp": { + SchemaProps: spec.SchemaProps{ + Description: "adminStateUp is the administrative state of the pool, which is up (true) or down (false).", + Type: []string{"boolean"}, + Format: "", + }, + }, + "sessionPersistence": { + SchemaProps: spec.SchemaProps{ + Description: "sessionPersistence is the session persistence configuration for the pool.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolSessionPersistence"), + }, + }, + "tlsEnabled": { + SchemaProps: spec.SchemaProps{ + Description: "tlsEnabled enables backend re-encryption when set to true. Requires TERMINATED_HTTPS or HTTPS protocol on the listener.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "tlsContainerRef": { + SchemaProps: spec.SchemaProps{ + Description: "tlsContainerRef is a reference to a secret containing a PKCS12 format certificate/key bundle for backend re-encryption.", + Type: []string{"string"}, + Format: "", + }, + }, + "caTLSContainerRef": { + SchemaProps: spec.SchemaProps{ + Description: "caTLSContainerRef is a reference to a secret containing the CA certificate for backend re-encryption.", + Type: []string{"string"}, + Format: "", + }, + }, + "crlContainerRef": { + SchemaProps: spec.SchemaProps{ + Description: "crlContainerRef is a reference to a secret containing the CA revocation list for backend re-encryption.", + Type: []string{"string"}, + Format: "", + }, + }, + "tlsCiphers": { + SchemaProps: spec.SchemaProps{ + Description: "tlsCiphers is a colon-separated list of ciphers for backend TLS connections.", + 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 for backend TLS connections.", + 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 backend TLS connections. Available protocols: h2, http/1.0, http/1.1.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + 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 pool.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "members": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "members is a list of backend members for this pool.", + 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.LBPoolMemberSpec"), + }, + }, + }, + }, + }, + }, + Required: []string{"lbAlgorithm", "protocol"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolMemberSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolSessionPersistence"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LBPoolResourceStatus 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: "", + }, + }, + "lbAlgorithm": { + SchemaProps: spec.SchemaProps{ + Description: "lbAlgorithm is the load balancing algorithm used by the pool.", + Type: []string{"string"}, + Format: "", + }, + }, + "protocol": { + SchemaProps: spec.SchemaProps{ + Description: "protocol is the protocol used by the pool.", + Type: []string{"string"}, + Format: "", + }, + }, + "loadBalancerIDs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "loadBalancerIDs is the list of LoadBalancer IDs this pool is associated with.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "listenerIDs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "listenerIDs is the list of Listener IDs this pool is associated with.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "projectID": { + SchemaProps: spec.SchemaProps{ + Description: "projectID is the ID of the Project this pool is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "adminStateUp": { + SchemaProps: spec.SchemaProps{ + Description: "adminStateUp is the administrative state of the pool, which is up (true) or down (false).", + Type: []string{"boolean"}, + Format: "", + }, + }, + "provisioningStatus": { + SchemaProps: spec.SchemaProps{ + Description: "provisioningStatus is the provisioning status of the pool.", + Type: []string{"string"}, + Format: "", + }, + }, + "operatingStatus": { + SchemaProps: spec.SchemaProps{ + Description: "operatingStatus is the operating status of the pool.", + Type: []string{"string"}, + Format: "", + }, + }, + "healthMonitorID": { + SchemaProps: spec.SchemaProps{ + Description: "healthMonitorID is the ID of the health monitor associated with this pool.", + Type: []string{"string"}, + Format: "", + }, + }, + "members": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "members is the list of members in this pool with their details.", + 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.LBPoolMemberStatus"), + }, + }, + }, + }, + }, + "sessionPersistence": { + SchemaProps: spec.SchemaProps{ + Description: "sessionPersistence is the session persistence configuration.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolSessionPersistence"), + }, + }, + "tlsEnabled": { + SchemaProps: spec.SchemaProps{ + Description: "tlsEnabled indicates whether backend re-encryption is enabled.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "tlsContainerRef": { + SchemaProps: spec.SchemaProps{ + Description: "tlsContainerRef is the reference to the TLS container.", + Type: []string{"string"}, + Format: "", + }, + }, + "caTLSContainerRef": { + SchemaProps: spec.SchemaProps{ + Description: "caTLSContainerRef is the reference to the CA TLS container.", + Type: []string{"string"}, + Format: "", + }, + }, + "crlContainerRef": { + SchemaProps: spec.SchemaProps{ + Description: "crlContainerRef is the reference to the CRL container.", + Type: []string{"string"}, + Format: "", + }, + }, + "tlsCiphers": { + SchemaProps: spec.SchemaProps{ + Description: "tlsCiphers is the list of TLS ciphers for backend connections.", + Type: []string{"string"}, + Format: "", + }, + }, + "tlsVersions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "tlsVersions is the list of TLS versions for backend connections.", + 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": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "alpnProtocols is the list of ALPN protocols for backend connections.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + 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: "", + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolMemberStatus", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolSessionPersistence"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolSessionPersistence(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LBPoolSessionPersistence represents session persistence configuration for a pool.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "type is the type of session persistence.", + Type: []string{"string"}, + Format: "", + }, + }, + "cookieName": { + SchemaProps: spec.SchemaProps{ + Description: "cookieName is the name of the cookie if persistence type is APP_COOKIE. Required when type is APP_COOKIE.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type"}, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LBPoolSpec 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.LBPoolImport"), + }, + }, + "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.LBPoolResourceSpec"), + }, + }, + "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.LBPoolImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_LBPoolStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LBPoolStatus 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.LBPoolResourceStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.LBPoolResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, } } diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index a4ad2fae..3e45f52f 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -166,6 +166,9 @@ var resources []templateFields = []templateFields{ { Name: "Listener", }, + { + Name: "LBPool", + }, } // These resources won't be generated diff --git a/config/crd/bases/openstack.k-orc.cloud_lbpools.yaml b/config/crd/bases/openstack.k-orc.cloud_lbpools.yaml new file mode 100644 index 00000000..222fb75f --- /dev/null +++ b/config/crd/bases/openstack.k-orc.cloud_lbpools.yaml @@ -0,0 +1,751 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: lbpools.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: LBPool + listKind: LBPoolList + plural: lbpools + singular: lbpool + 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: LBPool 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 + lbAlgorithm: + description: lbAlgorithm filters by the load balancing algorithm. + enum: + - LEAST_CONNECTIONS + - ROUND_ROBIN + - SOURCE_IP + - SOURCE_IP_PORT + type: string + listenerRef: + description: listenerRef filters by the Listener this pool + is associated with. + maxLength: 253 + minLength: 1 + type: string + loadBalancerRef: + description: loadBalancerRef filters by the LoadBalancer this + pool is associated with. + 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 + projectRef: + description: projectRef filters by the Project this pool is + associated with. + maxLength: 253 + minLength: 1 + type: string + protocol: + description: protocol filters by the protocol used by the + pool. + enum: + - HTTP + - HTTPS + - PROXY + - PROXYV2 + - SCTP + - TCP + - UDP + 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 + 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 pool, which is up (true) + or down (false). + type: boolean + alpnProtocols: + description: |- + alpnProtocols is a list of ALPN protocols for backend TLS connections. + Available protocols: h2, http/1.0, http/1.1. + items: + maxLength: 32 + type: string + maxItems: 10 + type: array + x-kubernetes-list-type: set + caTLSContainerRef: + description: |- + caTLSContainerRef is a reference to a secret containing the CA + certificate for backend re-encryption. + maxLength: 255 + type: string + x-kubernetes-validations: + - message: caTLSContainerRef is immutable + rule: self == oldSelf + crlContainerRef: + description: |- + crlContainerRef is a reference to a secret containing the CA + revocation list for backend re-encryption. + maxLength: 255 + type: string + x-kubernetes-validations: + - message: crlContainerRef is immutable + rule: self == oldSelf + description: + description: description is a human-readable description for the + resource. + maxLength: 255 + minLength: 1 + type: string + lbAlgorithm: + description: |- + lbAlgorithm is the load balancing algorithm used to distribute traffic + to the pool's members. + enum: + - LEAST_CONNECTIONS + - ROUND_ROBIN + - SOURCE_IP + - SOURCE_IP_PORT + type: string + x-kubernetes-validations: + - message: lbAlgorithm is immutable + rule: self == oldSelf + listenerRef: + description: |- + listenerRef is a reference to the ORC Listener which this pool is + associated with as the default pool. Either loadBalancerRef or listenerRef + must be specified. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: listenerRef is immutable + rule: self == oldSelf + loadBalancerRef: + description: |- + loadBalancerRef is a reference to the ORC LoadBalancer which this pool + is associated with. Either loadBalancerRef or listenerRef must be specified. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: loadBalancerRef is immutable + rule: self == oldSelf + members: + description: members is a list of backend members for this pool. + items: + description: LBPoolMemberSpec defines a member of an LB pool. + properties: + address: + description: address is the IP address of the member to + receive traffic. + maxLength: 45 + minLength: 1 + type: string + adminStateUp: + description: adminStateUp is the administrative state of + the member (up=true, down=false). + type: boolean + backup: + description: |- + backup indicates whether this is a backup member. Backup members only + receive traffic when all non-backup members are down. + type: boolean + name: + description: name is a human-readable name for the member. + maxLength: 255 + type: string + protocolPort: + description: protocolPort is the port on which the member + application is listening. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + subnetRef: + description: subnetRef is a reference to the ORC Subnet + where the member resides. + maxLength: 253 + minLength: 1 + type: string + weight: + description: |- + weight is the relative portion of traffic this member should receive. + A member with weight 10 receives 5x the traffic of a member with weight 2. + Default is 1. + format: int32 + maximum: 256 + minimum: 0 + type: integer + required: + - address + - protocolPort + type: object + maxItems: 256 + type: array + x-kubernetes-list-type: atomic + 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 + protocol: + description: protocol is the protocol used by the pool and its + members for traffic. + enum: + - HTTP + - HTTPS + - PROXY + - PROXYV2 + - SCTP + - TCP + - UDP + type: string + x-kubernetes-validations: + - message: protocol is immutable + rule: self == oldSelf + sessionPersistence: + description: sessionPersistence is the session persistence configuration + for the pool. + properties: + cookieName: + description: |- + cookieName is the name of the cookie if persistence type is APP_COOKIE. + Required when type is APP_COOKIE. + maxLength: 255 + type: string + type: + description: type is the type of session persistence. + enum: + - APP_COOKIE + - HTTP_COOKIE + - SOURCE_IP + type: string + required: + - type + type: object + tags: + description: tags is a list of tags which will be applied to the + pool. + items: + maxLength: 255 + minLength: 1 + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + tlsCiphers: + description: tlsCiphers is a colon-separated list of ciphers for + backend TLS connections. + maxLength: 2048 + type: string + tlsContainerRef: + description: |- + tlsContainerRef is a reference to a secret containing a PKCS12 format + certificate/key bundle for backend re-encryption. + maxLength: 255 + type: string + tlsEnabled: + description: |- + tlsEnabled enables backend re-encryption when set to true. Requires + TERMINATED_HTTPS or HTTPS protocol on the listener. + type: boolean + x-kubernetes-validations: + - message: tlsEnabled is immutable + rule: self == oldSelf + tlsVersions: + description: |- + tlsVersions is a list of TLS protocol versions to be used for backend + TLS connections. + items: + maxLength: 32 + type: string + maxItems: 10 + type: array + x-kubernetes-list-type: set + required: + - lbAlgorithm + - protocol + type: object + x-kubernetes-validations: + - message: either loadBalancerRef or listenerRef must be specified + rule: has(self.loadBalancerRef) || has(self.listenerRef) + 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 pool, + which is up (true) or down (false). + type: boolean + alpnProtocols: + description: alpnProtocols is the list of ALPN protocols for backend + connections. + items: + maxLength: 32 + type: string + maxItems: 10 + type: array + x-kubernetes-list-type: atomic + caTLSContainerRef: + description: caTLSContainerRef is the reference to the CA TLS + container. + maxLength: 1024 + type: string + crlContainerRef: + description: crlContainerRef is the reference to the CRL container. + maxLength: 1024 + type: string + description: + description: description is a human-readable description for the + resource. + maxLength: 1024 + type: string + healthMonitorID: + description: healthMonitorID is the ID of the health monitor associated + with this pool. + maxLength: 1024 + type: string + lbAlgorithm: + description: lbAlgorithm is the load balancing algorithm used + by the pool. + maxLength: 64 + type: string + listenerIDs: + description: listenerIDs is the list of Listener IDs this pool + is associated with. + items: + maxLength: 1024 + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: atomic + loadBalancerIDs: + description: loadBalancerIDs is the list of LoadBalancer IDs this + pool is associated with. + items: + maxLength: 1024 + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: atomic + members: + description: members is the list of members in this pool with + their details. + items: + description: LBPoolMemberStatus represents the observed state + of a pool member. + properties: + address: + description: address is the IP address of the member. + maxLength: 64 + type: string + adminStateUp: + description: adminStateUp is the administrative state of + the member. + type: boolean + backup: + description: backup indicates whether this is a backup member. + type: boolean + id: + description: id is the unique identifier of the member in + OpenStack. + maxLength: 1024 + type: string + name: + description: name is the human-readable name for the member. + maxLength: 255 + type: string + operatingStatus: + description: operatingStatus is the operating status of + the member. + maxLength: 64 + type: string + protocolPort: + description: protocolPort is the port on which the member + is listening. + format: int32 + type: integer + provisioningStatus: + description: provisioningStatus is the provisioning status + of the member. + maxLength: 64 + type: string + subnetID: + description: subnetID is the ID of the subnet the member + is on. + maxLength: 1024 + type: string + weight: + description: weight is the weight of the member for load + balancing. + format: int32 + type: integer + type: object + maxItems: 256 + type: array + x-kubernetes-list-type: atomic + 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 pool. + maxLength: 1024 + type: string + projectID: + description: projectID is the ID of the Project this pool is associated + with. + maxLength: 1024 + type: string + protocol: + description: protocol is the protocol used by the pool. + maxLength: 64 + type: string + provisioningStatus: + description: provisioningStatus is the provisioning status of + the pool. + maxLength: 1024 + type: string + sessionPersistence: + description: sessionPersistence is the session persistence configuration. + properties: + cookieName: + description: |- + cookieName is the name of the cookie if persistence type is APP_COOKIE. + Required when type is APP_COOKIE. + maxLength: 255 + type: string + type: + description: type is the type of session persistence. + enum: + - APP_COOKIE + - HTTP_COOKIE + - SOURCE_IP + type: string + required: + - type + type: object + 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 + tlsCiphers: + description: tlsCiphers is the list of TLS ciphers for backend + connections. + maxLength: 2048 + type: string + tlsContainerRef: + description: tlsContainerRef is the reference to the TLS container. + maxLength: 1024 + type: string + tlsEnabled: + description: tlsEnabled indicates whether backend re-encryption + is enabled. + type: boolean + tlsVersions: + description: tlsVersions is the list of TLS versions for backend + connections. + items: + maxLength: 32 + type: string + maxItems: 10 + type: array + x-kubernetes-list-type: atomic + 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 bc9e50c1..4bf07ed8 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_lbpools.yaml - bases/openstack.k-orc.cloud_listeners.yaml - bases/openstack.k-orc.cloud_loadbalancers.yaml - bases/openstack.k-orc.cloud_networks.yaml diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 085af8a7..05701470 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_lbpool.yaml - openstack_v1alpha1_listener.yaml - openstack_v1alpha1_loadbalancer.yaml - openstack_v1alpha1_network.yaml diff --git a/config/samples/openstack_v1alpha1_lbpool.yaml b/config/samples/openstack_v1alpha1_lbpool.yaml index 0e086a77..a8b65ecf 100644 --- a/config/samples/openstack_v1alpha1_lbpool.yaml +++ b/config/samples/openstack_v1alpha1_lbpool.yaml @@ -5,10 +5,29 @@ metadata: name: lbpool-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 LBPool - # TODO(scaffolding): Add all fields the resource supports + name: my-pool + description: Sample LBPool with HTTP backend members + loadBalancerRef: loadbalancer-sample + protocol: HTTP + lbAlgorithm: ROUND_ROBIN + adminStateUp: true + sessionPersistence: + type: SOURCE_IP + tags: + - environment:production + - team:platform + members: + - name: backend-1 + address: 10.0.0.10 + protocolPort: 8080 + weight: 5 + adminStateUp: true + - name: backend-2 + address: 10.0.0.11 + protocolPort: 8080 + weight: 3 + backup: true diff --git a/config/samples/openstack_v1alpha1_listener.yaml b/config/samples/openstack_v1alpha1_listener.yaml index f3c7081e..306e999b 100644 --- a/config/samples/openstack_v1alpha1_listener.yaml +++ b/config/samples/openstack_v1alpha1_listener.yaml @@ -15,3 +15,13 @@ spec: protocol: HTTP protocolPort: 80 adminStateUp: true + connectionLimit: 10000 + timeoutClientData: 50000 + timeoutMemberConnect: 5000 + timeoutMemberData: 50000 + allowedCIDRs: + - "10.0.0.0/8" + - "192.168.0.0/16" + tags: + - environment:production + - team:platform diff --git a/internal/controllers/lbpool/actuator.go b/internal/controllers/lbpool/actuator.go index 5e08ec7e..e185de14 100644 --- a/internal/controllers/lbpool/actuator.go +++ b/internal/controllers/lbpool/actuator.go @@ -18,7 +18,9 @@ package lbpool import ( "context" + "fmt" "iter" + "slices" "time" "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/pools" @@ -45,11 +47,22 @@ 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 lbpoolAvailablePollingPeriod = 15 * time.Second + // The frequency to poll when waiting for the resource to be deleted const lbpoolDeletingPollingPeriod = 15 * time.Second +// Provisioning status constants for LBPool +const ( + PoolProvisioningStatusActive = "ACTIVE" + PoolProvisioningStatusError = "ERROR" + PoolProvisioningStatusPendingCreate = "PENDING_CREATE" + PoolProvisioningStatusPendingUpdate = "PENDING_UPDATE" + PoolProvisioningStatusPendingDelete = "PENDING_DELETE" +) + type lbpoolActuator struct { osClient osclients.LBPoolClient k8sClient client.Client @@ -67,6 +80,17 @@ func (actuator lbpoolActuator) GetOSResourceByID(ctx context.Context, id string) if err != nil { return nil, progress.WrapError(err) } + + members := make([]pools.Member, 0, len(resource.Members)) + for _, memberId := range resource.Members { + member, err := actuator.osClient.GetMember(ctx, id, memberId.ID) + if err != nil { + return nil, progress.WrapError(err) + } + members = append(members, *member) + } + resource.Members = members + return resource, nil } @@ -76,39 +100,26 @@ func (actuator lbpoolActuator) ListOSResourcesForAdoption(ctx context.Context, o 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 := pools.ListOpts{ - Name: getResourceName(orcObject), - Description: ptr.Deref(resourceSpec.Description, ""), + Name: getResourceName(orcObject), + Protocol: string(resourceSpec.Protocol), + LBMethod: string(resourceSpec.LBAlgorithm), } return actuator.osClient.ListLBPools(ctx, listOpts), true } func (actuator lbpoolActuator) 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, rs := dependency.FetchDependency[*orcv1alpha1.LoadBalancer, orcv1alpha1.LoadBalancer]( + loadBalancer, rs := dependency.FetchDependency( ctx, actuator.k8sClient, obj.Namespace, filter.LoadBalancerRef, "LoadBalancer", func(dep *orcv1alpha1.LoadBalancer) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) - listener, rs := dependency.FetchDependency[*orcv1alpha1.Listener, orcv1alpha1.Listener]( - ctx, actuator.k8sClient, obj.Namespace, - filter.ListenerRef, "Listener", - func(dep *orcv1alpha1.Listener) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, - ) - reconcileStatus = reconcileStatus.WithReconcileStatus(rs) - - project, rs := dependency.FetchDependency[*orcv1alpha1.Project, orcv1alpha1.Project]( + project, rs := dependency.FetchDependency( ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", func(dep *orcv1alpha1.Project) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, @@ -120,12 +131,19 @@ func (actuator lbpoolActuator) ListOSResourcesForImport(ctx context.Context, obj } listOpts := pools.ListOpts{ - Name: string(ptr.Deref(filter.Name, "")), - Description: string(ptr.Deref(filter.Description, "")), - LoadBalancerID: ptr.Deref(loadBalancer.Status.ID, ""), - ListenerID: ptr.Deref(listener.Status.ID, ""), - ProjectID: ptr.Deref(project.Status.ID, ""), - // TODO(scaffolding): Add more import filters + Name: string(ptr.Deref(filter.Name, "")), + LoadbalancerID: ptr.Deref(loadBalancer.Status.ID, ""), + ProjectID: ptr.Deref(project.Status.ID, ""), + LBMethod: string(ptr.Deref(filter.LBAlgorithm, "")), + Protocol: string(ptr.Deref(filter.Protocol, "")), + } + + if len(filter.Tags) > 0 { + tags := make([]string, len(filter.Tags)) + for i := range filter.Tags { + tags[i] = string(filter.Tags[i]) + } + listOpts.Tags = tags } return actuator.osClient.ListLBPools(ctx, listOpts), reconcileStatus @@ -179,21 +197,62 @@ func (actuator lbpoolActuator) CreateResource(ctx context.Context, obj orcObject projectID = ptr.Deref(project.Status.ID, "") } } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus } + createOpts := pools.CreateOpts{ - Name: getResourceName(obj), - Description: ptr.Deref(resource.Description, ""), - LoadBalancerID: loadBalancerID, - ListenerID: listenerID, - ProjectID: projectID, - // TODO(scaffolding): Add more fields + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + LBMethod: pools.LBMethod(resource.LBAlgorithm), + Protocol: pools.Protocol(resource.Protocol), + LoadbalancerID: loadBalancerID, + ListenerID: listenerID, + ProjectID: projectID, + AdminStateUp: resource.AdminStateUp, + TLSEnabled: ptr.Deref(resource.TLSEnabled, false), + TLSContainerRef: ptr.Deref(resource.TLSContainerRef, ""), + CATLSContainerRef: ptr.Deref(resource.CATLSContainerRef, ""), + CRLContainerRef: ptr.Deref(resource.CRLContainerRef, ""), + TLSCiphers: ptr.Deref(resource.TLSCiphers, ""), + } + + if resource.SessionPersistence != nil { + createOpts.Persistence = &pools.SessionPersistence{ + Type: string(resource.SessionPersistence.Type), + CookieName: ptr.Deref(resource.SessionPersistence.CookieName, ""), + } + } + + if len(resource.TLSVersions) > 0 { + tlsVersions := make([]pools.TLSVersion, len(resource.TLSVersions)) + for i := range resource.TLSVersions { + tlsVersions[i] = pools.TLSVersion(resource.TLSVersions[i]) + } + createOpts.TLSVersions = tlsVersions + } + + if len(resource.ALPNProtocols) > 0 { + createOpts.ALPNProtocols = resource.ALPNProtocols + } + + 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 } osResource, err := actuator.osClient.CreateLBPool(ctx, createOpts) if err != nil { - // We should require the spec to be updated before retrying a create which returned a conflict + // 409 Conflict typically means the LoadBalancer is in a pending state (immutable). + // Wait for it to become available and retry. + if orcerrors.IsConflict(err) { + return nil, progress.WaitingOnOpenStack(progress.WaitingOnReady, lbpoolAvailablePollingPeriod) + } + // We should require the spec to be updated before retrying a create which returned a non-retryable error if !orcerrors.IsRetryable(err) { err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) } @@ -204,10 +263,22 @@ func (actuator lbpoolActuator) CreateResource(ctx context.Context, obj orcObject } func (actuator lbpoolActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { - if resource.Status == PoolStatusDeleting { + switch resource.ProvisioningStatus { + case PoolProvisioningStatusPendingDelete: + return progress.WaitingOnOpenStack(progress.WaitingOnReady, lbpoolDeletingPollingPeriod) + case PoolProvisioningStatusPendingCreate, PoolProvisioningStatusPendingUpdate: + // We can't delete a pool that's in a pending state, so we need to wait for it to become ACTIVE return progress.WaitingOnOpenStack(progress.WaitingOnReady, lbpoolDeletingPollingPeriod) } - return progress.WrapError(actuator.osClient.DeleteLBPool(ctx, resource.ID)) + + err := actuator.osClient.DeleteLBPool(ctx, resource.ID) + // 409 Conflict means the loadbalancer is already in PENDING_DELETE state. + // Treat this as success and let the controller poll for deletion completion. + if orcerrors.IsConflict(err) { + return progress.WaitingOnOpenStack(progress.WaitingOnReady, lbpoolDeletingPollingPeriod) + } + + return progress.WrapError(err) } func (actuator lbpoolActuator) updateResource(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { @@ -223,8 +294,13 @@ func (actuator lbpoolActuator) updateResource(ctx context.Context, obj orcObject handleNameUpdate(&updateOpts, obj, osResource) handleDescriptionUpdate(&updateOpts, resource, osResource) - - // TODO(scaffolding): add handler for all fields supporting mutability + handleAdminStateUpUpdate(&updateOpts, resource, osResource) + handleSessionPersistenceUpdate(&updateOpts, resource, osResource) + handleTLSContainerRefUpdate(&updateOpts, resource, osResource) + handleTLSCiphersUpdate(&updateOpts, resource, osResource) + handleTLSVersionsUpdate(&updateOpts, resource, osResource) + handleALPNProtocolsUpdate(&updateOpts, resource, osResource) + handleTagsUpdate(&updateOpts, resource, osResource) needsUpdate, err := needsUpdate(updateOpts) if err != nil { @@ -238,9 +314,10 @@ func (actuator lbpoolActuator) updateResource(ctx context.Context, obj orcObject _, err = actuator.osClient.UpdateLBPool(ctx, osResource.ID, updateOpts) - // We should require the spec to be updated before retrying an update which returned a conflict + // 409 Conflict typically means the LoadBalancer is in a pending state (immutable). + // Wait for it to become available and retry. if orcerrors.IsConflict(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + return progress.WaitingOnOpenStack(progress.WaitingOnReady, lbpoolAvailablePollingPeriod) } if err != nil { @@ -278,9 +355,273 @@ func handleDescriptionUpdate(updateOpts *pools.UpdateOpts, resource *resourceSpe } } +func handleAdminStateUpUpdate(updateOpts *pools.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + if resource.AdminStateUp != nil && *resource.AdminStateUp != osResource.AdminStateUp { + updateOpts.AdminStateUp = resource.AdminStateUp + } +} + +func handleSessionPersistenceUpdate(updateOpts *pools.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + // Check if we need to clear session persistence + if resource.SessionPersistence == nil { + if osResource.Persistence.Type != "" { + // Clear session persistence by setting an empty struct + updateOpts.Persistence = &pools.SessionPersistence{} + } + return + } + + // Check if session persistence needs to be updated + specPersistence := resource.SessionPersistence + osPersistence := osResource.Persistence + + if string(specPersistence.Type) != osPersistence.Type || + ptr.Deref(specPersistence.CookieName, "") != osPersistence.CookieName { + updateOpts.Persistence = &pools.SessionPersistence{ + Type: string(specPersistence.Type), + CookieName: ptr.Deref(specPersistence.CookieName, ""), + } + } +} + +func handleTLSContainerRefUpdate(updateOpts *pools.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + tlsContainerRef := ptr.Deref(resource.TLSContainerRef, "") + if osResource.TLSContainerRef != tlsContainerRef { + updateOpts.TLSContainerRef = &tlsContainerRef + } +} + +func handleTLSCiphersUpdate(updateOpts *pools.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + tlsCiphers := ptr.Deref(resource.TLSCiphers, "") + if osResource.TLSCiphers != tlsCiphers { + updateOpts.TLSCiphers = &tlsCiphers + } +} + +func handleTLSVersionsUpdate(updateOpts *pools.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + specVersions := resource.TLSVersions + osVersions := osResource.TLSVersions + + if len(specVersions) == 0 && len(osVersions) == 0 { + return + } + + // Compare slices + if !slices.Equal(specVersions, osVersions) { + tlsVersions := make([]pools.TLSVersion, len(specVersions)) + for i := range specVersions { + tlsVersions[i] = pools.TLSVersion(specVersions[i]) + } + updateOpts.TLSVersions = &tlsVersions + } +} + +func handleALPNProtocolsUpdate(updateOpts *pools.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + specProtocols := resource.ALPNProtocols + osProtocols := osResource.ALPNProtocols + + if len(specProtocols) == 0 && len(osProtocols) == 0 { + return + } + + if !slices.Equal(specProtocols, osProtocols) { + updateOpts.ALPNProtocols = &specProtocols + } +} + +func handleTagsUpdate(updateOpts *pools.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 + } +} + +// createMember creates a new member in the pool. +func (actuator lbpoolActuator) createMember(ctx context.Context, obj orcObjectPT, poolID string, memberSpec orcv1alpha1.LBPoolMemberSpec) progress.ReconcileStatus { + log := ctrl.LoggerFrom(ctx) + + subnet, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + memberSpec.SubnetRef, "Subnet", + func(dep *orcv1alpha1.Subnet) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { + return rs + } + + address := string(memberSpec.Address) + log.V(logging.Debug).Info("Creating member", "address", address, "port", memberSpec.ProtocolPort) + + createOpts := pools.CreateMemberOpts{ + Address: address, + ProtocolPort: int(memberSpec.ProtocolPort), + Name: ptr.Deref(memberSpec.Name, ""), + Weight: ptr.To(int(ptr.Deref(memberSpec.Weight, 1))), + Backup: memberSpec.Backup, + AdminStateUp: memberSpec.AdminStateUp, + SubnetID: ptr.Deref(subnet.Status.ID, ""), + } + + _, err := actuator.osClient.CreateMember(ctx, poolID, createOpts) + if err != nil { + if orcerrors.IsConflict(err) { + return progress.WaitingOnOpenStack(progress.WaitingOnReady, lbpoolAvailablePollingPeriod) + } + return progress.WrapError(err) + } + + return nil +} + +// updateMember updates an existing member if its spec differs from the current state. +// Returns true if the member was updated. +func (actuator lbpoolActuator) updateMember(ctx context.Context, poolID string, current *pools.Member, memberSpec orcv1alpha1.LBPoolMemberSpec) (bool, progress.ReconcileStatus) { + log := ctrl.LoggerFrom(ctx) + + updateOpts := pools.UpdateMemberOpts{} + needsUpdate := false + + desiredName := ptr.Deref(memberSpec.Name, "") + if current.Name != desiredName { + updateOpts.Name = &desiredName + needsUpdate = true + } + + desiredWeight := int(ptr.Deref(memberSpec.Weight, 1)) + if current.Weight != desiredWeight { + updateOpts.Weight = &desiredWeight + needsUpdate = true + } + + desiredBackup := ptr.Deref(memberSpec.Backup, false) + if current.Backup != desiredBackup { + updateOpts.Backup = &desiredBackup + needsUpdate = true + } + + desiredAdminStateUp := ptr.Deref(memberSpec.AdminStateUp, true) + if current.AdminStateUp != desiredAdminStateUp { + updateOpts.AdminStateUp = &desiredAdminStateUp + needsUpdate = true + } + + if !needsUpdate { + return false, nil + } + + log.V(logging.Debug).Info("Updating member", "memberID", current.ID) + _, err := actuator.osClient.UpdateMember(ctx, poolID, current.ID, updateOpts) + if err != nil { + if orcerrors.IsConflict(err) { + return false, progress.WaitingOnOpenStack(progress.WaitingOnReady, lbpoolAvailablePollingPeriod) + } + return false, progress.WrapError(err) + } + + return true, nil +} + +// deleteMember deletes a member from the pool. +func (actuator lbpoolActuator) deleteMember(ctx context.Context, poolID string, member *pools.Member) progress.ReconcileStatus { + log := ctrl.LoggerFrom(ctx) + + log.V(logging.Debug).Info("Deleting member", "memberID", member.ID) + err := actuator.osClient.DeleteMember(ctx, poolID, member.ID) + if err != nil { + if orcerrors.IsConflict(err) { + return progress.WaitingOnOpenStack(progress.WaitingOnReady, lbpoolAvailablePollingPeriod) + } + return progress.WrapError(err) + } + + return nil +} + +// reconcileMembers reconciles the pool members. +func (actuator lbpoolActuator) reconcileMembers(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { + log := ctrl.LoggerFrom(ctx) + + // Check pool provisioning status - we can only modify members when pool is ACTIVE + if osResource.ProvisioningStatus != PoolProvisioningStatusActive { + log.V(logging.Debug).Info("Pool not in ACTIVE state, waiting before modifying members", + "status", osResource.ProvisioningStatus) + return progress.WaitingOnOpenStack(progress.WaitingOnReady, lbpoolAvailablePollingPeriod) + } + + resource := obj.Spec.Resource + if resource == nil { + resource = &orcv1alpha1.LBPoolResourceSpec{} + } + + // List existing members from OpenStack, keyed by address:port + currentMembers := make(map[string]*pools.Member) + for member, err := range actuator.osClient.ListMembers(ctx, osResource.ID, pools.ListMembersOpts{}) { + if err != nil { + return progress.WrapError(err) + } + key := fmt.Sprintf("%s:%d", member.Address, member.ProtocolPort) + currentMembers[key] = member + } + + // Build desired members map keyed by address:port + desiredMemberMap := make(map[string]orcv1alpha1.LBPoolMemberSpec) + for _, m := range resource.Members { + key := fmt.Sprintf("%s:%d", m.Address, m.ProtocolPort) + desiredMemberMap[key] = m + } + + // Create missing members + for _, memberSpec := range resource.Members { + memberKey := fmt.Sprintf("%s:%d", memberSpec.Address, memberSpec.ProtocolPort) + if _, exists := currentMembers[memberKey]; !exists { + if rs := actuator.createMember(ctx, obj, osResource.ID, memberSpec); rs != nil { + return rs + } + return progress.NeedsRefresh() + } + } + + // Update existing members if changed + for key, current := range currentMembers { + memberSpec, exists := desiredMemberMap[key] + // Skip members marked for deletion + if !exists { + continue + } + + updated, rs := actuator.updateMember(ctx, osResource.ID, current, memberSpec) + if rs != nil { + return rs + } + if updated { + return progress.NeedsRefresh() + } + } + + // Delete extra members + for key, current := range currentMembers { + if _, exists := desiredMemberMap[key]; !exists { + if rs := actuator.deleteMember(ctx, osResource.ID, current); rs != nil { + return rs + } + return progress.NeedsRefresh() + } + } + + return nil +} + func (actuator lbpoolActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { return []resourceReconciler{ actuator.updateResource, + actuator.reconcileMembers, }, nil } diff --git a/internal/controllers/lbpool/actuator_test.go b/internal/controllers/lbpool/actuator_test.go index 57ce344f..8e2d3a8e 100644 --- a/internal/controllers/lbpool/actuator_test.go +++ b/internal/controllers/lbpool/actuator_test.go @@ -114,6 +114,223 @@ func TestHandleDescriptionUpdate(t *testing.T) { t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) } }) + } +} + +func TestHandleAdminStateUpUpdate(t *testing.T) { + testCases := []struct { + name string + newValue *bool + existingValue bool + expectChange bool + }{ + {name: "Identical true", newValue: ptr.To(true), existingValue: true, expectChange: false}, + {name: "Identical false", newValue: ptr.To(false), existingValue: false, expectChange: false}, + {name: "Different true to false", newValue: ptr.To(false), existingValue: true, expectChange: true}, + {name: "Different false to true", newValue: ptr.To(true), existingValue: false, expectChange: true}, + {name: "No value provided", newValue: nil, existingValue: true, expectChange: false}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.LBPoolResourceSpec{AdminStateUp: tt.newValue} + osResource := &osResourceT{AdminStateUp: tt.existingValue} + + updateOpts := pools.UpdateOpts{} + handleAdminStateUpUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + } +} + +func TestHandleSessionPersistenceUpdate(t *testing.T) { + testCases := []struct { + name string + newValue *orcv1alpha1.LBPoolSessionPersistence + existingValue pools.SessionPersistence + expectChange bool + }{ + { + name: "Identical SOURCE_IP", + newValue: &orcv1alpha1.LBPoolSessionPersistence{Type: orcv1alpha1.LBPoolSessionPersistenceSourceIP}, + existingValue: pools.SessionPersistence{Type: "SOURCE_IP"}, + expectChange: false, + }, + { + name: "Different type", + newValue: &orcv1alpha1.LBPoolSessionPersistence{Type: orcv1alpha1.LBPoolSessionPersistenceHTTPCookie}, + existingValue: pools.SessionPersistence{Type: "SOURCE_IP"}, + expectChange: true, + }, + { + name: "Add session persistence", + newValue: &orcv1alpha1.LBPoolSessionPersistence{Type: orcv1alpha1.LBPoolSessionPersistenceSourceIP}, + existingValue: pools.SessionPersistence{}, + expectChange: true, + }, + { + name: "Remove session persistence", + newValue: nil, + existingValue: pools.SessionPersistence{Type: "SOURCE_IP"}, + expectChange: true, + }, + { + name: "Both nil/empty", + newValue: nil, + existingValue: pools.SessionPersistence{}, + expectChange: false, + }, + { + name: "Identical APP_COOKIE with cookie name", + newValue: &orcv1alpha1.LBPoolSessionPersistence{Type: orcv1alpha1.LBPoolSessionPersistenceAppCookie, CookieName: ptr.To("mycookie")}, + existingValue: pools.SessionPersistence{Type: "APP_COOKIE", CookieName: "mycookie"}, + expectChange: false, + }, + { + name: "Different cookie name", + newValue: &orcv1alpha1.LBPoolSessionPersistence{Type: orcv1alpha1.LBPoolSessionPersistenceAppCookie, CookieName: ptr.To("newcookie")}, + existingValue: pools.SessionPersistence{Type: "APP_COOKIE", CookieName: "oldcookie"}, + expectChange: true, + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.LBPoolResourceSpec{SessionPersistence: tt.newValue} + osResource := &osResourceT{Persistence: tt.existingValue} + + updateOpts := pools.UpdateOpts{} + handleSessionPersistenceUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + } +} + +func TestHandleTLSContainerRefUpdate(t *testing.T) { + testCases := []struct { + name string + newValue *string + existingValue string + expectChange bool + }{ + {name: "Identical", newValue: ptr.To("ref"), existingValue: "ref", expectChange: false}, + {name: "Different", newValue: ptr.To("new-ref"), existingValue: "ref", expectChange: true}, + {name: "Add ref", newValue: ptr.To("ref"), existingValue: "", expectChange: true}, + {name: "Remove ref", newValue: nil, existingValue: "ref", expectChange: true}, + {name: "Both empty", newValue: nil, existingValue: "", expectChange: false}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.LBPoolResourceSpec{TLSContainerRef: tt.newValue} + osResource := &osResourceT{TLSContainerRef: tt.existingValue} + + updateOpts := pools.UpdateOpts{} + handleTLSContainerRefUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + } +} + +func TestHandleTLSCiphersUpdate(t *testing.T) { + testCases := []struct { + name string + newValue *string + existingValue string + expectChange bool + }{ + {name: "Identical", newValue: ptr.To("AES256:AES128"), existingValue: "AES256:AES128", expectChange: false}, + {name: "Different", newValue: ptr.To("AES256"), existingValue: "AES128", expectChange: true}, + {name: "Add ciphers", newValue: ptr.To("AES256"), existingValue: "", expectChange: true}, + {name: "Remove ciphers", newValue: nil, existingValue: "AES256", expectChange: true}, + {name: "Both empty", newValue: nil, existingValue: "", expectChange: false}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.LBPoolResourceSpec{TLSCiphers: tt.newValue} + osResource := &osResourceT{TLSCiphers: tt.existingValue} + + updateOpts := pools.UpdateOpts{} + handleTLSCiphersUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + } +} + +func TestHandleTLSVersionsUpdate(t *testing.T) { + testCases := []struct { + name string + newValue []string + existingValue []string + expectChange bool + }{ + {name: "Identical", newValue: []string{"TLSv1.2", "TLSv1.3"}, existingValue: []string{"TLSv1.2", "TLSv1.3"}, expectChange: false}, + {name: "Different", newValue: []string{"TLSv1.3"}, existingValue: []string{"TLSv1.2"}, expectChange: true}, + {name: "Add versions", newValue: []string{"TLSv1.2"}, existingValue: nil, expectChange: true}, + {name: "Remove versions", newValue: nil, existingValue: []string{"TLSv1.2"}, expectChange: true}, + {name: "Both empty", newValue: nil, existingValue: nil, expectChange: false}, + {name: "Different order", newValue: []string{"TLSv1.3", "TLSv1.2"}, existingValue: []string{"TLSv1.2", "TLSv1.3"}, expectChange: true}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.LBPoolResourceSpec{TLSVersions: tt.newValue} + osResource := &osResourceT{TLSVersions: tt.existingValue} + + updateOpts := pools.UpdateOpts{} + handleTLSVersionsUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + } +} + +func TestHandleALPNProtocolsUpdate(t *testing.T) { + testCases := []struct { + name string + newValue []string + existingValue []string + expectChange bool + }{ + {name: "Identical", newValue: []string{"h2", "http/1.1"}, existingValue: []string{"h2", "http/1.1"}, expectChange: false}, + {name: "Different", newValue: []string{"h2"}, existingValue: []string{"http/1.1"}, expectChange: true}, + {name: "Add protocols", newValue: []string{"h2"}, existingValue: nil, expectChange: true}, + {name: "Remove protocols", newValue: nil, existingValue: []string{"h2"}, expectChange: true}, + {name: "Both empty", newValue: nil, existingValue: nil, expectChange: false}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.LBPoolResourceSpec{ALPNProtocols: tt.newValue} + osResource := &osResourceT{ALPNProtocols: tt.existingValue} + + updateOpts := pools.UpdateOpts{} + handleALPNProtocolsUpdate(&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/lbpool/controller.go b/internal/controllers/lbpool/controller.go index 962ca88d..60c80685 100644 --- a/internal/controllers/lbpool/controller.go +++ b/internal/controllers/lbpool/controller.go @@ -120,6 +120,25 @@ var projectImportDependency = dependency.NewDependency[*orcv1alpha1.LBPoolList, }, ) +// Member dependencies - for resolving member subnet references +var subnetMemberDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.LBPoolList, *orcv1alpha1.Subnet]( + "spec.resource.members[*].subnetRef", + func(lbpool *orcv1alpha1.LBPool) []string { + resource := lbpool.Spec.Resource + if resource == nil { + return nil + } + var refs []string + for _, member := range resource.Members { + if member.SubnetRef != nil { + refs = append(refs, string(*member.SubnetRef)) + } + } + return refs + }, + finalizer, externalObjectFieldOwner, +) + // SetupWithManager sets up the controller with the Manager. func (c lbpoolReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) @@ -155,6 +174,11 @@ func (c lbpoolReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c return err } + subnetMemberWatchEventHandler, err := subnetMemberDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + builder := ctrl.NewControllerManagedBy(mgr). WithOptions(options). Watches(&orcv1alpha1.LoadBalancer{}, loadBalancerWatchEventHandler, @@ -178,6 +202,10 @@ func (c lbpoolReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c Watches(&orcv1alpha1.Project{}, projectImportWatchEventHandler, builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), ). + // Member subnet dependency + Watches(&orcv1alpha1.Subnet{}, subnetMemberWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Subnet{})), + ). For(&orcv1alpha1.LBPool{}) if err := errors.Join( @@ -187,6 +215,7 @@ func (c lbpoolReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c loadBalancerImportDependency.AddToManager(ctx, mgr), listenerImportDependency.AddToManager(ctx, mgr), projectImportDependency.AddToManager(ctx, mgr), + subnetMemberDependency.AddToManager(ctx, mgr), credentialsDependency.AddToManager(ctx, mgr), credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency), ); err != nil { diff --git a/internal/controllers/lbpool/status.go b/internal/controllers/lbpool/status.go index 6ecab9f5..f533c9ee 100644 --- a/internal/controllers/lbpool/status.go +++ b/internal/controllers/lbpool/status.go @@ -25,11 +25,6 @@ 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 PoolStatusAvailable = "available" -const PoolStatusInUse = "in-use" -const PoolStatusDeleting = "deleting" type lbpoolStatusWriter struct{} @@ -50,10 +45,12 @@ func (lbpoolStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.LBPool, return metav1.ConditionUnknown, nil } } - // TODO(scaffolding): add conditions for returning available, for instance: - if osResource.Status == PoolStatusAvailable || osResource.Status == PoolStatusInUse { + switch osResource.ProvisioningStatus { + case PoolProvisioningStatusActive: return metav1.ConditionTrue, nil + case PoolProvisioningStatusError: + return metav1.ConditionFalse, nil } // Otherwise we should continue to poll @@ -62,17 +59,100 @@ func (lbpoolStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.LBPool, func (lbpoolStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { resourceStatus := orcapplyconfigv1alpha1.LBPoolResourceStatus(). - WithLoadBalancerID(osResource.LoadBalancerID). - WithListenerID(osResource.ListenerID). + WithName(osResource.Name). + WithLBAlgorithm(osResource.LBMethod). + WithProtocol(osResource.Protocol). WithProjectID(osResource.ProjectID). - WithName(osResource.Name) - - // TODO(scaffolding): add all of the fields supported in the LBPoolResourceStatus struct - // If a zero-value isn't expected in the response, place it behind a conditional + WithProvisioningStatus(osResource.ProvisioningStatus). + WithOperatingStatus(osResource.OperatingStatus). + WithAdminStateUp(osResource.AdminStateUp). + WithTLSEnabled(osResource.TLSEnabled) if osResource.Description != "" { resourceStatus.WithDescription(osResource.Description) } + if osResource.MonitorID != "" { + resourceStatus.WithHealthMonitorID(osResource.MonitorID) + } + + if osResource.TLSContainerRef != "" { + resourceStatus.WithTLSContainerRef(osResource.TLSContainerRef) + } + + if osResource.CATLSContainerRef != "" { + resourceStatus.WithCATLSContainerRef(osResource.CATLSContainerRef) + } + + if osResource.CRLContainerRef != "" { + resourceStatus.WithCRLContainerRef(osResource.CRLContainerRef) + } + + if osResource.TLSCiphers != "" { + resourceStatus.WithTLSCiphers(osResource.TLSCiphers) + } + + if len(osResource.TLSVersions) > 0 { + resourceStatus.WithTLSVersions(osResource.TLSVersions...) + } + + if len(osResource.ALPNProtocols) > 0 { + resourceStatus.WithALPNProtocols(osResource.ALPNProtocols...) + } + + if len(osResource.Tags) > 0 { + resourceStatus.WithTags(osResource.Tags...) + } + + // Extract LoadBalancer IDs + if len(osResource.Loadbalancers) > 0 { + lbIDs := make([]string, len(osResource.Loadbalancers)) + for i, lb := range osResource.Loadbalancers { + lbIDs[i] = lb.ID + } + resourceStatus.WithLoadBalancerIDs(lbIDs...) + } + + // Extract Listener IDs + if len(osResource.Listeners) > 0 { + listenerIDs := make([]string, len(osResource.Listeners)) + for i, listener := range osResource.Listeners { + listenerIDs[i] = listener.ID + } + resourceStatus.WithListenerIDs(listenerIDs...) + } + + // Extract Member details + for i := range osResource.Members { + member := &osResource.Members[i] + + memberStatus := orcapplyconfigv1alpha1.LBPoolMemberStatus(). + WithID(member.ID). + WithAddress(member.Address). + WithProtocolPort(int32(member.ProtocolPort)). + WithWeight(int32(member.Weight)). + WithAdminStateUp(member.AdminStateUp). + WithBackup(member.Backup). + WithProvisioningStatus(member.ProvisioningStatus). + WithOperatingStatus(member.OperatingStatus) + if member.Name != "" { + memberStatus.WithName(member.Name) + } + if member.SubnetID != "" { + memberStatus.WithSubnetID(member.SubnetID) + } + resourceStatus.WithMembers(memberStatus) + } + + // Session persistence + if osResource.Persistence.Type != "" { + sessionPersistence := orcapplyconfigv1alpha1.LBPoolSessionPersistence(). + WithType(orcv1alpha1.LBPoolSessionPersistenceType(osResource.Persistence.Type)) + if osResource.Persistence.CookieName != "" { + sessionPersistence.WithCookieName(osResource.Persistence.CookieName) + } + resourceStatus.WithSessionPersistence(sessionPersistence) + } + statusApply.WithResource(resourceStatus) } diff --git a/internal/controllers/lbpool/tests/lbpool-create-full/00-assert.yaml b/internal/controllers/lbpool/tests/lbpool-create-full/00-assert.yaml index 52cb9011..cb2abfbd 100644 --- a/internal/controllers/lbpool/tests/lbpool-create-full/00-assert.yaml +++ b/internal/controllers/lbpool/tests/lbpool-create-full/00-assert.yaml @@ -4,10 +4,6 @@ kind: LBPool metadata: name: lbpool-create-full status: - resource: - name: lbpool-create-full-override - description: LBPool from "create full" test - # TODO(scaffolding): Add all fields the resource supports conditions: - type: Available status: "True" @@ -15,6 +11,15 @@ status: - type: Progressing status: "False" reason: Success + resource: + name: lbpool-create-full-override + description: LBPool from create full test + protocol: HTTP + lbAlgorithm: LEAST_CONNECTIONS + provisioningStatus: ACTIVE + adminStateUp: true + sessionPersistence: + type: SOURCE_IP --- apiVersion: kuttl.dev/v1beta1 kind: TestAssert @@ -27,17 +32,15 @@ resourceRefs: kind: LoadBalancer name: lbpool-create-full ref: loadBalancer - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Listener - name: lbpool-create-full - ref: listener - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Project - name: lbpool-create-full - ref: project assertAll: + # Dynamic checks that require CEL - celExpr: "lbpool.status.id != ''" - - celExpr: "lbpool.status.resource.loadBalancerID == loadBalancer.status.id" - - celExpr: "lbpool.status.resource.listenerID == listener.status.id" - - celExpr: "lbpool.status.resource.projectID == project.status.id" - # TODO(scaffolding): Add more checks + - celExpr: "lbpool.status.resource.loadBalancerIDs.exists(id, id == loadBalancer.status.id)" + - celExpr: "lbpool.status.resource.tags.exists(t, t == 'test')" + - celExpr: "lbpool.status.resource.tags.exists(t, t == 'lbpool')" + # Member assertions - need CEL for exists() checks + - celExpr: "size(lbpool.status.resource.members) == 2" + - celExpr: "lbpool.status.resource.members.exists(m, m.name == 'member-1' && m.address == '10.0.1.10' && m.protocolPort == 80)" + - celExpr: "lbpool.status.resource.members.exists(m, m.name == 'member-2' && m.address == '10.0.1.11' && m.protocolPort == 80)" + - celExpr: "lbpool.status.resource.members.exists(m, m.name == 'member-1' && m.weight == 5)" + - celExpr: "lbpool.status.resource.members.exists(m, m.name == 'member-2' && m.backup == true)" diff --git a/internal/controllers/lbpool/tests/lbpool-create-full/00-create-resource.yaml b/internal/controllers/lbpool/tests/lbpool-create-full/00-create-resource.yaml index 36d638eb..a2263f61 100644 --- a/internal/controllers/lbpool/tests/lbpool-create-full/00-create-resource.yaml +++ b/internal/controllers/lbpool/tests/lbpool-create-full/00-create-resource.yaml @@ -1,42 +1,40 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LoadBalancer +kind: Network metadata: name: lbpool-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 +kind: Subnet metadata: name: lbpool-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: lbpool-create-full + ipVersion: 4 + cidr: 10.0.1.0/24 --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Project +kind: LoadBalancer metadata: name: lbpool-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: + vipSubnetRef: lbpool-create-full --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LBPool @@ -44,14 +42,29 @@ metadata: name: lbpool-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: lbpool-create-full-override - description: LBPool from "create full" test + description: LBPool from create full test loadBalancerRef: lbpool-create-full - listenerRef: lbpool-create-full - projectRef: lbpool-create-full - # TODO(scaffolding): Add all fields the resource supports + protocol: HTTP + lbAlgorithm: LEAST_CONNECTIONS + adminStateUp: true + sessionPersistence: + type: SOURCE_IP + tags: + - "test" + - "lbpool" + members: + - name: member-1 + address: 10.0.1.10 + protocolPort: 80 + weight: 5 + adminStateUp: true + - name: member-2 + address: 10.0.1.11 + protocolPort: 80 + weight: 3 + backup: true \ No newline at end of file diff --git a/internal/controllers/lbpool/tests/lbpool-create-minimal/00-assert.yaml b/internal/controllers/lbpool/tests/lbpool-create-minimal/00-assert.yaml index d6700eb7..11d63839 100644 --- a/internal/controllers/lbpool/tests/lbpool-create-minimal/00-assert.yaml +++ b/internal/controllers/lbpool/tests/lbpool-create-minimal/00-assert.yaml @@ -4,9 +4,6 @@ kind: LBPool metadata: name: lbpool-create-minimal status: - resource: - name: lbpool-create-minimal - # TODO(scaffolding): Add all fields the resource supports conditions: - type: Available status: "True" @@ -14,6 +11,11 @@ status: - type: Progressing status: "False" reason: Success + resource: + name: lbpool-create-minimal + protocol: HTTP + lbAlgorithm: ROUND_ROBIN + provisioningStatus: ACTIVE --- apiVersion: kuttl.dev/v1beta1 kind: TestAssert @@ -22,6 +24,11 @@ resourceRefs: kind: LBPool name: lbpool-create-minimal ref: lbpool + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LoadBalancer + name: lbpool-create-minimal + ref: loadBalancer assertAll: + # Dynamic checks that require CEL - celExpr: "lbpool.status.id != ''" - # TODO(scaffolding): Add more checks + - celExpr: "lbpool.status.resource.loadBalancerIDs.exists(id, id == loadBalancer.status.id)" diff --git a/internal/controllers/lbpool/tests/lbpool-create-minimal/00-create-resource.yaml b/internal/controllers/lbpool/tests/lbpool-create-minimal/00-create-resource.yaml index dc44f1a4..86973cd4 100644 --- a/internal/controllers/lbpool/tests/lbpool-create-minimal/00-create-resource.yaml +++ b/internal/controllers/lbpool/tests/lbpool-create-minimal/00-create-resource.yaml @@ -1,14 +1,51 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LBPool +kind: Network metadata: name: lbpool-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: lbpool-create-minimal +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: lbpool-create-minimal + ipVersion: 4 + cidr: 10.0.0.0/24 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: lbpool-create-minimal +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + vipSubnetRef: lbpool-create-minimal +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-create-minimal +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + loadBalancerRef: lbpool-create-minimal + protocol: HTTP + lbAlgorithm: ROUND_ROBIN diff --git a/internal/controllers/lbpool/tests/lbpool-dependency/00-assert.yaml b/internal/controllers/lbpool/tests/lbpool-dependency/00-assert.yaml index b8dbad21..616642b4 100644 --- a/internal/controllers/lbpool/tests/lbpool-dependency/00-assert.yaml +++ b/internal/controllers/lbpool/tests/lbpool-dependency/00-assert.yaml @@ -21,11 +21,11 @@ metadata: status: conditions: - type: Available - message: Waiting for LoadBalancer/lbpool-dependency to be created + message: Waiting for LoadBalancer/lbpool-dependency-pending to be created status: "False" reason: Progressing - type: Progressing - message: Waiting for LoadBalancer/lbpool-dependency to be created + message: Waiting for LoadBalancer/lbpool-dependency-pending to be created status: "True" reason: Progressing --- diff --git a/internal/controllers/lbpool/tests/lbpool-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/lbpool/tests/lbpool-dependency/00-create-resources-missing-deps.yaml index 4fa47c94..10ffeab0 100644 --- a/internal/controllers/lbpool/tests/lbpool-dependency/00-create-resources-missing-deps.yaml +++ b/internal/controllers/lbpool/tests/lbpool-dependency/00-create-resources-missing-deps.yaml @@ -1,56 +1,98 @@ - --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LBPool +kind: Network metadata: - name: lbpool-dependency-no-loadbalancer + name: lbpool-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: lbpool-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: lbpool-dependency - # TODO(scaffolding): Add the necessary fields to create the resource + networkRef: lbpool-dependency + ipVersion: 4 + cidr: 10.0.3.0/24 --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LBPool +kind: LoadBalancer metadata: - name: lbpool-dependency-no-listener + name: lbpool-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: - listenerRef: lbpool-dependency - # TODO(scaffolding): Add the necessary fields to create the resource + vipSubnetRef: lbpool-dependency --- +# This LBPool is waiting for a LoadBalancer that doesn't exist yet apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LBPool metadata: - name: lbpool-dependency-no-project + name: lbpool-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: - projectRef: lbpool-dependency - # TODO(scaffolding): Add the necessary fields to create the resource + loadBalancerRef: lbpool-dependency-pending + protocol: HTTP + lbAlgorithm: ROUND_ROBIN --- +# This LBPool is waiting for a Secret that doesn't exist yet apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LBPool metadata: name: lbpool-dependency-no-secret spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: lbpool-dependency managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + loadBalancerRef: lbpool-dependency + protocol: HTTP + lbAlgorithm: ROUND_ROBIN +--- +# This LBPool is waiting for a Listener that doesn't exist yet +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-dependency-no-listener +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + listenerRef: lbpool-dependency + protocol: HTTP + lbAlgorithm: ROUND_ROBIN +--- +# This LBPool is waiting for a Project that doesn't exist yet +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-dependency-no-project +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + loadBalancerRef: lbpool-dependency + projectRef: lbpool-dependency + protocol: HTTP + lbAlgorithm: ROUND_ROBIN diff --git a/internal/controllers/lbpool/tests/lbpool-dependency/01-create-dependencies.yaml b/internal/controllers/lbpool/tests/lbpool-dependency/01-create-dependencies.yaml index feaab1bb..d05d48b3 100644 --- a/internal/controllers/lbpool/tests/lbpool-dependency/01-create-dependencies.yaml +++ b/internal/controllers/lbpool/tests/lbpool-dependency/01-create-dependencies.yaml @@ -6,30 +6,55 @@ commands: namespaced: true --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LoadBalancer +kind: Network metadata: - name: lbpool-dependency + name: lbpool-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: Subnet +metadata: + name: lbpool-dependency-pending +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: lbpool-dependency-pending + ipVersion: 4 + cidr: 10.0.4.0/24 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: lbpool-dependency-pending +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + vipSubnetRef: lbpool-dependency-pending +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Listener metadata: name: lbpool-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: + loadBalancerRef: lbpool-dependency + protocol: HTTP + protocolPort: 80 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project @@ -37,9 +62,7 @@ metadata: name: lbpool-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: {} diff --git a/internal/controllers/lbpool/tests/lbpool-dependency/02-assert.yaml b/internal/controllers/lbpool/tests/lbpool-dependency/02-assert.yaml index c6134d59..6d80fbb7 100644 --- a/internal/controllers/lbpool/tests/lbpool-dependency/02-assert.yaml +++ b/internal/controllers/lbpool/tests/lbpool-dependency/02-assert.yaml @@ -4,7 +4,7 @@ kind: TestAssert resourceRefs: - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LoadBalancer - name: lbpool-dependency + name: lbpool-dependency-pending ref: loadBalancer - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Listener diff --git a/internal/controllers/lbpool/tests/lbpool-dependency/02-delete-dependencies.yaml b/internal/controllers/lbpool/tests/lbpool-dependency/02-delete-dependencies.yaml index 695fc388..7c1f6e6e 100644 --- a/internal/controllers/lbpool/tests/lbpool-dependency/02-delete-dependencies.yaml +++ b/internal/controllers/lbpool/tests/lbpool-dependency/02-delete-dependencies.yaml @@ -3,7 +3,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 loadbalancer lbpool-dependency --wait=false + - command: kubectl delete loadbalancer lbpool-dependency-pending --wait=false namespaced: true - command: kubectl delete listener lbpool-dependency --wait=false namespaced: true diff --git a/internal/controllers/lbpool/tests/lbpool-dependency/03-assert.yaml b/internal/controllers/lbpool/tests/lbpool-dependency/03-assert.yaml index a29f76bd..44b61e4c 100644 --- a/internal/controllers/lbpool/tests/lbpool-dependency/03-assert.yaml +++ b/internal/controllers/lbpool/tests/lbpool-dependency/03-assert.yaml @@ -3,7 +3,7 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert commands: # Dependencies that were prevented deletion before should now be gone -- script: "! kubectl get loadbalancer lbpool-dependency --namespace $NAMESPACE" +- script: "! kubectl get loadbalancer lbpool-dependency-pending --namespace $NAMESPACE" skipLogOutput: true - script: "! kubectl get listener lbpool-dependency --namespace $NAMESPACE" skipLogOutput: true diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/00-assert.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/00-assert.yaml index c789efe4..ae5f6180 100644 --- a/internal/controllers/lbpool/tests/lbpool-import-dependency/00-assert.yaml +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/00-assert.yaml @@ -6,16 +6,10 @@ metadata: status: conditions: - type: Available - message: |- - Waiting for LoadBalancer/lbpool-import-dependency to be ready - Waiting for Listener/lbpool-import-dependency to be ready - Waiting for Project/lbpool-import-dependency to be ready + message: Waiting for LoadBalancer/lbpool-import-dependency to be ready status: "False" reason: Progressing - type: Progressing - message: |- - Waiting for LoadBalancer/lbpool-import-dependency to be ready - Waiting for Listener/lbpool-import-dependency to be ready - Waiting for Project/lbpool-import-dependency to be ready + message: Waiting for LoadBalancer/lbpool-import-dependency to be ready status: "True" reason: Progressing diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/00-import-resource.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/00-import-resource.yaml index 92c6ebba..95079242 100644 --- a/internal/controllers/lbpool/tests/lbpool-import-dependency/00-import-resource.yaml +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/00-import-resource.yaml @@ -13,32 +13,6 @@ spec: name: lbpool-import-dependency-external --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Listener -metadata: - name: lbpool-import-dependency -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - name: lbpool-import-dependency-external ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Project -metadata: - name: lbpool-import-dependency -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - name: lbpool-import-dependency-external ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LBPool metadata: name: lbpool-import-dependency @@ -50,5 +24,3 @@ spec: import: filter: loadBalancerRef: lbpool-import-dependency - listenerRef: lbpool-import-dependency - projectRef: lbpool-import-dependency diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/01-assert.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/01-assert.yaml index 123b8612..8d320196 100644 --- a/internal/controllers/lbpool/tests/lbpool-import-dependency/01-assert.yaml +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/01-assert.yaml @@ -21,16 +21,10 @@ metadata: status: conditions: - type: Available - message: |- - Waiting for LoadBalancer/lbpool-import-dependency to be ready - Waiting for Listener/lbpool-import-dependency to be ready - Waiting for Project/lbpool-import-dependency to be ready + message: Waiting for LoadBalancer/lbpool-import-dependency to be ready status: "False" reason: Progressing - type: Progressing - message: |- - Waiting for LoadBalancer/lbpool-import-dependency to be ready - Waiting for Listener/lbpool-import-dependency to be ready - Waiting for Project/lbpool-import-dependency to be ready + message: Waiting for LoadBalancer/lbpool-import-dependency to be ready status: "True" reason: Progressing diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/01-create-trap-resource.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/01-create-trap-resource.yaml index 786ee354..8019baf2 100644 --- a/internal/controllers/lbpool/tests/lbpool-import-dependency/01-create-trap-resource.yaml +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/01-create-trap-resource.yaml @@ -1,42 +1,40 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LoadBalancer +kind: Network metadata: name: lbpool-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: Listener +kind: Subnet metadata: name: lbpool-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: + networkRef: lbpool-import-dependency-not-this-one + ipVersion: 4 + cidr: 10.0.8.0/24 --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Project +kind: LoadBalancer metadata: name: lbpool-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: lbpool-import-dependency-not-this-one --- # This `lbpool-import-dependency-not-this-one` should not be picked by the import filter apiVersion: openstack.k-orc.cloud/v1alpha1 @@ -45,12 +43,10 @@ metadata: name: lbpool-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: lbpool-import-dependency-not-this-one - listenerRef: lbpool-import-dependency-not-this-one - projectRef: lbpool-import-dependency-not-this-one - # TODO(scaffolding): Add the necessary fields to create the resource + protocol: HTTP + lbAlgorithm: ROUND_ROBIN diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/02-assert.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/02-assert.yaml index 794e2fe0..b2ea0c9b 100644 --- a/internal/controllers/lbpool/tests/lbpool-import-dependency/02-assert.yaml +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/02-assert.yaml @@ -14,19 +14,9 @@ resourceRefs: kind: LoadBalancer name: lbpool-import-dependency ref: loadBalancer - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Listener - name: lbpool-import-dependency - ref: listener - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Project - name: lbpool-import-dependency - ref: project assertAll: - celExpr: "lbpool1.status.id != lbpool2.status.id" - - celExpr: "lbpool1.status.resource.loadBalancerID == loadBalancer.status.id" - - celExpr: "lbpool1.status.resource.listenerID == listener.status.id" - - celExpr: "lbpool1.status.resource.projectID == project.status.id" + - celExpr: "loadBalancer.status.id in lbpool1.status.resource.loadBalancerIDs" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LBPool diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/02-create-resource.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/02-create-resource.yaml index 64829b2f..e991a159 100644 --- a/internal/controllers/lbpool/tests/lbpool-import-dependency/02-create-resource.yaml +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/02-create-resource.yaml @@ -1,42 +1,40 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LoadBalancer +kind: Network metadata: name: lbpool-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 +kind: Subnet metadata: name: lbpool-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: + networkRef: lbpool-import-dependency-external + ipVersion: 4 + cidr: 10.0.9.0/24 --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Project +kind: LoadBalancer metadata: name: lbpool-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: lbpool-import-dependency-external --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LBPool @@ -44,12 +42,10 @@ metadata: name: lbpool-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: lbpool-import-dependency-external - listenerRef: lbpool-import-dependency-external - projectRef: lbpool-import-dependency-external - # TODO(scaffolding): Add the necessary fields to create the resource + protocol: HTTP + lbAlgorithm: ROUND_ROBIN diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/03-assert.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/03-assert.yaml index badd4558..f52ed06f 100644 --- a/internal/controllers/lbpool/tests/lbpool-import-dependency/03-assert.yaml +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/03-assert.yaml @@ -4,7 +4,3 @@ kind: TestAssert commands: - script: "! kubectl get loadbalancer lbpool-import-dependency --namespace $NAMESPACE" skipLogOutput: true -- script: "! kubectl get listener lbpool-import-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get project lbpool-import-dependency --namespace $NAMESPACE" - skipLogOutput: true diff --git a/internal/controllers/lbpool/tests/lbpool-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/lbpool/tests/lbpool-import-dependency/03-delete-import-dependencies.yaml index 24a62289..bc7c094e 100644 --- a/internal/controllers/lbpool/tests/lbpool-import-dependency/03-delete-import-dependencies.yaml +++ b/internal/controllers/lbpool/tests/lbpool-import-dependency/03-delete-import-dependencies.yaml @@ -5,7 +5,3 @@ commands: # We should be able to delete the import dependencies - command: kubectl delete loadbalancer lbpool-import-dependency namespaced: true - - command: kubectl delete listener lbpool-import-dependency - namespaced: true - - command: kubectl delete project lbpool-import-dependency - namespaced: true diff --git a/internal/controllers/lbpool/tests/lbpool-import-error/00-create-resources.yaml b/internal/controllers/lbpool/tests/lbpool-import-error/00-create-resources.yaml index 05a1bc12..016fbcd5 100644 --- a/internal/controllers/lbpool/tests/lbpool-import-error/00-create-resources.yaml +++ b/internal/controllers/lbpool/tests/lbpool-import-error/00-create-resources.yaml @@ -1,17 +1,56 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: lbpool-import-error +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: lbpool-import-error +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: lbpool-import-error + ipVersion: 4 + cidr: 10.0.7.0/24 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: lbpool-import-error +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + vipSubnetRef: lbpool-import-error +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LBPool metadata: name: lbpool-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: + name: lbpool-import-error-match description: LBPool from "import error" test - # TODO(scaffolding): add any required field + loadBalancerRef: lbpool-import-error + protocol: HTTP + lbAlgorithm: ROUND_ROBIN --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LBPool @@ -19,10 +58,12 @@ metadata: name: lbpool-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: + name: lbpool-import-error-match description: LBPool from "import error" test - # TODO(scaffolding): add any required field + loadBalancerRef: lbpool-import-error + protocol: TCP + lbAlgorithm: ROUND_ROBIN diff --git a/internal/controllers/lbpool/tests/lbpool-import-error/01-import-resource.yaml b/internal/controllers/lbpool/tests/lbpool-import-error/01-import-resource.yaml index 8135a327..69179d9d 100644 --- a/internal/controllers/lbpool/tests/lbpool-import-error/01-import-resource.yaml +++ b/internal/controllers/lbpool/tests/lbpool-import-error/01-import-resource.yaml @@ -10,4 +10,4 @@ spec: managementPolicy: unmanaged import: filter: - description: LBPool from "import error" test + name: lbpool-import-error-match diff --git a/internal/controllers/lbpool/tests/lbpool-import/00-import-resource.yaml b/internal/controllers/lbpool/tests/lbpool-import/00-import-resource.yaml index 16b61c9d..3dd8c488 100644 --- a/internal/controllers/lbpool/tests/lbpool-import/00-import-resource.yaml +++ b/internal/controllers/lbpool/tests/lbpool-import/00-import-resource.yaml @@ -11,5 +11,3 @@ spec: import: filter: name: lbpool-import-external - description: LBPool lbpool-import-external from "lbpool-import" test - # TODO(scaffolding): Add all fields supported by the filter diff --git a/internal/controllers/lbpool/tests/lbpool-import/01-assert.yaml b/internal/controllers/lbpool/tests/lbpool-import/01-assert.yaml index db0c051f..dc71aa16 100644 --- a/internal/controllers/lbpool/tests/lbpool-import/01-assert.yaml +++ b/internal/controllers/lbpool/tests/lbpool-import/01-assert.yaml @@ -15,8 +15,6 @@ status: reason: Success resource: name: lbpool-import-external-not-this-one - description: LBPool lbpool-import-external from "lbpool-import" test - # TODO(scaffolding): Add fields necessary to match filter --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LBPool diff --git a/internal/controllers/lbpool/tests/lbpool-import/01-create-trap-resource.yaml b/internal/controllers/lbpool/tests/lbpool-import/01-create-trap-resource.yaml index 1559ff22..39a76170 100644 --- a/internal/controllers/lbpool/tests/lbpool-import/01-create-trap-resource.yaml +++ b/internal/controllers/lbpool/tests/lbpool-import/01-create-trap-resource.yaml @@ -1,4 +1,41 @@ --- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: lbpool-import-not-this-one +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: lbpool-import-not-this-one +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: lbpool-import-not-this-one + ipVersion: 4 + cidr: 10.0.5.0/24 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: lbpool-import-external-not-this-one +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + vipSubnetRef: lbpool-import-not-this-one +--- # This `lbpool-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. @@ -8,10 +45,10 @@ metadata: name: lbpool-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: LBPool lbpool-import-external from "lbpool-import" test - # TODO(scaffolding): Add fields necessary to match filter + loadBalancerRef: lbpool-import-external-not-this-one + protocol: HTTP + lbAlgorithm: ROUND_ROBIN diff --git a/internal/controllers/lbpool/tests/lbpool-import/02-assert.yaml b/internal/controllers/lbpool/tests/lbpool-import/02-assert.yaml index 19c25e30..47c4085f 100644 --- a/internal/controllers/lbpool/tests/lbpool-import/02-assert.yaml +++ b/internal/controllers/lbpool/tests/lbpool-import/02-assert.yaml @@ -29,5 +29,3 @@ status: reason: Success resource: name: lbpool-import-external - description: LBPool lbpool-import-external from "lbpool-import" test - # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/lbpool/tests/lbpool-import/02-create-resource.yaml b/internal/controllers/lbpool/tests/lbpool-import/02-create-resource.yaml index b08284ef..a22be23c 100644 --- a/internal/controllers/lbpool/tests/lbpool-import/02-create-resource.yaml +++ b/internal/controllers/lbpool/tests/lbpool-import/02-create-resource.yaml @@ -1,14 +1,51 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: lbpool-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: lbpool-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: lbpool-import + ipVersion: 4 + cidr: 10.0.6.0/24 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: lbpool-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + vipSubnetRef: lbpool-import +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LBPool metadata: name: lbpool-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: LBPool lbpool-import-external from "lbpool-import" test - # TODO(scaffolding): Add fields necessary to match filter + loadBalancerRef: lbpool-import + protocol: HTTP + lbAlgorithm: ROUND_ROBIN diff --git a/internal/controllers/lbpool/tests/lbpool-update/00-assert.yaml b/internal/controllers/lbpool/tests/lbpool-update/00-assert.yaml index 6bde68bf..71ad22fa 100644 --- a/internal/controllers/lbpool/tests/lbpool-update/00-assert.yaml +++ b/internal/controllers/lbpool/tests/lbpool-update/00-assert.yaml @@ -1,14 +1,4 @@ --- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: LBPool - name: lbpool-update - ref: lbpool -assertAll: - - celExpr: "!has(lbpool.status.resource.description)" ---- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LBPool metadata: @@ -16,7 +6,9 @@ metadata: status: resource: name: lbpool-update - # TODO(scaffolding): Add matches for more fields + protocol: HTTP + lbAlgorithm: ROUND_ROBIN + provisioningStatus: ACTIVE conditions: - type: Available status: "True" @@ -24,3 +16,15 @@ status: - type: Progressing status: "False" reason: Success +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LBPool + name: lbpool-update + ref: lbpool +assertAll: + # Dynamic checks that require CEL + - celExpr: "lbpool.status.id != ''" + - celExpr: "!has(lbpool.status.resource.description) || lbpool.status.resource.description == ''" \ No newline at end of file diff --git a/internal/controllers/lbpool/tests/lbpool-update/00-minimal-resource.yaml b/internal/controllers/lbpool/tests/lbpool-update/00-minimal-resource.yaml index e79b4208..087510a7 100644 --- a/internal/controllers/lbpool/tests/lbpool-update/00-minimal-resource.yaml +++ b/internal/controllers/lbpool/tests/lbpool-update/00-minimal-resource.yaml @@ -1,14 +1,52 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: LBPool +kind: Network metadata: name: lbpool-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: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: lbpool-update +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: lbpool-update + ipVersion: 4 + cidr: 10.0.2.0/24 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LoadBalancer +metadata: + name: lbpool-update +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + vipSubnetRef: lbpool-update +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: LBPool +metadata: + name: lbpool-update +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + loadBalancerRef: lbpool-update + protocol: HTTP + lbAlgorithm: ROUND_ROBIN + adminStateUp: true diff --git a/internal/controllers/lbpool/tests/lbpool-update/01-assert.yaml b/internal/controllers/lbpool/tests/lbpool-update/01-assert.yaml index 4ba2af26..310f0c05 100644 --- a/internal/controllers/lbpool/tests/lbpool-update/01-assert.yaml +++ b/internal/controllers/lbpool/tests/lbpool-update/01-assert.yaml @@ -7,7 +7,8 @@ status: resource: name: lbpool-update-updated description: lbpool-update-updated - # TODO(scaffolding): match all fields that were modified + provisioningStatus: ACTIVE + adminStateUp: false conditions: - type: Available status: "True" @@ -15,3 +16,20 @@ status: - type: Progressing status: "False" reason: Success +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LBPool + name: lbpool-update + ref: lbpool +assertAll: + # Dynamic checks that require CEL + - celExpr: "lbpool.status.resource.tags.exists(t, t == 'updated')" + # Member assertions - need CEL for exists() checks + - celExpr: "size(lbpool.status.resource.members) == 2" + - celExpr: "lbpool.status.resource.members.exists(m, m.name == 'member-a' && m.address == '10.0.2.10' && m.protocolPort == 8080)" + - celExpr: "lbpool.status.resource.members.exists(m, m.name == 'member-b' && m.address == '10.0.2.11' && m.protocolPort == 8080)" + - celExpr: "lbpool.status.resource.members.exists(m, m.name == 'member-a' && m.weight == 10)" + - celExpr: "lbpool.status.resource.members.exists(m, m.name == 'member-b' && m.backup == true)" diff --git a/internal/controllers/lbpool/tests/lbpool-update/01-updated-resource.yaml b/internal/controllers/lbpool/tests/lbpool-update/01-updated-resource.yaml index 93b09d64..2325db15 100644 --- a/internal/controllers/lbpool/tests/lbpool-update/01-updated-resource.yaml +++ b/internal/controllers/lbpool/tests/lbpool-update/01-updated-resource.yaml @@ -7,4 +7,15 @@ spec: resource: name: lbpool-update-updated description: lbpool-update-updated - # TODO(scaffolding): update all mutable fields + adminStateUp: false + tags: + - "updated" + members: + - name: member-a + address: 10.0.2.10 + protocolPort: 8080 + weight: 10 + - name: member-b + address: 10.0.2.11 + protocolPort: 8080 + backup: true \ No newline at end of file diff --git a/internal/controllers/lbpool/tests/lbpool-update/02-assert.yaml b/internal/controllers/lbpool/tests/lbpool-update/02-assert.yaml index 1021909c..abb87686 100644 --- a/internal/controllers/lbpool/tests/lbpool-update/02-assert.yaml +++ b/internal/controllers/lbpool/tests/lbpool-update/02-assert.yaml @@ -1,14 +1,4 @@ --- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: LBPool - name: lbpool-update - ref: lbpool -assertAll: - - celExpr: "!has(lbpool.status.resource.description)" ---- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: LBPool metadata: @@ -16,7 +6,10 @@ metadata: status: resource: name: lbpool-update - # TODO(scaffolding): validate that updated fields were all reverted to their original value + protocol: HTTP + lbAlgorithm: ROUND_ROBIN + provisioningStatus: ACTIVE + adminStateUp: true conditions: - type: Available status: "True" @@ -24,3 +17,17 @@ status: - type: Progressing status: "False" reason: Success +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: LBPool + name: lbpool-update + ref: lbpool +assertAll: + # Dynamic checks that require CEL - checking for absence or empty + - celExpr: "!has(lbpool.status.resource.description) || lbpool.status.resource.description == ''" + - celExpr: "!has(lbpool.status.resource.tags) || size(lbpool.status.resource.tags) == 0" + # Members should be removed + - celExpr: "!has(lbpool.status.resource.members) || size(lbpool.status.resource.members) == 0" diff --git a/internal/controllers/lbpool/zz_generated.adapter.go b/internal/controllers/lbpool/zz_generated.adapter.go new file mode 100644 index 00000000..0576f3d6 --- /dev/null +++ b/internal/controllers/lbpool/zz_generated.adapter.go @@ -0,0 +1,88 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright 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 lbpool + +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.LBPool + orcObjectListT = orcv1alpha1.LBPoolList + resourceSpecT = orcv1alpha1.LBPoolResourceSpec + filterT = orcv1alpha1.LBPoolFilter +) + +// Derived types +type ( + orcObjectPT = *orcObjectT + adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT] + adapterT = lbpoolAdapter +) + +type lbpoolAdapter struct { + *orcv1alpha1.LBPool +} + +var _ adapterI = &adapterT{} + +func (f adapterT) GetObject() orcObjectPT { + return f.LBPool +} + +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/lbpool/zz_generated.controller.go b/internal/controllers/lbpool/zz_generated.controller.go new file mode 100644 index 00000000..eff17521 --- /dev/null +++ b/internal/controllers/lbpool/zz_generated.controller.go @@ -0,0 +1,45 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright 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 lbpool + +import ( + corev1 "k8s.io/api/core/v1" + + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" + orcstrings "github.com/k-orc/openstack-resource-controller/v2/internal/util/strings" +) + +var ( + // NOTE: controllerName must be defined in any controller using this template + + // finalizer is the string this controller adds to an object's Finalizers + finalizer = orcstrings.GetFinalizerName(controllerName) + + // externalObjectFieldOwner is the field owner we use when using + // server-side-apply on objects we don't control + externalObjectFieldOwner = orcstrings.GetSSAFieldOwner(controllerName) + + credentialsDependency = dependency.NewDeletionGuardDependency[*orcObjectListT, *corev1.Secret]( + "spec.cloudCredentialsRef.secretName", + func(obj orcObjectPT) []string { + return []string{obj.Spec.CloudCredentialsRef.SecretName} + }, + finalizer, externalObjectFieldOwner, + dependency.OverrideDependencyName("credentials"), + ) +) diff --git a/internal/controllers/listener/actuator.go b/internal/controllers/listener/actuator.go index 8f878a69..2c93e820 100644 --- a/internal/controllers/listener/actuator.go +++ b/internal/controllers/listener/actuator.go @@ -102,8 +102,8 @@ func (actuator listenerActuator) ListOSResourcesForImport(ctx context.Context, o } listOpts := listeners.ListOpts{ - Name: string(ptr.Deref(filter.Name, "")), - LoadbalancerID: ptr.Deref(loadBalancer.Status.ID, ""), + Name: string(ptr.Deref(filter.Name, "")), + LoadbalancerID: ptr.Deref(loadBalancer.Status.ID, ""), } return actuator.osClient.ListListeners(ctx, listOpts), reconcileStatus diff --git a/internal/osclients/lbpool.go b/internal/osclients/lbpool.go index a6ce5af1..ea283116 100644 --- a/internal/osclients/lbpool.go +++ b/internal/osclients/lbpool.go @@ -33,6 +33,13 @@ type LBPoolClient interface { DeleteLBPool(ctx context.Context, resourceID string) error GetLBPool(ctx context.Context, resourceID string) (*pools.Pool, error) UpdateLBPool(ctx context.Context, id string, opts pools.UpdateOptsBuilder) (*pools.Pool, error) + + // Member operations + ListMembers(ctx context.Context, poolID string, opts pools.ListMembersOptsBuilder) iter.Seq2[*pools.Member, error] + GetMember(ctx context.Context, poolID, memberID string) (*pools.Member, error) + CreateMember(ctx context.Context, poolID string, opts pools.CreateMemberOptsBuilder) (*pools.Member, error) + UpdateMember(ctx context.Context, poolID, memberID string, opts pools.UpdateMemberOptsBuilder) (*pools.Member, error) + DeleteMember(ctx context.Context, poolID, memberID string) error } type lbpoolClient struct{ client *gophercloud.ServiceClient } @@ -74,6 +81,29 @@ func (c lbpoolClient) UpdateLBPool(ctx context.Context, id string, opts pools.Up return pools.Update(ctx, c.client, id, opts).Extract() } +func (c lbpoolClient) ListMembers(ctx context.Context, poolID string, opts pools.ListMembersOptsBuilder) iter.Seq2[*pools.Member, error] { + pager := pools.ListMembers(c.client, poolID, opts) + return func(yield func(*pools.Member, error) bool) { + _ = pager.EachPage(ctx, yieldPage(pools.ExtractMembers, yield)) + } +} + +func (c lbpoolClient) GetMember(ctx context.Context, poolID, memberID string) (*pools.Member, error) { + return pools.GetMember(ctx, c.client, poolID, memberID).Extract() +} + +func (c lbpoolClient) CreateMember(ctx context.Context, poolID string, opts pools.CreateMemberOptsBuilder) (*pools.Member, error) { + return pools.CreateMember(ctx, c.client, poolID, opts).Extract() +} + +func (c lbpoolClient) UpdateMember(ctx context.Context, poolID, memberID string, opts pools.UpdateMemberOptsBuilder) (*pools.Member, error) { + return pools.UpdateMember(ctx, c.client, poolID, memberID, opts).Extract() +} + +func (c lbpoolClient) DeleteMember(ctx context.Context, poolID, memberID string) error { + return pools.DeleteMember(ctx, c.client, poolID, memberID).ExtractErr() +} + type lbpoolErrorClient struct{ error } // NewLBPoolErrorClient returns a LBPoolClient in which every method returns the given error. @@ -102,3 +132,25 @@ func (e lbpoolErrorClient) GetLBPool(_ context.Context, _ string) (*pools.Pool, func (e lbpoolErrorClient) UpdateLBPool(_ context.Context, _ string, _ pools.UpdateOptsBuilder) (*pools.Pool, error) { return nil, e.error } + +func (e lbpoolErrorClient) ListMembers(_ context.Context, _ string, _ pools.ListMembersOptsBuilder) iter.Seq2[*pools.Member, error] { + return func(yield func(*pools.Member, error) bool) { + yield(nil, e.error) + } +} + +func (e lbpoolErrorClient) GetMember(_ context.Context, _, _ string) (*pools.Member, error) { + return nil, e.error +} + +func (e lbpoolErrorClient) CreateMember(_ context.Context, _ string, _ pools.CreateMemberOptsBuilder) (*pools.Member, error) { + return nil, e.error +} + +func (e lbpoolErrorClient) UpdateMember(_ context.Context, _, _ string, _ pools.UpdateMemberOptsBuilder) (*pools.Member, error) { + return nil, e.error +} + +func (e lbpoolErrorClient) DeleteMember(_ context.Context, _, _ string) error { + return e.error +} diff --git a/internal/osclients/mock/doc.go b/internal/osclients/mock/doc.go index ae2e1fa2..1aa5beea 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=lbpool.go -source=../lbpool.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock LBPoolClient +//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt lbpool.go > _lbpool.go && mv _lbpool.go lbpool.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" diff --git a/internal/osclients/mock/lbpool.go b/internal/osclients/mock/lbpool.go new file mode 100644 index 00000000..7d351667 --- /dev/null +++ b/internal/osclients/mock/lbpool.go @@ -0,0 +1,204 @@ +/* +Copyright 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: ../lbpool.go +// +// Generated by this command: +// +// mockgen -package mock -destination=lbpool.go -source=../lbpool.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock LBPoolClient +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + iter "iter" + reflect "reflect" + + pools "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/pools" + gomock "go.uber.org/mock/gomock" +) + +// MockLBPoolClient is a mock of LBPoolClient interface. +type MockLBPoolClient struct { + ctrl *gomock.Controller + recorder *MockLBPoolClientMockRecorder + isgomock struct{} +} + +// MockLBPoolClientMockRecorder is the mock recorder for MockLBPoolClient. +type MockLBPoolClientMockRecorder struct { + mock *MockLBPoolClient +} + +// NewMockLBPoolClient creates a new mock instance. +func NewMockLBPoolClient(ctrl *gomock.Controller) *MockLBPoolClient { + mock := &MockLBPoolClient{ctrl: ctrl} + mock.recorder = &MockLBPoolClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLBPoolClient) EXPECT() *MockLBPoolClientMockRecorder { + return m.recorder +} + +// CreateLBPool mocks base method. +func (m *MockLBPoolClient) CreateLBPool(ctx context.Context, opts pools.CreateOptsBuilder) (*pools.Pool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLBPool", ctx, opts) + ret0, _ := ret[0].(*pools.Pool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateLBPool indicates an expected call of CreateLBPool. +func (mr *MockLBPoolClientMockRecorder) CreateLBPool(ctx, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLBPool", reflect.TypeOf((*MockLBPoolClient)(nil).CreateLBPool), ctx, opts) +} + +// CreateMember mocks base method. +func (m *MockLBPoolClient) CreateMember(ctx context.Context, poolID string, opts pools.CreateMemberOptsBuilder) (*pools.Member, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateMember", ctx, poolID, opts) + ret0, _ := ret[0].(*pools.Member) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateMember indicates an expected call of CreateMember. +func (mr *MockLBPoolClientMockRecorder) CreateMember(ctx, poolID, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMember", reflect.TypeOf((*MockLBPoolClient)(nil).CreateMember), ctx, poolID, opts) +} + +// DeleteLBPool mocks base method. +func (m *MockLBPoolClient) DeleteLBPool(ctx context.Context, resourceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLBPool", ctx, resourceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLBPool indicates an expected call of DeleteLBPool. +func (mr *MockLBPoolClientMockRecorder) DeleteLBPool(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLBPool", reflect.TypeOf((*MockLBPoolClient)(nil).DeleteLBPool), ctx, resourceID) +} + +// DeleteMember mocks base method. +func (m *MockLBPoolClient) DeleteMember(ctx context.Context, poolID, memberID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteMember", ctx, poolID, memberID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteMember indicates an expected call of DeleteMember. +func (mr *MockLBPoolClientMockRecorder) DeleteMember(ctx, poolID, memberID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMember", reflect.TypeOf((*MockLBPoolClient)(nil).DeleteMember), ctx, poolID, memberID) +} + +// GetLBPool mocks base method. +func (m *MockLBPoolClient) GetLBPool(ctx context.Context, resourceID string) (*pools.Pool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLBPool", ctx, resourceID) + ret0, _ := ret[0].(*pools.Pool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLBPool indicates an expected call of GetLBPool. +func (mr *MockLBPoolClientMockRecorder) GetLBPool(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLBPool", reflect.TypeOf((*MockLBPoolClient)(nil).GetLBPool), ctx, resourceID) +} + +// GetMember mocks base method. +func (m *MockLBPoolClient) GetMember(ctx context.Context, poolID, memberID string) (*pools.Member, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMember", ctx, poolID, memberID) + ret0, _ := ret[0].(*pools.Member) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMember indicates an expected call of GetMember. +func (mr *MockLBPoolClientMockRecorder) GetMember(ctx, poolID, memberID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMember", reflect.TypeOf((*MockLBPoolClient)(nil).GetMember), ctx, poolID, memberID) +} + +// ListLBPools mocks base method. +func (m *MockLBPoolClient) ListLBPools(ctx context.Context, listOpts pools.ListOptsBuilder) iter.Seq2[*pools.Pool, error] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLBPools", ctx, listOpts) + ret0, _ := ret[0].(iter.Seq2[*pools.Pool, error]) + return ret0 +} + +// ListLBPools indicates an expected call of ListLBPools. +func (mr *MockLBPoolClientMockRecorder) ListLBPools(ctx, listOpts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLBPools", reflect.TypeOf((*MockLBPoolClient)(nil).ListLBPools), ctx, listOpts) +} + +// ListMembers mocks base method. +func (m *MockLBPoolClient) ListMembers(ctx context.Context, poolID string, opts pools.ListMembersOptsBuilder) iter.Seq2[*pools.Member, error] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListMembers", ctx, poolID, opts) + ret0, _ := ret[0].(iter.Seq2[*pools.Member, error]) + return ret0 +} + +// ListMembers indicates an expected call of ListMembers. +func (mr *MockLBPoolClientMockRecorder) ListMembers(ctx, poolID, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMembers", reflect.TypeOf((*MockLBPoolClient)(nil).ListMembers), ctx, poolID, opts) +} + +// UpdateLBPool mocks base method. +func (m *MockLBPoolClient) UpdateLBPool(ctx context.Context, id string, opts pools.UpdateOptsBuilder) (*pools.Pool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateLBPool", ctx, id, opts) + ret0, _ := ret[0].(*pools.Pool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateLBPool indicates an expected call of UpdateLBPool. +func (mr *MockLBPoolClientMockRecorder) UpdateLBPool(ctx, id, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLBPool", reflect.TypeOf((*MockLBPoolClient)(nil).UpdateLBPool), ctx, id, opts) +} + +// UpdateMember mocks base method. +func (m *MockLBPoolClient) UpdateMember(ctx context.Context, poolID, memberID string, opts pools.UpdateMemberOptsBuilder) (*pools.Member, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateMember", ctx, poolID, memberID, opts) + ret0, _ := ret[0].(*pools.Member) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateMember indicates an expected call of UpdateMember. +func (mr *MockLBPoolClientMockRecorder) UpdateMember(ctx, poolID, memberID, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateMember", reflect.TypeOf((*MockLBPoolClient)(nil).UpdateMember), ctx, poolID, memberID, opts) +} diff --git a/internal/scope/mock.go b/internal/scope/mock.go index e36cbc95..10fdde39 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 + LBPoolClient *mock.MockLBPoolClient ListenerClient *mock.MockListenerClient LoadBalancerClient *mock.MockLoadBalancerClient NetworkClient *mock.MockNetworkClient @@ -65,6 +66,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { volumeClient := mock.NewMockVolumeClient(mockCtrl) volumetypeClient := mock.NewMockVolumeTypeClient(mockCtrl) loadBalancerClient := mock.NewMockLoadBalancerClient(mockCtrl) + lbPoolClient := mock.NewMockLBPoolClient(mockCtrl) return &MockScopeFactory{ ComputeClient: computeClient, @@ -80,6 +82,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { ServiceClient: serviceClient, VolumeClient: volumeClient, VolumeTypeClient: volumetypeClient, + LBPoolClient: lbPoolClient, } } @@ -146,6 +149,10 @@ func (f *MockScopeFactory) NewLoadBalancerClient() (osclients.LoadBalancerClient return f.LoadBalancerClient, nil } +func (f *MockScopeFactory) NewLBPoolClient() (osclients.LBPoolClient, error) { + return f.LBPoolClient, 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 85500f66..dd943ca8 100644 --- a/internal/scope/provider.go +++ b/internal/scope/provider.go @@ -189,6 +189,10 @@ func (s *providerScope) NewLoadBalancerClient() (clients.LoadBalancerClient, err return clients.NewLoadBalancerClient(s.providerClient, s.providerClientOpts) } +func (s *providerScope) NewLBPoolClient() (clients.LBPoolClient, error) { + return clients.NewLBPoolClient(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 736bd21e..59f40344 100644 --- a/internal/scope/scope.go +++ b/internal/scope/scope.go @@ -61,6 +61,7 @@ type Scope interface { NewVolumeTypeClient() (osclients.VolumeTypeClient, error) NewListenerClient() (osclients.ListenerClient, error) NewLoadBalancerClient() (osclients.LoadBalancerClient, error) + NewLBPoolClient() (osclients.LBPoolClient, error) ExtractToken() (*tokens.Token, error) } diff --git a/kuttl-test.yaml b/kuttl-test.yaml index 44deb3f0..9645e874 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/lbpool/tests/ - ./internal/controllers/listener/tests/ - ./internal/controllers/loadbalancer/tests/ - ./internal/controllers/network/tests/ diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/lbpool.go b/pkg/clients/applyconfiguration/api/v1alpha1/lbpool.go new file mode 100644 index 00000000..ae17c0a7 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/lbpool.go @@ -0,0 +1,281 @@ +/* +Copyright 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" +) + +// LBPoolApplyConfiguration represents a declarative configuration of the LBPool type for use +// with apply. +type LBPoolApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *LBPoolSpecApplyConfiguration `json:"spec,omitempty"` + Status *LBPoolStatusApplyConfiguration `json:"status,omitempty"` +} + +// LBPool constructs a declarative configuration of the LBPool type for use with +// apply. +func LBPool(name, namespace string) *LBPoolApplyConfiguration { + b := &LBPoolApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("LBPool") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b +} + +// ExtractLBPool extracts the applied configuration owned by fieldManager from +// lBPool. If no managedFields are found in lBPool for fieldManager, a +// LBPoolApplyConfiguration 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. +// lBPool must be a unmodified LBPool API object that was retrieved from the Kubernetes API. +// ExtractLBPool 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 ExtractLBPool(lBPool *apiv1alpha1.LBPool, fieldManager string) (*LBPoolApplyConfiguration, error) { + return extractLBPool(lBPool, fieldManager, "") +} + +// ExtractLBPoolStatus is the same as ExtractLBPool except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractLBPoolStatus(lBPool *apiv1alpha1.LBPool, fieldManager string) (*LBPoolApplyConfiguration, error) { + return extractLBPool(lBPool, fieldManager, "status") +} + +func extractLBPool(lBPool *apiv1alpha1.LBPool, fieldManager string, subresource string) (*LBPoolApplyConfiguration, error) { + b := &LBPoolApplyConfiguration{} + err := managedfields.ExtractInto(lBPool, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LBPool"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(lBPool.Name) + b.WithNamespace(lBPool.Namespace) + + b.WithKind("LBPool") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b, nil +} +func (b LBPoolApplyConfiguration) 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 *LBPoolApplyConfiguration) WithKind(value string) *LBPoolApplyConfiguration { + 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 *LBPoolApplyConfiguration) WithAPIVersion(value string) *LBPoolApplyConfiguration { + 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 *LBPoolApplyConfiguration) WithName(value string) *LBPoolApplyConfiguration { + 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 *LBPoolApplyConfiguration) WithGenerateName(value string) *LBPoolApplyConfiguration { + 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 *LBPoolApplyConfiguration) WithNamespace(value string) *LBPoolApplyConfiguration { + 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 *LBPoolApplyConfiguration) WithUID(value types.UID) *LBPoolApplyConfiguration { + 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 *LBPoolApplyConfiguration) WithResourceVersion(value string) *LBPoolApplyConfiguration { + 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 *LBPoolApplyConfiguration) WithGeneration(value int64) *LBPoolApplyConfiguration { + 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 *LBPoolApplyConfiguration) WithCreationTimestamp(value metav1.Time) *LBPoolApplyConfiguration { + 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 *LBPoolApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *LBPoolApplyConfiguration { + 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 *LBPoolApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *LBPoolApplyConfiguration { + 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 *LBPoolApplyConfiguration) WithLabels(entries map[string]string) *LBPoolApplyConfiguration { + 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 *LBPoolApplyConfiguration) WithAnnotations(entries map[string]string) *LBPoolApplyConfiguration { + 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 *LBPoolApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *LBPoolApplyConfiguration { + 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 *LBPoolApplyConfiguration) WithFinalizers(values ...string) *LBPoolApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *LBPoolApplyConfiguration) 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 *LBPoolApplyConfiguration) WithSpec(value *LBPoolSpecApplyConfiguration) *LBPoolApplyConfiguration { + 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 *LBPoolApplyConfiguration) WithStatus(value *LBPoolStatusApplyConfiguration) *LBPoolApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *LBPoolApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *LBPoolApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *LBPoolApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *LBPoolApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolfilter.go new file mode 100644 index 00000000..fcf2805c --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolfilter.go @@ -0,0 +1,141 @@ +/* +Copyright 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" +) + +// LBPoolFilterApplyConfiguration represents a declarative configuration of the LBPoolFilter type for use +// with apply. +type LBPoolFilterApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + LoadBalancerRef *apiv1alpha1.KubernetesNameRef `json:"loadBalancerRef,omitempty"` + ListenerRef *apiv1alpha1.KubernetesNameRef `json:"listenerRef,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + LBAlgorithm *apiv1alpha1.LBPoolLBAlgorithm `json:"lbAlgorithm,omitempty"` + Protocol *apiv1alpha1.LBPoolProtocol `json:"protocol,omitempty"` + Tags []apiv1alpha1.LBPoolTag `json:"tags,omitempty"` + TagsAny []apiv1alpha1.LBPoolTag `json:"tagsAny,omitempty"` + NotTags []apiv1alpha1.LBPoolTag `json:"notTags,omitempty"` + NotTagsAny []apiv1alpha1.LBPoolTag `json:"notTagsAny,omitempty"` +} + +// LBPoolFilterApplyConfiguration constructs a declarative configuration of the LBPoolFilter type for use with +// apply. +func LBPoolFilter() *LBPoolFilterApplyConfiguration { + return &LBPoolFilterApplyConfiguration{} +} + +// 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 *LBPoolFilterApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *LBPoolFilterApplyConfiguration { + 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 *LBPoolFilterApplyConfiguration) WithDescription(value string) *LBPoolFilterApplyConfiguration { + 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 *LBPoolFilterApplyConfiguration) WithLoadBalancerRef(value apiv1alpha1.KubernetesNameRef) *LBPoolFilterApplyConfiguration { + b.LoadBalancerRef = &value + return b +} + +// WithListenerRef sets the ListenerRef 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 ListenerRef field is set to the value of the last call. +func (b *LBPoolFilterApplyConfiguration) WithListenerRef(value apiv1alpha1.KubernetesNameRef) *LBPoolFilterApplyConfiguration { + b.ListenerRef = &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 *LBPoolFilterApplyConfiguration) WithProjectRef(value apiv1alpha1.KubernetesNameRef) *LBPoolFilterApplyConfiguration { + b.ProjectRef = &value + return b +} + +// WithLBAlgorithm sets the LBAlgorithm 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 LBAlgorithm field is set to the value of the last call. +func (b *LBPoolFilterApplyConfiguration) WithLBAlgorithm(value apiv1alpha1.LBPoolLBAlgorithm) *LBPoolFilterApplyConfiguration { + b.LBAlgorithm = &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 *LBPoolFilterApplyConfiguration) WithProtocol(value apiv1alpha1.LBPoolProtocol) *LBPoolFilterApplyConfiguration { + b.Protocol = &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 *LBPoolFilterApplyConfiguration) WithTags(values ...apiv1alpha1.LBPoolTag) *LBPoolFilterApplyConfiguration { + 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 *LBPoolFilterApplyConfiguration) WithTagsAny(values ...apiv1alpha1.LBPoolTag) *LBPoolFilterApplyConfiguration { + 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 *LBPoolFilterApplyConfiguration) WithNotTags(values ...apiv1alpha1.LBPoolTag) *LBPoolFilterApplyConfiguration { + 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 *LBPoolFilterApplyConfiguration) WithNotTagsAny(values ...apiv1alpha1.LBPoolTag) *LBPoolFilterApplyConfiguration { + for i := range values { + b.NotTagsAny = append(b.NotTagsAny, values[i]) + } + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolimport.go new file mode 100644 index 00000000..b1effe1c --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolimport.go @@ -0,0 +1,48 @@ +/* +Copyright 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 + +// LBPoolImportApplyConfiguration represents a declarative configuration of the LBPoolImport type for use +// with apply. +type LBPoolImportApplyConfiguration struct { + ID *string `json:"id,omitempty"` + Filter *LBPoolFilterApplyConfiguration `json:"filter,omitempty"` +} + +// LBPoolImportApplyConfiguration constructs a declarative configuration of the LBPoolImport type for use with +// apply. +func LBPoolImport() *LBPoolImportApplyConfiguration { + return &LBPoolImportApplyConfiguration{} +} + +// 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 *LBPoolImportApplyConfiguration) WithID(value string) *LBPoolImportApplyConfiguration { + 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 *LBPoolImportApplyConfiguration) WithFilter(value *LBPoolFilterApplyConfiguration) *LBPoolImportApplyConfiguration { + b.Filter = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolmemberspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolmemberspec.go new file mode 100644 index 00000000..58d884c5 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolmemberspec.go @@ -0,0 +1,97 @@ +/* +Copyright 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" +) + +// LBPoolMemberSpecApplyConfiguration represents a declarative configuration of the LBPoolMemberSpec type for use +// with apply. +type LBPoolMemberSpecApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Address *apiv1alpha1.IPvAny `json:"address,omitempty"` + ProtocolPort *int32 `json:"protocolPort,omitempty"` + SubnetRef *apiv1alpha1.KubernetesNameRef `json:"subnetRef,omitempty"` + Weight *int32 `json:"weight,omitempty"` + Backup *bool `json:"backup,omitempty"` + AdminStateUp *bool `json:"adminStateUp,omitempty"` +} + +// LBPoolMemberSpecApplyConfiguration constructs a declarative configuration of the LBPoolMemberSpec type for use with +// apply. +func LBPoolMemberSpec() *LBPoolMemberSpecApplyConfiguration { + return &LBPoolMemberSpecApplyConfiguration{} +} + +// 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 *LBPoolMemberSpecApplyConfiguration) WithName(value string) *LBPoolMemberSpecApplyConfiguration { + b.Name = &value + return b +} + +// WithAddress sets the Address 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 Address field is set to the value of the last call. +func (b *LBPoolMemberSpecApplyConfiguration) WithAddress(value apiv1alpha1.IPvAny) *LBPoolMemberSpecApplyConfiguration { + b.Address = &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 *LBPoolMemberSpecApplyConfiguration) WithProtocolPort(value int32) *LBPoolMemberSpecApplyConfiguration { + b.ProtocolPort = &value + return b +} + +// WithSubnetRef sets the SubnetRef 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 SubnetRef field is set to the value of the last call. +func (b *LBPoolMemberSpecApplyConfiguration) WithSubnetRef(value apiv1alpha1.KubernetesNameRef) *LBPoolMemberSpecApplyConfiguration { + b.SubnetRef = &value + return b +} + +// WithWeight sets the Weight 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 Weight field is set to the value of the last call. +func (b *LBPoolMemberSpecApplyConfiguration) WithWeight(value int32) *LBPoolMemberSpecApplyConfiguration { + b.Weight = &value + return b +} + +// WithBackup sets the Backup 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 Backup field is set to the value of the last call. +func (b *LBPoolMemberSpecApplyConfiguration) WithBackup(value bool) *LBPoolMemberSpecApplyConfiguration { + b.Backup = &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 *LBPoolMemberSpecApplyConfiguration) WithAdminStateUp(value bool) *LBPoolMemberSpecApplyConfiguration { + b.AdminStateUp = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolmemberstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolmemberstatus.go new file mode 100644 index 00000000..68c5d83a --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolmemberstatus.go @@ -0,0 +1,120 @@ +/* +Copyright 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 + +// LBPoolMemberStatusApplyConfiguration represents a declarative configuration of the LBPoolMemberStatus type for use +// with apply. +type LBPoolMemberStatusApplyConfiguration struct { + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Address *string `json:"address,omitempty"` + ProtocolPort *int32 `json:"protocolPort,omitempty"` + SubnetID *string `json:"subnetID,omitempty"` + Weight *int32 `json:"weight,omitempty"` + Backup *bool `json:"backup,omitempty"` + AdminStateUp *bool `json:"adminStateUp,omitempty"` + ProvisioningStatus *string `json:"provisioningStatus,omitempty"` + OperatingStatus *string `json:"operatingStatus,omitempty"` +} + +// LBPoolMemberStatusApplyConfiguration constructs a declarative configuration of the LBPoolMemberStatus type for use with +// apply. +func LBPoolMemberStatus() *LBPoolMemberStatusApplyConfiguration { + return &LBPoolMemberStatusApplyConfiguration{} +} + +// 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 *LBPoolMemberStatusApplyConfiguration) WithID(value string) *LBPoolMemberStatusApplyConfiguration { + b.ID = &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 *LBPoolMemberStatusApplyConfiguration) WithName(value string) *LBPoolMemberStatusApplyConfiguration { + b.Name = &value + return b +} + +// WithAddress sets the Address 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 Address field is set to the value of the last call. +func (b *LBPoolMemberStatusApplyConfiguration) WithAddress(value string) *LBPoolMemberStatusApplyConfiguration { + b.Address = &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 *LBPoolMemberStatusApplyConfiguration) WithProtocolPort(value int32) *LBPoolMemberStatusApplyConfiguration { + b.ProtocolPort = &value + return b +} + +// WithSubnetID sets the SubnetID 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 SubnetID field is set to the value of the last call. +func (b *LBPoolMemberStatusApplyConfiguration) WithSubnetID(value string) *LBPoolMemberStatusApplyConfiguration { + b.SubnetID = &value + return b +} + +// WithWeight sets the Weight 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 Weight field is set to the value of the last call. +func (b *LBPoolMemberStatusApplyConfiguration) WithWeight(value int32) *LBPoolMemberStatusApplyConfiguration { + b.Weight = &value + return b +} + +// WithBackup sets the Backup 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 Backup field is set to the value of the last call. +func (b *LBPoolMemberStatusApplyConfiguration) WithBackup(value bool) *LBPoolMemberStatusApplyConfiguration { + b.Backup = &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 *LBPoolMemberStatusApplyConfiguration) WithAdminStateUp(value bool) *LBPoolMemberStatusApplyConfiguration { + b.AdminStateUp = &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 *LBPoolMemberStatusApplyConfiguration) WithProvisioningStatus(value string) *LBPoolMemberStatusApplyConfiguration { + 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 *LBPoolMemberStatusApplyConfiguration) WithOperatingStatus(value string) *LBPoolMemberStatusApplyConfiguration { + b.OperatingStatus = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolresourcespec.go new file mode 100644 index 00000000..0af964df --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolresourcespec.go @@ -0,0 +1,207 @@ +/* +Copyright 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" +) + +// LBPoolResourceSpecApplyConfiguration represents a declarative configuration of the LBPoolResourceSpec type for use +// with apply. +type LBPoolResourceSpecApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + LBAlgorithm *apiv1alpha1.LBPoolLBAlgorithm `json:"lbAlgorithm,omitempty"` + Protocol *apiv1alpha1.LBPoolProtocol `json:"protocol,omitempty"` + LoadBalancerRef *apiv1alpha1.KubernetesNameRef `json:"loadBalancerRef,omitempty"` + ListenerRef *apiv1alpha1.KubernetesNameRef `json:"listenerRef,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + AdminStateUp *bool `json:"adminStateUp,omitempty"` + SessionPersistence *LBPoolSessionPersistenceApplyConfiguration `json:"sessionPersistence,omitempty"` + TLSEnabled *bool `json:"tlsEnabled,omitempty"` + TLSContainerRef *string `json:"tlsContainerRef,omitempty"` + CATLSContainerRef *string `json:"caTLSContainerRef,omitempty"` + CRLContainerRef *string `json:"crlContainerRef,omitempty"` + TLSCiphers *string `json:"tlsCiphers,omitempty"` + TLSVersions []string `json:"tlsVersions,omitempty"` + ALPNProtocols []string `json:"alpnProtocols,omitempty"` + Tags []apiv1alpha1.LBPoolTag `json:"tags,omitempty"` + Members []LBPoolMemberSpecApplyConfiguration `json:"members,omitempty"` +} + +// LBPoolResourceSpecApplyConfiguration constructs a declarative configuration of the LBPoolResourceSpec type for use with +// apply. +func LBPoolResourceSpec() *LBPoolResourceSpecApplyConfiguration { + return &LBPoolResourceSpecApplyConfiguration{} +} + +// 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 *LBPoolResourceSpecApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *LBPoolResourceSpecApplyConfiguration { + 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 *LBPoolResourceSpecApplyConfiguration) WithDescription(value string) *LBPoolResourceSpecApplyConfiguration { + b.Description = &value + return b +} + +// WithLBAlgorithm sets the LBAlgorithm 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 LBAlgorithm field is set to the value of the last call. +func (b *LBPoolResourceSpecApplyConfiguration) WithLBAlgorithm(value apiv1alpha1.LBPoolLBAlgorithm) *LBPoolResourceSpecApplyConfiguration { + b.LBAlgorithm = &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 *LBPoolResourceSpecApplyConfiguration) WithProtocol(value apiv1alpha1.LBPoolProtocol) *LBPoolResourceSpecApplyConfiguration { + b.Protocol = &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 *LBPoolResourceSpecApplyConfiguration) WithLoadBalancerRef(value apiv1alpha1.KubernetesNameRef) *LBPoolResourceSpecApplyConfiguration { + b.LoadBalancerRef = &value + return b +} + +// WithListenerRef sets the ListenerRef 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 ListenerRef field is set to the value of the last call. +func (b *LBPoolResourceSpecApplyConfiguration) WithListenerRef(value apiv1alpha1.KubernetesNameRef) *LBPoolResourceSpecApplyConfiguration { + b.ListenerRef = &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 *LBPoolResourceSpecApplyConfiguration) WithProjectRef(value apiv1alpha1.KubernetesNameRef) *LBPoolResourceSpecApplyConfiguration { + 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 *LBPoolResourceSpecApplyConfiguration) WithAdminStateUp(value bool) *LBPoolResourceSpecApplyConfiguration { + b.AdminStateUp = &value + return b +} + +// WithSessionPersistence sets the SessionPersistence 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 SessionPersistence field is set to the value of the last call. +func (b *LBPoolResourceSpecApplyConfiguration) WithSessionPersistence(value *LBPoolSessionPersistenceApplyConfiguration) *LBPoolResourceSpecApplyConfiguration { + b.SessionPersistence = value + return b +} + +// WithTLSEnabled sets the TLSEnabled 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 TLSEnabled field is set to the value of the last call. +func (b *LBPoolResourceSpecApplyConfiguration) WithTLSEnabled(value bool) *LBPoolResourceSpecApplyConfiguration { + b.TLSEnabled = &value + return b +} + +// WithTLSContainerRef sets the TLSContainerRef 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 TLSContainerRef field is set to the value of the last call. +func (b *LBPoolResourceSpecApplyConfiguration) WithTLSContainerRef(value string) *LBPoolResourceSpecApplyConfiguration { + b.TLSContainerRef = &value + return b +} + +// WithCATLSContainerRef sets the CATLSContainerRef 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 CATLSContainerRef field is set to the value of the last call. +func (b *LBPoolResourceSpecApplyConfiguration) WithCATLSContainerRef(value string) *LBPoolResourceSpecApplyConfiguration { + b.CATLSContainerRef = &value + return b +} + +// WithCRLContainerRef sets the CRLContainerRef 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 CRLContainerRef field is set to the value of the last call. +func (b *LBPoolResourceSpecApplyConfiguration) WithCRLContainerRef(value string) *LBPoolResourceSpecApplyConfiguration { + b.CRLContainerRef = &value + 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 *LBPoolResourceSpecApplyConfiguration) WithTLSCiphers(value string) *LBPoolResourceSpecApplyConfiguration { + 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 *LBPoolResourceSpecApplyConfiguration) WithTLSVersions(values ...string) *LBPoolResourceSpecApplyConfiguration { + 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 *LBPoolResourceSpecApplyConfiguration) WithALPNProtocols(values ...string) *LBPoolResourceSpecApplyConfiguration { + for i := range values { + b.ALPNProtocols = append(b.ALPNProtocols, values[i]) + } + 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 *LBPoolResourceSpecApplyConfiguration) WithTags(values ...apiv1alpha1.LBPoolTag) *LBPoolResourceSpecApplyConfiguration { + for i := range values { + b.Tags = append(b.Tags, values[i]) + } + return b +} + +// WithMembers adds the given value to the Members 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 Members field. +func (b *LBPoolResourceSpecApplyConfiguration) WithMembers(values ...*LBPoolMemberSpecApplyConfiguration) *LBPoolResourceSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithMembers") + } + b.Members = append(b.Members, *values[i]) + } + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolresourcestatus.go new file mode 100644 index 00000000..e09cb89c --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolresourcestatus.go @@ -0,0 +1,234 @@ +/* +Copyright 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 + +// LBPoolResourceStatusApplyConfiguration represents a declarative configuration of the LBPoolResourceStatus type for use +// with apply. +type LBPoolResourceStatusApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + LBAlgorithm *string `json:"lbAlgorithm,omitempty"` + Protocol *string `json:"protocol,omitempty"` + LoadBalancerIDs []string `json:"loadBalancerIDs,omitempty"` + ListenerIDs []string `json:"listenerIDs,omitempty"` + ProjectID *string `json:"projectID,omitempty"` + AdminStateUp *bool `json:"adminStateUp,omitempty"` + ProvisioningStatus *string `json:"provisioningStatus,omitempty"` + OperatingStatus *string `json:"operatingStatus,omitempty"` + HealthMonitorID *string `json:"healthMonitorID,omitempty"` + Members []LBPoolMemberStatusApplyConfiguration `json:"members,omitempty"` + SessionPersistence *LBPoolSessionPersistenceApplyConfiguration `json:"sessionPersistence,omitempty"` + TLSEnabled *bool `json:"tlsEnabled,omitempty"` + TLSContainerRef *string `json:"tlsContainerRef,omitempty"` + CATLSContainerRef *string `json:"caTLSContainerRef,omitempty"` + CRLContainerRef *string `json:"crlContainerRef,omitempty"` + TLSCiphers *string `json:"tlsCiphers,omitempty"` + TLSVersions []string `json:"tlsVersions,omitempty"` + ALPNProtocols []string `json:"alpnProtocols,omitempty"` + Tags []string `json:"tags,omitempty"` +} + +// LBPoolResourceStatusApplyConfiguration constructs a declarative configuration of the LBPoolResourceStatus type for use with +// apply. +func LBPoolResourceStatus() *LBPoolResourceStatusApplyConfiguration { + return &LBPoolResourceStatusApplyConfiguration{} +} + +// 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 *LBPoolResourceStatusApplyConfiguration) WithName(value string) *LBPoolResourceStatusApplyConfiguration { + 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 *LBPoolResourceStatusApplyConfiguration) WithDescription(value string) *LBPoolResourceStatusApplyConfiguration { + b.Description = &value + return b +} + +// WithLBAlgorithm sets the LBAlgorithm 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 LBAlgorithm field is set to the value of the last call. +func (b *LBPoolResourceStatusApplyConfiguration) WithLBAlgorithm(value string) *LBPoolResourceStatusApplyConfiguration { + b.LBAlgorithm = &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 *LBPoolResourceStatusApplyConfiguration) WithProtocol(value string) *LBPoolResourceStatusApplyConfiguration { + b.Protocol = &value + return b +} + +// WithLoadBalancerIDs adds the given value to the LoadBalancerIDs 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 LoadBalancerIDs field. +func (b *LBPoolResourceStatusApplyConfiguration) WithLoadBalancerIDs(values ...string) *LBPoolResourceStatusApplyConfiguration { + for i := range values { + b.LoadBalancerIDs = append(b.LoadBalancerIDs, values[i]) + } + return b +} + +// WithListenerIDs adds the given value to the ListenerIDs 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 ListenerIDs field. +func (b *LBPoolResourceStatusApplyConfiguration) WithListenerIDs(values ...string) *LBPoolResourceStatusApplyConfiguration { + for i := range values { + b.ListenerIDs = append(b.ListenerIDs, values[i]) + } + 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 *LBPoolResourceStatusApplyConfiguration) WithProjectID(value string) *LBPoolResourceStatusApplyConfiguration { + 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 *LBPoolResourceStatusApplyConfiguration) WithAdminStateUp(value bool) *LBPoolResourceStatusApplyConfiguration { + b.AdminStateUp = &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 *LBPoolResourceStatusApplyConfiguration) WithProvisioningStatus(value string) *LBPoolResourceStatusApplyConfiguration { + 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 *LBPoolResourceStatusApplyConfiguration) WithOperatingStatus(value string) *LBPoolResourceStatusApplyConfiguration { + b.OperatingStatus = &value + return b +} + +// WithHealthMonitorID sets the HealthMonitorID 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 HealthMonitorID field is set to the value of the last call. +func (b *LBPoolResourceStatusApplyConfiguration) WithHealthMonitorID(value string) *LBPoolResourceStatusApplyConfiguration { + b.HealthMonitorID = &value + return b +} + +// WithMembers adds the given value to the Members 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 Members field. +func (b *LBPoolResourceStatusApplyConfiguration) WithMembers(values ...*LBPoolMemberStatusApplyConfiguration) *LBPoolResourceStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithMembers") + } + b.Members = append(b.Members, *values[i]) + } + return b +} + +// WithSessionPersistence sets the SessionPersistence 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 SessionPersistence field is set to the value of the last call. +func (b *LBPoolResourceStatusApplyConfiguration) WithSessionPersistence(value *LBPoolSessionPersistenceApplyConfiguration) *LBPoolResourceStatusApplyConfiguration { + b.SessionPersistence = value + return b +} + +// WithTLSEnabled sets the TLSEnabled 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 TLSEnabled field is set to the value of the last call. +func (b *LBPoolResourceStatusApplyConfiguration) WithTLSEnabled(value bool) *LBPoolResourceStatusApplyConfiguration { + b.TLSEnabled = &value + return b +} + +// WithTLSContainerRef sets the TLSContainerRef 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 TLSContainerRef field is set to the value of the last call. +func (b *LBPoolResourceStatusApplyConfiguration) WithTLSContainerRef(value string) *LBPoolResourceStatusApplyConfiguration { + b.TLSContainerRef = &value + return b +} + +// WithCATLSContainerRef sets the CATLSContainerRef 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 CATLSContainerRef field is set to the value of the last call. +func (b *LBPoolResourceStatusApplyConfiguration) WithCATLSContainerRef(value string) *LBPoolResourceStatusApplyConfiguration { + b.CATLSContainerRef = &value + return b +} + +// WithCRLContainerRef sets the CRLContainerRef 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 CRLContainerRef field is set to the value of the last call. +func (b *LBPoolResourceStatusApplyConfiguration) WithCRLContainerRef(value string) *LBPoolResourceStatusApplyConfiguration { + b.CRLContainerRef = &value + 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 *LBPoolResourceStatusApplyConfiguration) WithTLSCiphers(value string) *LBPoolResourceStatusApplyConfiguration { + 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 *LBPoolResourceStatusApplyConfiguration) WithTLSVersions(values ...string) *LBPoolResourceStatusApplyConfiguration { + 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 *LBPoolResourceStatusApplyConfiguration) WithALPNProtocols(values ...string) *LBPoolResourceStatusApplyConfiguration { + for i := range values { + b.ALPNProtocols = append(b.ALPNProtocols, values[i]) + } + 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 *LBPoolResourceStatusApplyConfiguration) WithTags(values ...string) *LBPoolResourceStatusApplyConfiguration { + for i := range values { + b.Tags = append(b.Tags, values[i]) + } + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolsessionpersistence.go b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolsessionpersistence.go new file mode 100644 index 00000000..9bd3955a --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolsessionpersistence.go @@ -0,0 +1,52 @@ +/* +Copyright 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" +) + +// LBPoolSessionPersistenceApplyConfiguration represents a declarative configuration of the LBPoolSessionPersistence type for use +// with apply. +type LBPoolSessionPersistenceApplyConfiguration struct { + Type *apiv1alpha1.LBPoolSessionPersistenceType `json:"type,omitempty"` + CookieName *string `json:"cookieName,omitempty"` +} + +// LBPoolSessionPersistenceApplyConfiguration constructs a declarative configuration of the LBPoolSessionPersistence type for use with +// apply. +func LBPoolSessionPersistence() *LBPoolSessionPersistenceApplyConfiguration { + return &LBPoolSessionPersistenceApplyConfiguration{} +} + +// WithType sets the Type 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 Type field is set to the value of the last call. +func (b *LBPoolSessionPersistenceApplyConfiguration) WithType(value apiv1alpha1.LBPoolSessionPersistenceType) *LBPoolSessionPersistenceApplyConfiguration { + b.Type = &value + return b +} + +// WithCookieName sets the CookieName 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 CookieName field is set to the value of the last call. +func (b *LBPoolSessionPersistenceApplyConfiguration) WithCookieName(value string) *LBPoolSessionPersistenceApplyConfiguration { + b.CookieName = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolspec.go new file mode 100644 index 00000000..24bf7b13 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolspec.go @@ -0,0 +1,79 @@ +/* +Copyright 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" +) + +// LBPoolSpecApplyConfiguration represents a declarative configuration of the LBPoolSpec type for use +// with apply. +type LBPoolSpecApplyConfiguration struct { + Import *LBPoolImportApplyConfiguration `json:"import,omitempty"` + Resource *LBPoolResourceSpecApplyConfiguration `json:"resource,omitempty"` + ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` + ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` +} + +// LBPoolSpecApplyConfiguration constructs a declarative configuration of the LBPoolSpec type for use with +// apply. +func LBPoolSpec() *LBPoolSpecApplyConfiguration { + return &LBPoolSpecApplyConfiguration{} +} + +// 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 *LBPoolSpecApplyConfiguration) WithImport(value *LBPoolImportApplyConfiguration) *LBPoolSpecApplyConfiguration { + 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 *LBPoolSpecApplyConfiguration) WithResource(value *LBPoolResourceSpecApplyConfiguration) *LBPoolSpecApplyConfiguration { + 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 *LBPoolSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *LBPoolSpecApplyConfiguration { + 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 *LBPoolSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *LBPoolSpecApplyConfiguration { + 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 *LBPoolSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *LBPoolSpecApplyConfiguration { + b.CloudCredentialsRef = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolstatus.go new file mode 100644 index 00000000..080dfa81 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/lbpoolstatus.go @@ -0,0 +1,66 @@ +/* +Copyright 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" +) + +// LBPoolStatusApplyConfiguration represents a declarative configuration of the LBPoolStatus type for use +// with apply. +type LBPoolStatusApplyConfiguration struct { + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *LBPoolResourceStatusApplyConfiguration `json:"resource,omitempty"` +} + +// LBPoolStatusApplyConfiguration constructs a declarative configuration of the LBPoolStatus type for use with +// apply. +func LBPoolStatus() *LBPoolStatusApplyConfiguration { + return &LBPoolStatusApplyConfiguration{} +} + +// 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 *LBPoolStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *LBPoolStatusApplyConfiguration { + 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 *LBPoolStatusApplyConfiguration) WithID(value string) *LBPoolStatusApplyConfiguration { + 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 *LBPoolStatusApplyConfiguration) WithResource(value *LBPoolResourceStatusApplyConfiguration) *LBPoolStatusApplyConfiguration { + b.Resource = value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index a014f715..75d5a071 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -998,6 +998,339 @@ 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.LBPool + 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.LBPoolSpec + default: {} + - name: status + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LBPoolStatus + default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LBPoolFilter + map: + fields: + - name: description + type: + scalar: string + - name: lbAlgorithm + type: + scalar: string + - name: listenerRef + 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: projectRef + type: + scalar: string + - name: protocol + type: + scalar: string + - 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.LBPoolImport + map: + fields: + - name: filter + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LBPoolFilter + - name: id + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LBPoolMemberSpec + map: + fields: + - name: address + type: + scalar: string + - name: adminStateUp + type: + scalar: boolean + - name: backup + type: + scalar: boolean + - name: name + type: + scalar: string + - name: protocolPort + type: + scalar: numeric + - name: subnetRef + type: + scalar: string + - name: weight + type: + scalar: numeric +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LBPoolMemberStatus + map: + fields: + - name: address + type: + scalar: string + - name: adminStateUp + type: + scalar: boolean + - name: backup + type: + scalar: boolean + - name: id + type: + scalar: string + - name: name + type: + scalar: string + - name: operatingStatus + type: + scalar: string + - name: protocolPort + type: + scalar: numeric + - name: provisioningStatus + type: + scalar: string + - name: subnetID + type: + scalar: string + - name: weight + type: + scalar: numeric +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LBPoolResourceSpec + map: + fields: + - name: adminStateUp + type: + scalar: boolean + - name: alpnProtocols + type: + list: + elementType: + scalar: string + elementRelationship: associative + - name: caTLSContainerRef + type: + scalar: string + - name: crlContainerRef + type: + scalar: string + - name: description + type: + scalar: string + - name: lbAlgorithm + type: + scalar: string + - name: listenerRef + type: + scalar: string + - name: loadBalancerRef + type: + scalar: string + - name: members + type: + list: + elementType: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LBPoolMemberSpec + elementRelationship: atomic + - name: name + type: + scalar: string + - name: projectRef + type: + scalar: string + - name: protocol + type: + scalar: string + - name: sessionPersistence + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LBPoolSessionPersistence + - name: tags + type: + list: + elementType: + scalar: string + elementRelationship: associative + - name: tlsCiphers + type: + scalar: string + - name: tlsContainerRef + type: + scalar: string + - name: tlsEnabled + type: + scalar: boolean + - name: tlsVersions + type: + list: + elementType: + scalar: string + elementRelationship: associative +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LBPoolResourceStatus + map: + fields: + - name: adminStateUp + type: + scalar: boolean + - name: alpnProtocols + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: caTLSContainerRef + type: + scalar: string + - name: crlContainerRef + type: + scalar: string + - name: description + type: + scalar: string + - name: healthMonitorID + type: + scalar: string + - name: lbAlgorithm + type: + scalar: string + - name: listenerIDs + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: loadBalancerIDs + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: members + type: + list: + elementType: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LBPoolMemberStatus + elementRelationship: atomic + - name: name + type: + scalar: string + - name: operatingStatus + type: + scalar: string + - name: projectID + type: + scalar: string + - name: protocol + type: + scalar: string + - name: provisioningStatus + type: + scalar: string + - name: sessionPersistence + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LBPoolSessionPersistence + - name: tags + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: tlsCiphers + type: + scalar: string + - name: tlsContainerRef + type: + scalar: string + - name: tlsEnabled + type: + scalar: boolean + - name: tlsVersions + type: + list: + elementType: + scalar: string + elementRelationship: atomic +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LBPoolSessionPersistence + map: + fields: + - name: cookieName + type: + scalar: string + - name: type + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LBPoolSpec + 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.LBPoolImport + - 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.LBPoolResourceSpec +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.LBPoolStatus + 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.LBPoolResourceStatus - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Listener map: fields: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 468b1284..20709d91 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -160,6 +160,26 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.KeyPairSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("KeyPairStatus"): return &apiv1alpha1.KeyPairStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("LBPool"): + return &apiv1alpha1.LBPoolApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("LBPoolFilter"): + return &apiv1alpha1.LBPoolFilterApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("LBPoolImport"): + return &apiv1alpha1.LBPoolImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("LBPoolMemberSpec"): + return &apiv1alpha1.LBPoolMemberSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("LBPoolMemberStatus"): + return &apiv1alpha1.LBPoolMemberStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("LBPoolResourceSpec"): + return &apiv1alpha1.LBPoolResourceSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("LBPoolResourceStatus"): + return &apiv1alpha1.LBPoolResourceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("LBPoolSessionPersistence"): + return &apiv1alpha1.LBPoolSessionPersistenceApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("LBPoolSpec"): + return &apiv1alpha1.LBPoolSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("LBPoolStatus"): + return &apiv1alpha1.LBPoolStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Listener"): return &apiv1alpha1.ListenerApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ListenerFilter"): 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 6efb7534..ff399c2c 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 + LBPoolsGetter ListenersGetter LoadBalancersGetter NetworksGetter @@ -80,6 +81,10 @@ func (c *OpenstackV1alpha1Client) KeyPairs(namespace string) KeyPairInterface { return newKeyPairs(c, namespace) } +func (c *OpenstackV1alpha1Client) LBPools(namespace string) LBPoolInterface { + return newLBPools(c, namespace) +} + func (c *OpenstackV1alpha1Client) Listeners(namespace string) ListenerInterface { return newListeners(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 c883425f..b8c18fd0 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) LBPools(namespace string) v1alpha1.LBPoolInterface { + return newFakeLBPools(c, namespace) +} + func (c *FakeOpenstackV1alpha1) Listeners(namespace string) v1alpha1.ListenerInterface { return newFakeListeners(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_lbpool.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_lbpool.go new file mode 100644 index 00000000..53f02f7a --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_lbpool.go @@ -0,0 +1,51 @@ +/* +Copyright 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" +) + +// fakeLBPools implements LBPoolInterface +type fakeLBPools struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.LBPool, *v1alpha1.LBPoolList, *apiv1alpha1.LBPoolApplyConfiguration] + Fake *FakeOpenstackV1alpha1 +} + +func newFakeLBPools(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.LBPoolInterface { + return &fakeLBPools{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.LBPool, *v1alpha1.LBPoolList, *apiv1alpha1.LBPoolApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("lbpools"), + v1alpha1.SchemeGroupVersion.WithKind("LBPool"), + func() *v1alpha1.LBPool { return &v1alpha1.LBPool{} }, + func() *v1alpha1.LBPoolList { return &v1alpha1.LBPoolList{} }, + func(dst, src *v1alpha1.LBPoolList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.LBPoolList) []*v1alpha1.LBPool { return gentype.ToPointerSlice(list.Items) }, + func(list *v1alpha1.LBPoolList, items []*v1alpha1.LBPool) { + 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 5c56b529..1564dfda 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 LBPoolExpansion interface{} + type ListenerExpansion interface{} type LoadBalancerExpansion interface{} diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/lbpool.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/lbpool.go new file mode 100644 index 00000000..416a643a --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/lbpool.go @@ -0,0 +1,74 @@ +/* +Copyright 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" +) + +// LBPoolsGetter has a method to return a LBPoolInterface. +// A group's client should implement this interface. +type LBPoolsGetter interface { + LBPools(namespace string) LBPoolInterface +} + +// LBPoolInterface has methods to work with LBPool resources. +type LBPoolInterface interface { + Create(ctx context.Context, lBPool *apiv1alpha1.LBPool, opts v1.CreateOptions) (*apiv1alpha1.LBPool, error) + Update(ctx context.Context, lBPool *apiv1alpha1.LBPool, opts v1.UpdateOptions) (*apiv1alpha1.LBPool, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, lBPool *apiv1alpha1.LBPool, opts v1.UpdateOptions) (*apiv1alpha1.LBPool, 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.LBPool, error) + List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.LBPoolList, 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.LBPool, err error) + Apply(ctx context.Context, lBPool *applyconfigurationapiv1alpha1.LBPoolApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.LBPool, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, lBPool *applyconfigurationapiv1alpha1.LBPoolApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.LBPool, err error) + LBPoolExpansion +} + +// lBPools implements LBPoolInterface +type lBPools struct { + *gentype.ClientWithListAndApply[*apiv1alpha1.LBPool, *apiv1alpha1.LBPoolList, *applyconfigurationapiv1alpha1.LBPoolApplyConfiguration] +} + +// newLBPools returns a LBPools +func newLBPools(c *OpenstackV1alpha1Client, namespace string) *lBPools { + return &lBPools{ + gentype.NewClientWithListAndApply[*apiv1alpha1.LBPool, *apiv1alpha1.LBPoolList, *applyconfigurationapiv1alpha1.LBPoolApplyConfiguration]( + "lbpools", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apiv1alpha1.LBPool { return &apiv1alpha1.LBPool{} }, + func() *apiv1alpha1.LBPoolList { return &apiv1alpha1.LBPoolList{} }, + ), + } +} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go index d70fcbb1..4a6ea367 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 + // LBPools returns a LBPoolInformer. + LBPools() LBPoolInformer // Listeners returns a ListenerInformer. Listeners() ListenerInformer // LoadBalancers returns a LoadBalancerInformer. @@ -109,6 +111,11 @@ func (v *version) KeyPairs() KeyPairInformer { return &keyPairInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// LBPools returns a LBPoolInformer. +func (v *version) LBPools() LBPoolInformer { + return &lBPoolInformer{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} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/lbpool.go b/pkg/clients/informers/externalversions/api/v1alpha1/lbpool.go new file mode 100644 index 00000000..1460e2a1 --- /dev/null +++ b/pkg/clients/informers/externalversions/api/v1alpha1/lbpool.go @@ -0,0 +1,102 @@ +/* +Copyright 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" +) + +// LBPoolInformer provides access to a shared informer and lister for +// LBPools. +type LBPoolInformer interface { + Informer() cache.SharedIndexInformer + Lister() apiv1alpha1.LBPoolLister +} + +type lBPoolInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewLBPoolInformer constructs a new informer for LBPool 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 NewLBPoolInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredLBPoolInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredLBPoolInformer constructs a new informer for LBPool 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 NewFilteredLBPoolInformer(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().LBPools(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().LBPools(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().LBPools(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().LBPools(namespace).Watch(ctx, options) + }, + }, + &v2apiv1alpha1.LBPool{}, + resyncPeriod, + indexers, + ) +} + +func (f *lBPoolInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredLBPoolInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *lBPoolInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&v2apiv1alpha1.LBPool{}, f.defaultInformer) +} + +func (f *lBPoolInformer) Lister() apiv1alpha1.LBPoolLister { + return apiv1alpha1.NewLBPoolLister(f.Informer().GetIndexer()) +} diff --git a/pkg/clients/informers/externalversions/generic.go b/pkg/clients/informers/externalversions/generic.go index 91df84e0..fc8a71e6 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("lbpools"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().LBPools().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("listeners"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Listeners().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("loadbalancers"): diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go index 5a0aec77..4c4dd369 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{} +// LBPoolListerExpansion allows custom methods to be added to +// LBPoolLister. +type LBPoolListerExpansion interface{} + +// LBPoolNamespaceListerExpansion allows custom methods to be added to +// LBPoolNamespaceLister. +type LBPoolNamespaceListerExpansion interface{} + // ListenerListerExpansion allows custom methods to be added to // ListenerLister. type ListenerListerExpansion interface{} diff --git a/pkg/clients/listers/api/v1alpha1/lbpool.go b/pkg/clients/listers/api/v1alpha1/lbpool.go new file mode 100644 index 00000000..8537e729 --- /dev/null +++ b/pkg/clients/listers/api/v1alpha1/lbpool.go @@ -0,0 +1,70 @@ +/* +Copyright 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" +) + +// LBPoolLister helps list LBPools. +// All objects returned here must be treated as read-only. +type LBPoolLister interface { + // List lists all LBPools in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.LBPool, err error) + // LBPools returns an object that can list and get LBPools. + LBPools(namespace string) LBPoolNamespaceLister + LBPoolListerExpansion +} + +// lBPoolLister implements the LBPoolLister interface. +type lBPoolLister struct { + listers.ResourceIndexer[*apiv1alpha1.LBPool] +} + +// NewLBPoolLister returns a new LBPoolLister. +func NewLBPoolLister(indexer cache.Indexer) LBPoolLister { + return &lBPoolLister{listers.New[*apiv1alpha1.LBPool](indexer, apiv1alpha1.Resource("lbpool"))} +} + +// LBPools returns an object that can list and get LBPools. +func (s *lBPoolLister) LBPools(namespace string) LBPoolNamespaceLister { + return lBPoolNamespaceLister{listers.NewNamespaced[*apiv1alpha1.LBPool](s.ResourceIndexer, namespace)} +} + +// LBPoolNamespaceLister helps list and get LBPools. +// All objects returned here must be treated as read-only. +type LBPoolNamespaceLister interface { + // List lists all LBPools in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.LBPool, err error) + // Get retrieves the LBPool from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*apiv1alpha1.LBPool, error) + LBPoolNamespaceListerExpansion +} + +// lBPoolNamespaceLister implements the LBPoolNamespaceLister +// interface. +type lBPoolNamespaceLister struct { + listers.ResourceIndexer[*apiv1alpha1.LBPool] +} diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 398bf9b6..ca0c9af3 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) +- [LBPool](#lbpool) - [Listener](#listener) - [LoadBalancer](#loadbalancer) - [Network](#network) @@ -171,6 +172,7 @@ _Appears in:_ - [GroupSpec](#groupspec) - [ImageSpec](#imagespec) - [KeyPairSpec](#keypairspec) +- [LBPoolSpec](#lbpoolspec) - [ListenerSpec](#listenerspec) - [LoadBalancerSpec](#loadbalancerspec) - [NetworkSpec](#networkspec) @@ -1009,6 +1011,7 @@ _Appears in:_ - [FloatingIPFilter](#floatingipfilter) - [FloatingIPResourceSpec](#floatingipresourcespec) - [HostRoute](#hostroute) +- [LBPoolMemberSpec](#lbpoolmemberspec) - [LoadBalancerResourceSpec](#loadbalancerresourcespec) - [SubnetFilter](#subnetfilter) - [SubnetGateway](#subnetgateway) @@ -1622,6 +1625,7 @@ _Appears in:_ - [GroupFilter](#groupfilter) - [GroupResourceSpec](#groupresourcespec) - [LBPoolFilter](#lbpoolfilter) +- [LBPoolMemberSpec](#lbpoolmemberspec) - [LBPoolResourceSpec](#lbpoolresourcespec) - [ListenerFilter](#listenerfilter) - [ListenerResourceSpec](#listenerresourcespec) @@ -1648,9 +1652,321 @@ _Appears in:_ +#### LBPool +LBPool is the Schema for an ORC resource. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | +| `kind` _string_ | `LBPool` | | | +| `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` _[LBPoolSpec](#lbpoolspec)_ | spec specifies the desired state of the resource. | | | +| `status` _[LBPoolStatus](#lbpoolstatus)_ | status defines the observed state of the resource. | | | + + +#### LBPoolFilter + + + +LBPoolFilter defines an existing resource by its properties + +_Validation:_ +- MinProperties: 1 + +_Appears in:_ +- [LBPoolImport](#lbpoolimport) + +| 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 pool is associated with. | | MaxLength: 253
MinLength: 1
| +| `listenerRef` _[KubernetesNameRef](#kubernetesnameref)_ | listenerRef filters by the Listener this pool is associated with. | | MaxLength: 253
MinLength: 1
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef filters by the Project this pool is associated with. | | MaxLength: 253
MinLength: 1
| +| `lbAlgorithm` _[LBPoolLBAlgorithm](#lbpoollbalgorithm)_ | lbAlgorithm filters by the load balancing algorithm. | | Enum: [LEAST_CONNECTIONS ROUND_ROBIN SOURCE_IP SOURCE_IP_PORT]
| +| `protocol` _[LBPoolProtocol](#lbpoolprotocol)_ | protocol filters by the protocol used by the pool. | | Enum: [HTTP HTTPS PROXY PROXYV2 SCTP TCP UDP]
| +| `tags` _[LBPoolTag](#lbpooltag) 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` _[LBPoolTag](#lbpooltag) 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` _[LBPoolTag](#lbpooltag) 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` _[LBPoolTag](#lbpooltag) 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
| + + +#### LBPoolImport + + + +LBPoolImport specifies an existing resource which will be imported instead of +creating a new one + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [LBPoolSpec](#lbpoolspec) + +| 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` _[LBPoolFilter](#lbpoolfilter)_ | 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
| + + +#### LBPoolLBAlgorithm + +_Underlying type:_ _string_ + +LBPoolLBAlgorithm represents the load balancing algorithm used by a pool. + +_Validation:_ +- Enum: [LEAST_CONNECTIONS ROUND_ROBIN SOURCE_IP SOURCE_IP_PORT] + +_Appears in:_ +- [LBPoolFilter](#lbpoolfilter) +- [LBPoolResourceSpec](#lbpoolresourcespec) + +| Field | Description | +| --- | --- | +| `LEAST_CONNECTIONS` | | +| `ROUND_ROBIN` | | +| `SOURCE_IP` | | +| `SOURCE_IP_PORT` | | + + +#### LBPoolMemberSpec + + + +LBPoolMemberSpec defines a member of an LB pool. + + + +_Appears in:_ +- [LBPoolResourceSpec](#lbpoolresourcespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | name is a human-readable name for the member. | | MaxLength: 255
| +| `address` _[IPvAny](#ipvany)_ | address is the IP address of the member to receive traffic. | | MaxLength: 45
MinLength: 1
| +| `protocolPort` _integer_ | protocolPort is the port on which the member application is listening. | | Maximum: 65535
Minimum: 1
| +| `subnetRef` _[KubernetesNameRef](#kubernetesnameref)_ | subnetRef is a reference to the ORC Subnet where the member resides. | | MaxLength: 253
MinLength: 1
| +| `weight` _integer_ | weight is the relative portion of traffic this member should receive.
A member with weight 10 receives 5x the traffic of a member with weight 2.
Default is 1. | | Maximum: 256
Minimum: 0
| +| `backup` _boolean_ | backup indicates whether this is a backup member. Backup members only
receive traffic when all non-backup members are down. | | | +| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the member (up=true, down=false). | | | + + +#### LBPoolMemberStatus + + + +LBPoolMemberStatus represents the observed state of a pool member. + + + +_Appears in:_ +- [LBPoolResourceStatus](#lbpoolresourcestatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `id` _string_ | id is the unique identifier of the member in OpenStack. | | MaxLength: 1024
| +| `name` _string_ | name is the human-readable name for the member. | | MaxLength: 255
| +| `address` _string_ | address is the IP address of the member. | | MaxLength: 64
| +| `protocolPort` _integer_ | protocolPort is the port on which the member is listening. | | | +| `subnetID` _string_ | subnetID is the ID of the subnet the member is on. | | MaxLength: 1024
| +| `weight` _integer_ | weight is the weight of the member for load balancing. | | | +| `backup` _boolean_ | backup indicates whether this is a backup member. | | | +| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the member. | | | +| `provisioningStatus` _string_ | provisioningStatus is the provisioning status of the member. | | MaxLength: 64
| +| `operatingStatus` _string_ | operatingStatus is the operating status of the member. | | MaxLength: 64
| + + +#### LBPoolProtocol + +_Underlying type:_ _string_ + +LBPoolProtocol represents the protocol used by a pool. + +_Validation:_ +- Enum: [HTTP HTTPS PROXY PROXYV2 SCTP TCP UDP] + +_Appears in:_ +- [LBPoolFilter](#lbpoolfilter) +- [LBPoolResourceSpec](#lbpoolresourcespec) + +| Field | Description | +| --- | --- | +| `HTTP` | | +| `HTTPS` | | +| `PROXY` | | +| `PROXYV2` | | +| `SCTP` | | +| `TCP` | | +| `UDP` | | + + +#### LBPoolResourceSpec + + + +LBPoolResourceSpec contains the desired state of the resource. + + + +_Appears in:_ +- [LBPoolSpec](#lbpoolspec) + +| 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
| +| `lbAlgorithm` _[LBPoolLBAlgorithm](#lbpoollbalgorithm)_ | lbAlgorithm is the load balancing algorithm used to distribute traffic
to the pool's members. | | Enum: [LEAST_CONNECTIONS ROUND_ROBIN SOURCE_IP SOURCE_IP_PORT]
| +| `protocol` _[LBPoolProtocol](#lbpoolprotocol)_ | protocol is the protocol used by the pool and its members for traffic. | | Enum: [HTTP HTTPS PROXY PROXYV2 SCTP TCP UDP]
| +| `loadBalancerRef` _[KubernetesNameRef](#kubernetesnameref)_ | loadBalancerRef is a reference to the ORC LoadBalancer which this pool
is associated with. Either loadBalancerRef or listenerRef must be specified. | | MaxLength: 253
MinLength: 1
| +| `listenerRef` _[KubernetesNameRef](#kubernetesnameref)_ | listenerRef is a reference to the ORC Listener which this pool is
associated with as the default pool. Either loadBalancerRef or listenerRef
must be specified. | | 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 pool, which is up (true)
or down (false). | | | +| `sessionPersistence` _[LBPoolSessionPersistence](#lbpoolsessionpersistence)_ | sessionPersistence is the session persistence configuration for the pool. | | | +| `tlsEnabled` _boolean_ | tlsEnabled enables backend re-encryption when set to true. Requires
TERMINATED_HTTPS or HTTPS protocol on the listener. | | | +| `tlsContainerRef` _string_ | tlsContainerRef is a reference to a secret containing a PKCS12 format
certificate/key bundle for backend re-encryption. | | MaxLength: 255
| +| `caTLSContainerRef` _string_ | caTLSContainerRef is a reference to a secret containing the CA
certificate for backend re-encryption. | | MaxLength: 255
| +| `crlContainerRef` _string_ | crlContainerRef is a reference to a secret containing the CA
revocation list for backend re-encryption. | | MaxLength: 255
| +| `tlsCiphers` _string_ | tlsCiphers is a colon-separated list of ciphers for backend TLS connections. | | MaxLength: 2048
| +| `tlsVersions` _string array_ | tlsVersions is a list of TLS protocol versions to be used for backend
TLS connections. | | MaxItems: 10
items:MaxLength: 32
| +| `alpnProtocols` _string array_ | alpnProtocols is a list of ALPN protocols for backend TLS connections.
Available protocols: h2, http/1.0, http/1.1. | | MaxItems: 10
items:MaxLength: 32
| +| `tags` _[LBPoolTag](#lbpooltag) array_ | tags is a list of tags which will be applied to the pool. | | MaxItems: 64
MaxLength: 255
MinLength: 1
| +| `members` _[LBPoolMemberSpec](#lbpoolmemberspec) array_ | members is a list of backend members for this pool. | | MaxItems: 256
| + + +#### LBPoolResourceStatus + + + +LBPoolResourceStatus represents the observed state of the resource. + + + +_Appears in:_ +- [LBPoolStatus](#lbpoolstatus) + +| 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
| +| `lbAlgorithm` _string_ | lbAlgorithm is the load balancing algorithm used by the pool. | | MaxLength: 64
| +| `protocol` _string_ | protocol is the protocol used by the pool. | | MaxLength: 64
| +| `loadBalancerIDs` _string array_ | loadBalancerIDs is the list of LoadBalancer IDs this pool is associated with. | | MaxItems: 64
items:MaxLength: 1024
| +| `listenerIDs` _string array_ | listenerIDs is the list of Listener IDs this pool is associated with. | | MaxItems: 64
items:MaxLength: 1024
| +| `projectID` _string_ | projectID is the ID of the Project this pool is associated with. | | MaxLength: 1024
| +| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the pool,
which is up (true) or down (false). | | | +| `provisioningStatus` _string_ | provisioningStatus is the provisioning status of the pool. | | MaxLength: 1024
| +| `operatingStatus` _string_ | operatingStatus is the operating status of the pool. | | MaxLength: 1024
| +| `healthMonitorID` _string_ | healthMonitorID is the ID of the health monitor associated with this pool. | | MaxLength: 1024
| +| `members` _[LBPoolMemberStatus](#lbpoolmemberstatus) array_ | members is the list of members in this pool with their details. | | MaxItems: 256
| +| `sessionPersistence` _[LBPoolSessionPersistence](#lbpoolsessionpersistence)_ | sessionPersistence is the session persistence configuration. | | | +| `tlsEnabled` _boolean_ | tlsEnabled indicates whether backend re-encryption is enabled. | | | +| `tlsContainerRef` _string_ | tlsContainerRef is the reference to the TLS container. | | MaxLength: 1024
| +| `caTLSContainerRef` _string_ | caTLSContainerRef is the reference to the CA TLS container. | | MaxLength: 1024
| +| `crlContainerRef` _string_ | crlContainerRef is the reference to the CRL container. | | MaxLength: 1024
| +| `tlsCiphers` _string_ | tlsCiphers is the list of TLS ciphers for backend connections. | | MaxLength: 2048
| +| `tlsVersions` _string array_ | tlsVersions is the list of TLS versions for backend connections. | | MaxItems: 10
items:MaxLength: 32
| +| `alpnProtocols` _string array_ | alpnProtocols is the list of ALPN protocols for backend connections. | | MaxItems: 10
items:MaxLength: 32
| +| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 64
items:MaxLength: 255
| + + +#### LBPoolSessionPersistence + + + +LBPoolSessionPersistence represents session persistence configuration for a pool. + + + +_Appears in:_ +- [LBPoolResourceSpec](#lbpoolresourcespec) +- [LBPoolResourceStatus](#lbpoolresourcestatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `type` _[LBPoolSessionPersistenceType](#lbpoolsessionpersistencetype)_ | type is the type of session persistence. | | Enum: [APP_COOKIE HTTP_COOKIE SOURCE_IP]
| +| `cookieName` _string_ | cookieName is the name of the cookie if persistence type is APP_COOKIE.
Required when type is APP_COOKIE. | | MaxLength: 255
| + + +#### LBPoolSessionPersistenceType + +_Underlying type:_ _string_ + +LBPoolSessionPersistenceType represents the type of session persistence. + +_Validation:_ +- Enum: [APP_COOKIE HTTP_COOKIE SOURCE_IP] + +_Appears in:_ +- [LBPoolSessionPersistence](#lbpoolsessionpersistence) + +| Field | Description | +| --- | --- | +| `APP_COOKIE` | LBPoolSessionPersistenceAppCookie relies on a cookie established by the backend application.
| +| `HTTP_COOKIE` | LBPoolSessionPersistenceHTTPCookie causes the load balancer to create a cookie on first request.
| +| `SOURCE_IP` | LBPoolSessionPersistenceSourceIP routes connections from the same source IP to the same member.
| + + +#### LBPoolSpec + + + +LBPoolSpec defines the desired state of an ORC object. + + + +_Appears in:_ +- [LBPool](#lbpool) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `import` _[LBPoolImport](#lbpoolimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| +| `resource` _[LBPoolResourceSpec](#lbpoolresourcespec)_ | 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 | | | + + +#### LBPoolStatus + + + +LBPoolStatus defines the observed state of an ORC resource. + + + +_Appears in:_ +- [LBPool](#lbpool) + +| 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` _[LBPoolResourceStatus](#lbpoolresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | + + +#### LBPoolTag + +_Underlying type:_ _string_ + + + +_Validation:_ +- MaxLength: 255 +- MinLength: 1 + +_Appears in:_ +- [LBPoolFilter](#lbpoolfilter) +- [LBPoolResourceSpec](#lbpoolresourcespec) @@ -2127,6 +2443,7 @@ _Appears in:_ - [GroupSpec](#groupspec) - [ImageSpec](#imagespec) - [KeyPairSpec](#keypairspec) +- [LBPoolSpec](#lbpoolspec) - [ListenerSpec](#listenerspec) - [LoadBalancerSpec](#loadbalancerspec) - [NetworkSpec](#networkspec) @@ -2163,6 +2480,7 @@ _Appears in:_ - [GroupSpec](#groupspec) - [ImageSpec](#imagespec) - [KeyPairSpec](#keypairspec) +- [LBPoolSpec](#lbpoolspec) - [ListenerSpec](#listenerspec) - [LoadBalancerSpec](#loadbalancerspec) - [NetworkSpec](#networkspec)